From 68162242916447872fac49e5fe0b31aa6949300a Mon Sep 17 00:00:00 2001 From: ckdvk Date: Thu, 6 Feb 2025 16:05:17 +0800 Subject: [PATCH 01/36] =?UTF-8?q?Setting=20para=20edici=C3=B3n=20en=20WSL?= =?UTF-8?q?=20y=20revisi=C3=B3n=20de=20la=20introducci=C3=B3n.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 00_intro.md | 84 +++++++++++++++++++++++---------------------- html/00_intro.html | 84 +++++++++++++++++++++++---------------------- html/01_values.html | 14 ++++---- html/ejs.js | 2 +- package.json | 1 + 5 files changed, 95 insertions(+), 90 deletions(-) diff --git a/00_intro.md b/00_intro.md index b8932db0..b9e0685c 100644 --- a/00_intro.md +++ b/00_intro.md @@ -2,7 +2,7 @@ # Introducción -{{quote {author: "Ellen Ullman", title: "Cerca de la máquina: Tecnofilia y sus Descontentos", chapter: true} +{{quote {author: "Ellen Ullman", title: "Close to the Machine: Technophilia and Its Discontents", chapter: true} Creemos que estamos creando el sistema para nuestros propios propósitos. Creemos que lo estamos haciendo a nuestra propia imagen... Pero la computadora en realidad no es como nosotros. Es una proyección de una parte muy pequeña de nosotros mismos: esa parte dedicada a la lógica, el orden, la regla y la claridad. @@ -10,37 +10,37 @@ quote}} {{figure {url: "img/chapter_picture_00.jpg", alt: "Ilustración de un destornillador junto a una placa de circuitos de aproximadamente el mismo tamaño", chapter: "framed"}}} -Este es un libro sobre cómo instruir a ((computadora))s. Las computadoras son tan comunes como los destornilladores hoy en día, pero son bastante más complejas, y hacer que hagan lo que quieres no siempre es fácil. +Este es un libro sobre cómo instruir a las ((computadora))s. Las computadoras son tan comunes como los destornilladores hoy en día, pero son bastante más complejas, y hacer que hagan lo que quieres no siempre es fácil. -Si la tarea que tienes para tu computadora es común, bien entendida, como mostrarte tu correo electrónico o actuar como una calculadora, puedes abrir la ((aplicación)) correspondiente y ponerte a trabajar. Pero para tareas únicas o abiertas, a menudo no hay una aplicación adecuada. +Si la tarea que tienes para tu computadora es algo común y bien conocido como mostrarte tu correo electrónico funcionar a modo de calculadora, puedes abrir la ((aplicación)) correspondiente y ponerte a trabajar. Sin embargo, para tareas únicas o abiertas, a menudo no hay una aplicación adecuada. -Ahí es donde entra en juego la programación. _Programar_ es el acto de construir un _programa_—un conjunto de instrucciones precisas que le dicen a una computadora qué hacer. Debido a que las computadoras son bestias tontas y pedantes, programar es fundamentalmente tedioso y frustrante. +Ahí es donde entra en juego la programación. _Programar_ es el acto de construir un _programa_—un conjunto de instrucciones precisas que le dicen a una computadora qué hacer. Dado que las computadoras son criaturas estúpidas y cuadriculadas, programar resulta ser una tarea fundamentalmente tediosa y frustrante. {{index ["programación", "la alegría de"], speed}} -Por suerte, si puedes superar ese hecho, e incluso disfrutar del rigor de pensar en términos que las máquinas tontas pueden manejar, programar puede ser gratificante. Te permite hacer cosas en segundos que te tomarían _una eternidad_ a mano. Es una forma de hacer que tu herramienta informática haga cosas que antes no podía hacer. Además, se convierte en un maravilloso juego de resolución de acertijos y pensamiento abstracto. +Por suerte, si eres capaz de afrontar esto —y también quizá si disfrutas del rigor de pensar en términos que una de estas máquinas pueda entender— programar puede ser gratificante. Te permite hacer en segundos cosas que te tomarían _una eternidad_ a mano. Es una forma de hacer que tu herramienta informática haga cosas que antes no podía hacer. Además, se convierte en un maravilloso juego de resolución de puzles y pensamiento abstracto. -La mayoría de la programación se realiza con ((lenguajes de programación)). Un _lenguaje de programación_ es un lenguaje artificialmente construido utilizado para instruir a las computadoras. Es interesante que la forma más efectiva que hemos encontrado para comunicarnos con una computadora se base tanto en la forma en que nos comunicamos entre nosotros. Al igual que los idiomas humanos, los lenguajes informáticos permiten combinar palabras y frases de nuevas formas, lo que permite expresar conceptos cada vez más nuevos. +La mayoría de la programación se realiza con ((lenguajes de programación)). Un _lenguaje de programación_ es un lenguaje artificialmente construido utilizado para dar instrucciones a las computadoras. Es interesante que la forma más efectiva que hemos encontrado para comunicarnos con una computadora se base tanto en la forma en que nos comunicamos entre nosotros. Al igual que los idiomas humanos, los lenguajes informáticos permiten combinar palabras y frases de nuevas maneras, permitiendo expresar nuevos conceptos que no se habían expresado antes. {{index [JavaScript, "availability of"], "casual computing"}} -En un momento dado, las interfaces basadas en lenguaje, como los _prompts_ de BASIC y DOS de los años 1980 y 1990, eran el principal método de interactuar con las computadoras. Para el uso informático rutinario, estas se han reemplazado en gran medida por interfaces visuales, que son más fáciles de aprender pero ofrecen menos libertad. Pero si sabes dónde buscar, los lenguajes todavía están ahí. Uno de ellos, _JavaScript_, está integrado en cada navegador web moderno y por lo tanto está disponible en casi todos los dispositivos. +En un momento dado, las interfaces basadas en lenguaje, como los _prompts_ de BASIC y DOS de los años 1980 y 1990, eran el principal método de interacción con las computadoras. n el uso del día a día, estas se han reemplazado en gran medida por interfaces visuales, que son más fáciles de aprender, aunque ofrecen menos libertad. No obstante, si sabes dónde mirar, los lenguajes de programación siguen ahí. Uno de ellos, _JavaScript_, está integrado en cada navegador web moderno —y por tanto está disponible en casi todos los dispositivos. {{indexsee "web browser", browser}} -Este libro intentará que te familiarices lo suficiente con este lenguaje para hacer cosas útiles y entretenidas con él. +Este libro intentará que te familiarices lo suficiente con este lenguaje como para hacer cosas útiles y entretenidas con él. ## Sobre la programación {{index ["programación", "la dificultad de"]}} -Además de explicar JavaScript, presentaré los principios básicos de la programación. Resulta que programar es difícil. Las reglas fundamentales son simples y claras, pero los programas construidos sobre estas reglas tienden a volverse lo suficientemente complejos como para introducir sus propias reglas y complejidades. Estás construyendo tu propio laberinto, de alguna manera, y fácilmente puedes perderte en él. +Además de explicar JavaScript, presentaré los principios básicos de la programación. Resulta que programar es una tarea difícil. Las reglas fundamentales son simples y claras, pero los programas construidos sobre estas reglas tienden a volverse lo suficientemente complejos como para introducir sus propias reglas y complejidades. De algún modo, estás construyendo tu propio laberinto, y es fácil que te pierdas en él. {{index aprendizaje}} -Habrá momentos en los que leer este libro resulte terriblemente frustrante. Si eres nuevo en la programación, habrá mucho material nuevo que asimilar. Gran parte de este material luego se combinará de maneras que requieren que hagas conexiones adicionales. +Habrá momentos en los que leer este libro resulte terriblemente frustrante. Si eres nuevo en la programación, habrá mucho material nuevo que asimilar. Gran parte de este material luego se _combinará_ de maneras que requierirán que hagas nuevas conexiones mentales. -Depende de ti hacer el esfuerzo necesario. Cuando te cueste seguir el libro, no saques conclusiones precipitadas sobre tus propias capacidades. Estás bien, simplemente necesitas seguir adelante. Tómate un descanso, vuelve a leer algo de material y asegúrate de leer y comprender los programas de ejemplo y los ((ejercicios)). Aprender es un trabajo duro, pero todo lo que aprendas será tuyo y facilitará aún más el aprendizaje futuro. +Depende de ti hacer el esfuerzo necesario. Cuando te cueste seguir el libro, no saques conclusiones precipitadas sobre tus propias capacidades. Está todo bien —simplemente necesitas seguir adelante. Tómate un descanso, vuelve a leer algo de material y asegúrate de leer y comprender los programas de ejemplo y los ((ejercicios)). Aprender es un trabajo duro, pero todo lo que aprendas será tuyo y facilitará aún más el aprendizaje futuro. {{quote {autor: "Ursula K. Le Guin", title: "La mano izquierda de la oscuridad"} @@ -52,27 +52,27 @@ quote}} {{index [programa, "naturaleza de"], datos}} -Un programa es muchas cosas. Es un trozo de texto escrito por un programador, es la fuerza directiva que hace que la computadora haga lo que hace, es información en la memoria de la computadora, y al mismo tiempo controla las acciones realizadas en esta memoria. Las analogías que intentan comparar los programas con objetos familiares tienden a quedarse cortas. Una comparación vagamente adecuada es comparar un programa con una máquina: suelen estar implicadas muchas partes separadas y, para hacer que todo funcione, debemos considerar las formas en que estas partes se interconectan y contribuyen a la operación del conjunto. +Un programa es muchas cosas. Es un trozo de texto escrito por un programador, es la fuerza directriz que hace que la computadora haga lo que hace, es información en la memoria de la computadora, y al mismo tiempo controla las acciones realizadas en esta memoria. Las analogías que intentan comparar los programas con objetos familiares tienden a quedarse cortas. Una comparación vagamente adecuada es comparar un programa con una máquina: suelen estar formadas por muchas partes separadas y, para hacer que todo funcione, debemos considerar las formas en que estas partes se interconectan y contribuyen a la operación del conjunto. -Una ((computadora)) es una máquina física que actúa como anfitriona de estas máquinas inmateriales. Las computadoras mismas solo pueden hacer cosas increíblemente sencillas. La razón por la que son tan útiles es que hacen estas cosas a una velocidad increíblemente alta. Un programa puede combinar ingeniosamente un número enorme de estas acciones simples para hacer cosas muy complicadas. +Una ((computadora)) es una máquina física que actúa como anfitriona de estas máquinas inmateriales. Una computadora por si sola solo es capaz de hacer cosas estúpidamente sencillas. La razón por la que son tan útiles es que hacen estas cosas a una velocidad increíblemente alta. Un programa puede combinar ingeniosamente un número enorme de estas acciones simples para hacer cosas muy complicadas. {{index ["programación", "alegría de"]}} -Un programa es una construcción del pensamiento. Es gratuito de construir, es liviano y crece fácilmente bajo nuestras manos al teclear. Pero a medida que un programa crece, también lo hace su ((complejidad)). La habilidad de programar es la habilidad de construir programas que no te confundan a ti mismo. Los mejores programas son aquellos que logran hacer algo interesante mientras siguen siendo fáciles de entender. +Un programa es una construcción del pensamiento. No tiene coste ni peso, y crece fácilmente según tecleamos. Pero a medida que un programa crece, también lo hace su ((complejidad)). La habilidad de programar es la habilidad de construir programas que no te confundan a ti mismo. Los mejores programas son aquellos que logran hacer algo interesante mientras siguen siendo fáciles de entender. {{index "estilo de programación", "mejores prácticas"}} -Algunos programadores creen que esta complejidad se gestiona mejor utilizando solo un conjunto pequeño de técnicas bien comprendidas en sus programas. Han compuesto reglas estrictas ("mejores prácticas") que prescriben la forma que deberían tener los programas y se mantienen cuidadosamente dentro de su pequeña zona segura. +Algunos programadores creen que esta complejidad se gestiona mejor utilizando solo un puñado de técnicas conocidas en sus programas. Han creado reglas estrictas ("mejores prácticas") que prescriben la forma que deberían tener los programas y se mantienen diligentemente dentro de su pequeño espacio seguro. {{index experimento}} -Esto no solo es aburrido, es inefectivo. A menudo, nuevos problemas requieren soluciones nuevas. El campo de la programación es joven y aún se está desarrollando rápidamente, y es lo suficientemente variado como para tener espacio para enfoques radicalmente diferentes. Hay muchos errores terribles que cometer en el diseño de programas, y deberías ir y cometerlos al menos una vez para entenderlos. Una noción de cómo es un buen programa se desarrolla con la práctica, no se aprende de una lista de reglas. +Esto no solo es aburrido sino que es ineficaz. A menudo, nuevos problemas requieren soluciones nuevas. El campo de la programación es joven y sin embargo se está desarrollando rápidamente, con variedad suficiente como para adoptar enfoques radicalmente distintos. Hay muchos errores terribles que cometer en el diseño de un programa, y deberías cometerlos al menos una vez para entenderlos. Una noción de cómo es un buen programa se desarrolla con la práctica, no se aprende de una lista de reglas. ## Por qué importa el lenguaje {{index "lenguaje de programación", "código de máquina", "datos binarios"}} -Al principio, en los inicios de la informática, no existían los lenguajes de programación. Los programas lucían algo así: +Al principio, en los inicios de la informática, no existían los lenguajes de programación. Los programas tenían una pinta como la siguiente: ```{lang: null} 00110001 00000000 00000000 @@ -88,11 +88,11 @@ Al principio, en los inicios de la informática, no existían los lenguajes de p {{index ["programación", "historia de"], "tarjeta perforada", complejidad}} -Este es un programa para sumar los números del 1 al 10 y mostrar el resultado: `1 + 2 + ... + 10 = 55`. Podría ejecutarse en una máquina hipotética simple. Para programar los primeros ordenadores, era necesario configurar grandes conjuntos de interruptores en la posición correcta o perforar agujeros en tiras de cartón y alimentarlos al ordenador. Puedes imaginar lo tedioso y propenso a errores que era este procedimiento. Incluso escribir programas simples requería mucha astucia y disciplina. Los complejos eran casi inconcebibles. +Este es un programa para sumar los números del 1 al 10 y mostrar el resultado: `1 + 2 + ... + 10 = 55`. Podría ejecutarse en una máquina hipotética simple. Para programar los primeros ordenadores, era necesario configurar grandes conjuntos de interruptores en la posición correcta o perforar agujeros en tiras de cartón y dárselos a la computadora. Ya te puedes imaginar lo tedioso y propenso a errores que era este procedimiento. Incluso escribir programas simples requería de mucha astucia y disciplina. Los complejos eran casi inconcebibles. {{index bit, "mago (poderoso)"}} -Por supuesto, introducir manualmente estos patrones arcanos de bits (los unos y ceros) hacía que el programador se sintiera como un mago poderoso. Y eso debe valer algo en términos de satisfacción laboral. +Por supuesto, introducir manualmente estos misteriosos patrones de bits (los unos y ceros) hacía que el programador se sintiera como un mago poderoso. Y eso debe valer algo en términos de satisfacción laboral. {{index memoria, "instrucción"}} @@ -110,7 +110,7 @@ Cada línea del programa anterior contiene una única instrucción. Podría escr {{index legibilidad, nomenclatura, enlace}} -Aunque eso ya es más legible que la sopa de bits, sigue siendo bastante confusa. Usar nombres en lugar de números para las instrucciones y las ubicaciones de memoria ayuda: +Aunque eso ya es más legible que la sopa de bits anterior, sigue siendo bastante confuso. Usar nombres en lugar de números para las instrucciones y las ubicaciones de memoria ayuda: ```{lang: "null"} Establecer “total” en 0. @@ -128,7 +128,7 @@ Aunque eso ya es más legible que la sopa de bits, sigue siendo bastante confusa {{index bucle, salto, "ejemplo de suma"}} -¿Puedes ver cómo funciona el programa en este punto? Las dos primeras líneas asignan los valores iniciales a dos ubicaciones de memoria: `total` se utilizará para construir el resultado de la computación, y `count` llevará la cuenta del número que estamos observando en ese momento. Las líneas que utilizan `compare` probablemente sean las más confusas. El programa quiere ver si `count` es igual a 11 para decidir si puede dejar de ejecutarse. Debido a que nuestra máquina hipotética es bastante primitiva, solo puede comprobar si un número es cero y tomar una decisión en función de ese valor. Por lo tanto, utiliza la ubicación de memoria etiquetada como `compare` para calcular el valor de `count - 11` y tomar una decisión basada en ese valor. Las siguientes dos líneas suman el valor de `count` al resultado e incrementan `count` en 1 cada vez que el programa decide que `count` aún no es 11. Aquí está el mismo programa en JavaScript: +¿Puedes ver cómo funciona el programa en este punto? Las dos primeras líneas asignan los valores iniciales a dos ubicaciones de memoria: `total` se utilizará para construir el resultado de la suma, y `count` llevará la cuenta del número que estamos observando en ese momento. Las líneas que utilizan `compare` probablemente sean las más confusas. El programa quiere ver si `count` es igual a 11 para decidir si puede parar de ejecutarse. Debido a que nuestra máquina hipotética es bastante primitiva, solo puede comprobar si un número es cero y tomar una decisión en función de ese valor. Por lo tanto, utiliza la ubicación de memoria etiquetada como `compare` para calcular el valor de `count - 11` y tomar una decisión basada en el resultado. Las siguientes dos líneas suman el valor de `count` al resultado e incrementan `count` en 1 cada vez que el programa decide que `count` aún no vale 11. Aquí está el mismo programa en JavaScript: ``` let total = 0, count = 1; @@ -142,15 +142,15 @@ console.log(total); {{index "bucle while", bucle, [llaves, bloque]}} -Esta versión nos proporciona algunas mejoras. Lo más importante es que ya no es necesario especificar la forma en que queremos que el programa salte hacia adelante y hacia atrás; la construcción `while` se encarga de eso. Continúa ejecutando el bloque (entre llaves) debajo de él siempre y cuando se cumpla la condición que se le ha dado. Esa condición es `count <= 10`, lo que significa "el recuento es menor o igual a 10". Ya no tenemos que crear un valor temporal y compararlo con cero, lo cual era simplemente un detalle no interesante. Parte del poder de los lenguajes de programación es que pueden encargarse de los detalles no interesantes por nosotros. +Esta versión nos proporciona algunas mejoras más. Lo más importante es que ya no es necesario especificar la forma en que queremos que el programa salte hacia adelante y hacia atrás; la construcción `while` se encarga de eso. Continúa ejecutando el bloque (entre llaves) debajo de él siempre y cuando se cumpla la condición que se le ha dado. Esa condición es `count <= 10`, lo que significa "el recuento es menor o igual a 10". Ya no tenemos que crear un valor temporal y compararlo con cero, lo cual era simplemente un detalle carente de interés. Parte del poder de los lenguajes de programación es que pueden encargarse de los detalles que no nos interesan. {{index "console.log"}} -Al final del programa, después de que la construcción `while` haya terminado, se utiliza la operación `console.log` para escribir el resultado. +Al final del programa, después de que la construcción `while` haya terminado, se utiliza la operación `console.log` para mostrar el resultado. {{index "función de suma", "función de rango", "abstracción", "función"}} -Finalmente, así es como podría verse el programa si tuviéramos a nuestra disposición las operaciones convenientes `rango` y `suma`, que respectivamente crean una colección de números dentro de un rango y calculan la suma de una colección de números: +Finalmente, así es como podría verse el programa si tuviéramos a nuestra disposición las útiles operaciones `rango` y `suma`, que crean una colección de números dentro de un rango y calculan la suma de una colección de números, respectivamente: ```{startCode: true} console.log(suma(rango(1, 10))); @@ -159,11 +159,11 @@ console.log(suma(rango(1, 10))); {{index legibilidad}} -La moraleja de esta historia es que el mismo programa puede expresarse de formas largas y cortas, ilegibles y legibles. La primera versión del programa era extremadamente críptica, mientras que esta última es casi en inglés: registra (`log`) la `suma` del `rango` de números del 1 al 10. (Veremos en [capítulos posteriores](data) cómo definir operaciones como `suma` y `rango`.) +La moraleja de esta historia es que un mismo programa puede expresarse de formas largas y cortas, ilegibles y legibles. La primera versión del programa era extremadamente críptica, mientras que esta última es casi hablar en inglés: registra (`log`) la `suma` del `rango` de números del 1 al 10 (veremos en [capítulos posteriores](data) cómo definir operaciones como `suma` y `rango`). {{index ["lenguaje de programación", "poder de"], composabilidad}} -Un buen lenguaje de programación ayuda al programador al permitirle hablar sobre las acciones que la computadora debe realizar a un nivel más alto. Ayuda a omitir detalles, proporciona bloques de construcción convenientes (como `while` y `console.log`), te permite definir tus propios bloques de construcción (como `suma` y `rango`), y hace que esos bloques sean fáciles de componer. +Un buen lenguaje de programación ayuda al programador, al permitirle hablar sobre las acciones que la computadora debe realizar a un más alto nivel. Ayuda a omitir detalles, proporciona bloques de construcción convenientes (como `while` y `console.log`), te permite definir tus propios bloques de construcción (como `suma` y `rango`), y hace que esos bloques sean fáciles de componer. ## ¿Qué es JavaScript? @@ -171,19 +171,19 @@ Un buen lenguaje de programación ayuda al programador al permitirle hablar sobr {{indexsee WWW, "World Wide Web"}} -JavaScript fue introducido en 1995 como una forma de agregar programas a páginas web en el navegador Netscape Navigator. Desde entonces, el lenguaje ha sido adoptado por todos los demás navegadores web gráficos principales. Ha hecho posibles aplicaciones web modernas, es decir, aplicaciones con las que puedes interactuar directamente sin tener que recargar la página para cada acción. JavaScript también se utiliza en sitios web más tradicionales para proporcionar distintas formas de interactividad e ingenio. +JavaScript fue introducido en 1995 como una forma de agregar programas a páginas web en el navegador Netscape Navigator. Desde entonces, el lenguaje ha sido adoptado por todos los demás principales navegadores web gráficos. Ha hecho posibles aplicaciones web modernas, es decir, aplicaciones con las que puedes interactuar directamente sin tener que recargar la página para cada acción. JavaScript también se utiliza en sitios web más tradicionales para proporcionar distintas formas de interactividad e ingenio. {{index Java, nombre}} -Es importante tener en cuenta que JavaScript casi no tiene nada que ver con el lenguaje de programación llamado Java. El nombre similar fue inspirado por consideraciones de marketing en lugar de un buen juicio. Cuando se estaba introduciendo JavaScript, el lenguaje Java se estaba comercializando mucho y ganaba popularidad. Alguien pensó que era una buena idea intentar aprovechar este éxito. Ahora estamos atrapados con el nombre. +Es importante mencionar que JavaScript no tiene casi nada que ver con el lenguaje de programación llamado Java. La elección de un nombre tan parecido se debe más a consideraciones de marketing que a un buen criterio. Cuando se estaba introduciendo JavaScript, el lenguaje Java se estaba comercializando mucho y ganaba popularidad. Alguien pensó que era una buena idea intentar aprovechar este éxito y ahora tenemos que quedarnos con el nombre. {{index ECMAScript, compatibilidad}} -Después de su adopción fuera de Netscape, se escribió un ((documento estándar)) para describir la forma en que debería funcionar el lenguaje JavaScript para que las diversas piezas de software que afirmaban soportar JavaScript pudieran asegurarse de que realmente proporcionaban el mismo lenguaje. Esto se llama el estándar ECMAScript, según la organización Ecma International que llevó a cabo la estandarización. En la práctica, los términos ECMAScript y JavaScript se pueden usar indistintamente, son dos nombres para el mismo lenguaje. +Después de su adopción fuera de Netscape, se redactó un ((documento estándar)) para describir cómo debería funcionar el lenguaje JavaScript, de manera que los diferentes programas que decían soportar JavaScript pudieran asegurarse de que realmente proporcionaban el mismo lenguaje. A esto se le llama el estándar ECMAScript, en honor a la organización Ecma International que llevó a cabo la estandarización. En la práctica, los términos ECMAScript y JavaScript se pueden usar de manera intercambiable; son dos nombres para el mismo lenguaje. {{index JavaScript, "debilidades de", "depuración"}} -Hay quienes dirán cosas _terribles_ sobre JavaScript. Muchas de esas cosas son ciertas. Cuando me pidieron que escribiera algo en JavaScript por primera vez, rápidamente llegué a detestarlo. Aceptaba casi cualquier cosa que escribía pero lo interpretaba de una manera completamente diferente a lo que yo quería decir. Esto tenía mucho que ver con el hecho de que no tenía ni idea de lo que estaba haciendo, por supuesto, pero hay un problema real aquí: JavaScript es ridículamente liberal en lo que permite. La idea detrás de este diseño era que haría la programación en JavaScript más fácil para principiantes. En realidad, esto hace que encontrar problemas en tus programas sea más difícil porque el sistema no te los señalará. +Hay quienes dirán cosas _terribles_ sobre JavaScript. Muchas de ellas son ciertas. Cuando me pidieron que escribiera algo en JavaScript por primera vez, empecé a detestarlo rápidamente. Aceptaba casi cualquier cosa que escribía pero lo interpretaba de una manera completamente diferente a lo que yo quería decir. Esto tenía mucho que ver con el hecho de que yo no tenía ni idea de lo que estaba haciendo, por supuesto, pero hay un problema real aquí: JavaScript es ridículamente flexible en lo que permite. La idea detrás de este diseño era que haría la programación en JavaScript más fácil para principiantes. En realidad, esto hace que encontrar problemas en tus programas sea más difícil porque el sistema no te los va a señalar. {{index JavaScript, "flexibilidad de"}} @@ -191,19 +191,19 @@ Esta flexibilidad también tiene sus ventajas. Deja espacio para técnicas impos {{index futuro, [JavaScript, "versiones de"], ECMAScript, "ECMAScript 6"}} -Ha habido varias versiones de JavaScript. La versión ECMAScript 3 fue la versión ampliamente soportada durante el ascenso al dominio de JavaScript, aproximadamente entre 2000 y 2010. Durante este tiempo, se estaba trabajando en una versión ambiciosa 4, la cual planeaba una serie de mejoras y extensiones radicales al lenguaje. Cambiar un lenguaje vivo y ampliamente utilizado de esa manera resultó ser políticamente difícil, y el trabajo en la versión 4 fue abandonado en 2008. Una versión 5, mucho menos ambiciosa, que solo realizaba algunas mejoras no controversiales, salió en 2009. En 2015, salió la versión 6, una actualización importante que incluía algunas de las ideas previstas para la versión 4. Desde entonces, hemos tenido nuevas actualizaciones pequeñas cada año. +Ha habido varias versiones de JavaScript. La versión ECMAScript 3 fue la versión más respaldada durante el ascenso al dominio de JavaScript, aproximadamente entre 2000 y 2010. Durante este tiempo, se estaba trabajando en una ambiciosa versión 4, la cual planeaba una serie de mejoras y extensiones radicales del lenguaje. Cambiar un lenguaje vivo y ampliamente utilizado de esa manera resultó ser políticamente difícil, y el trabajo en la versión 4 se abandonó en 2008. Una mucho menos ambiciosa versión 5, que solo realizaba algunas mejoras poco controvertidas, se lanzó en 2009. En 2015, salió la versión 6, una actualización importante que incluía algunas de las ideas previstas para la versión 4. Desde entonces, hemos tenido nuevas pequeñas actualizaciones cada año. -El hecho de que JavaScript esté evolucionando significa que los navegadores tienen que mantenerse constantemente al día. Si estás usando un navegador más antiguo, es posible que no admita todas las funciones. Los diseñadores del lenguaje se aseguran de no realizar cambios que puedan romper programas existentes, por lo que los nuevos navegadores aún pueden ejecutar programas antiguos. En este libro, estoy utilizando la versión 2023 de JavaScript. +El hecho de que JavaScript esté evolucionando significa que los navegadores tienen que mantenerse constantemente al día. Si estás usando un navegador más antiguo, es posible que no admita todas las funciones. Los diseñadores del lenguaje se aseguran de no realizar cambios que puedan romper programas ya existentes, por lo que los nuevos navegadores aún pueden ejecutar programas antiguos. En este libro, estoy utilizando la versión 2023 de JavaScript. {{index [JavaScript, "usos de"]}} Los navegadores web no son las únicas plataformas en las que se utiliza JavaScript. Algunas bases de datos, como MongoDB y CouchDB, utilizan JavaScript como su lenguaje de secuencias de comandos y consulta. Varias plataformas para programación de escritorio y servidores, especialmente el proyecto ((Node.js)) (el tema del [Capítulo ?](node)), proporcionan un entorno para programar en JavaScript fuera del navegador. -## Código y qué hacer con él +## Código, y qué hacer con él {{index "leer código", "escribir código"}} -El _código_ es el texto que constituye los programas. La mayoría de los capítulos en este libro contienen bastante código. Creo que leer código y escribir ((código)) son partes indispensables de ((aprender)) a programar. Intenta no solo echar un vistazo a los ejemplos, léelos atentamente y entiéndelos. Esto puede ser lento y confuso al principio, pero te prometo que pronto le tomarás la mano. Lo mismo ocurre con los ((ejercicios)). No des por sentado que los entiendes hasta que hayas escrito realmente una solución que funcione. +El _código_ es el texto que constituye los programas. La mayoría de los capítulos en este libro contienen bastante código. Creo que leer código y escribir ((código)) son partes indispensables de ((aprender)) a programar. Intenta no solo mirar por encima los ejemplos, léelos atentamente y entiéndelos. Esto puede ser lento y confuso al principio, pero te prometo que pronto le pillarás el truco. Lo mismo ocurre con los ((ejercicios)). No des por sentado que los entiendes hasta que hayas escrito una solución que realmente funcione. {{index "interpretación"}} @@ -225,17 +225,19 @@ if}} {{index "herramientas de desarrollo", "consola de JavaScript"}} -Ejecutar los programas definidos en este libro fuera del sitio web del libro requiere cierto cuidado. Muchos ejemplos son independientes y deberían funcionar en cualquier entorno de JavaScript. Pero el código en los capítulos posteriores a menudo está escrito para un entorno específico (navegador o Node.js) y solo puede ejecutarse allí. Además, muchos capítulos definen programas más grandes, y las piezas de código que aparecen en ellos dependen unas de otras o de archivos externos. El [sandbox](https://eloquentjavascript.net/code) en el sitio web proporciona enlaces a archivos ZIP que contienen todos los scripts y archivos de datos necesarios para ejecutar el código de un capítulo dado. +Ejecutar los programas definidos en este libro fuera del sitio web del libro requiere cierto cuidado. Muchos ejemplos son independientes y deberían funcionar en cualquier entorno de JavaScript. Pero el código en los capítulos posteriores a menudo está escrito para un entorno específico (navegador o Node.js) y solo puede ejecutarse allí. Además, muchos capítulos definen programas más grandes, y los trozos de código que aparecen en ellos dependen unos de otros, o de archivos externos. El [sandbox](https://eloquentjavascript.net/code) en el sitio web proporciona enlaces a archivos ZIP que contienen todos los scripts y archivos de datos necesarios para ejecutar el código de un capítulo dado. ## Visión general de este libro -Este libro consta aproximadamente de tres partes. Los primeros 12 capítulos tratan sobre el lenguaje JavaScript. Los siguientes siete capítulos son acerca de los navegadores web y la forma en que se utiliza JavaScript para programarlos. Por último, dos capítulos están dedicados a ((Node.js)), otro entorno para programar en JavaScript. Hay cinco _capítulos de proyectos_ en el libro que describen programas de ejemplo más grandes para darte una idea de la programación real. +Este libro consta aproximadamente de tres partes. Los primeros 12 capítulos tratan sobre el lenguaje JavaScript. Los siguientes siete capítulos son acerca de los navegadores web y la forma en que se utiliza JavaScript para programarlos. Por último, se dedican dos capítulos a ((Node.js)), otro entorno para programar en JavaScript. Hay cinco _capítulos de proyectos_ en el libro que describen programas de ejemplo más grandes para darte una idea de programación de verdad. -La parte del lenguaje del libro comienza con cuatro capítulos que introducen la estructura básica del lenguaje JavaScript. Discuten las [estructuras de control](program_structure) (como la palabra `while` que viste en esta introducción), las [funciones](functions) (escribir tus propios bloques de construcción) y las [estructuras de datos](data). Después de estos, serás capaz de escribir programas básicos. Luego, los Capítulos [?](higher_order) y [?](object) introducen técnicas para usar funciones y objetos para escribir código más _abstracto_ y mantener la complejidad bajo control. Después de un [primer capítulo del proyecto](robot) que construye un robot de entrega rudimentario, la parte del lenguaje del libro continúa con capítulos sobre [manejo de errores y corrección de errores](error), [expresiones regulares](regexp) (una herramienta importante para trabajar con texto), [modularidad](modules) (otra defensa contra la complejidad) y [programación asíncrona](async) (tratando con eventos que toman tiempo). El [segundo capítulo del proyecto](language), donde implementamos un lenguaje de programación, concluye la primera parte del libro. +La parte del libro sobre el lenguaje comienza con cuatro capítulos que introducen la estructura básica del lenguaje JavaScript. En ellos, se discuten las [estructuras de control](program_structure) (como la palabra `while` que viste en esta introducción), las [funciones](functions) (escribir tus propios bloques de construcción) y las [estructuras de datos](data). Después de estos, serás capaz de escribir programas básicos. Luego, los Capítulos [?](higher_order) y [?](object) introducen técnicas para usar funciones y objetos para escribir código más _abstracto_ y mantener la complejidad bajo control. -La segunda parte del libro, de los capítulos [?](browser) a [?](paint), describe las herramientas a las que tiene acceso JavaScript en un navegador. Aprenderás a mostrar cosas en la pantalla (Capítulos [?](dom) y [?](canvas)), responder a la entrada del usuario ([Capítulo ?](event)) y comunicarte a través de la red ([Capítulo ?](http)). Nuevamente hay dos capítulos de proyecto en esta parte, construyendo un [juego de plataformas](game) y un [programa de pintura de píxeles](paint). +Después de un [primer capítulo de proyecto](robot) en el que se construye un robot de entrega rudimentario, la parte del libro sobre lenguaje continúa con capítulos acerca de [manejo de errores y corrección de errores](error), [expresiones regulares](regexp) (una herramienta importante para trabajar con texto), [modularidad](modules) (otra defensa contra la complejidad) y [programación asíncrona](async) (tratando con eventos que llevan tiempo). El [segundo capítulo de proyecto](language), donde implementamos un lenguaje de programación, cierra la primera parte del libro. -El [Capítulo ?](node) describe Node.js, y el [Capítulo ?](skillsharing) construye un pequeño sitio web utilizando esa herramienta. +La segunda parte del libro, de los capítulos [?](browser) a [?](paint), describe las herramientas a las que tiene acceso JavaScript en un navegador. Aprenderás a mostrar cosas en la pantalla (Capítulos [?](dom) y [?](canvas)), responder a la entrada del usuario ([Capítulo ?](event)) y comunicarte a través de la red ([Capítulo ?](http)). Nuevamente hay dos capítulos de proyecto en esta parte que consisten construir un [juego de plataformas](game) y un [programa de pintura de píxeles](paint), respectivamente. + +En el [Capítulo ?](node) se describe Node.js, y en el [Capítulo ?](skillsharing) se construye un pequeño sitio web utilizando esta herramienta. {{if comercial @@ -247,7 +249,7 @@ if}} {{index "función factorial"}} -En este libro, el texto escrito en una fuente `monoespaciada` representará elementos de programas. A veces estos son fragmentos autosuficientes, y a veces simplemente se refieren a partes de un programa cercano. Los programas (de los cuales ya has visto algunos) se escriben de la siguiente manera: +En este libro, el texto escrito en una fuente `monoespaciada` representará partes de programas. A veces serán fragmentos autosuficientes, y a veces simplemente se referirán a partes de un programa que se acabe de comentar. Los programas (de los cuales ya has visto algunos) se escriben de la siguiente manera: ``` function factorial(n) { @@ -261,7 +263,7 @@ function factorial(n) { {{index "console.log"}} -A veces, para mostrar la salida que produce un programa, la salida esperada se escribe después, con dos barras inclinadas y una flecha al frente. +A veces, para mostrar la salida que produce un programa, la salida esperada se escribe después, con dos barras diagonales y una flecha en frente. ``` console.log(factorial(8)); diff --git a/html/00_intro.html b/html/00_intro.html index 77ba1004..bee5e720 100644 --- a/html/00_intro.html +++ b/html/00_intro.html @@ -16,31 +16,31 @@

Introducción

Creemos que estamos creando el sistema para nuestros propios propósitos. Creemos que lo estamos haciendo a nuestra propia imagen... Pero la computadora en realidad no es como nosotros. Es una proyección de una parte muy pequeña de nosotros mismos: esa parte dedicada a la lógica, el orden, la regla y la claridad.

- +
Ilustración de un destornillador junto a una placa de circuitos de aproximadamente el mismo tamaño
-

Este es un libro sobre cómo instruir a computadoras. Las computadoras son tan comunes como los destornilladores hoy en día, pero son bastante más complejas, y hacer que hagan lo que quieres no siempre es fácil.

+

Este es un libro sobre cómo instruir a las computadoras. Las computadoras son tan comunes como los destornilladores hoy en día, pero son bastante más complejas, y hacer que hagan lo que quieres no siempre es fácil.

-

Si la tarea que tienes para tu computadora es común, bien entendida, como mostrarte tu correo electrónico o actuar como una calculadora, puedes abrir la aplicación correspondiente y ponerte a trabajar. Pero para tareas únicas o abiertas, a menudo no hay una aplicación adecuada.

+

Si la tarea que tienes para tu computadora es algo común y bien conocido como mostrarte tu correo electrónico funcionar a modo de calculadora, puedes abrir la aplicación correspondiente y ponerte a trabajar. Sin embargo, para tareas únicas o abiertas, a menudo no hay una aplicación adecuada.

-

Ahí es donde entra en juego la programación. Programar es el acto de construir un programa—un conjunto de instrucciones precisas que le dicen a una computadora qué hacer. Debido a que las computadoras son bestias tontas y pedantes, programar es fundamentalmente tedioso y frustrante.

+

Ahí es donde entra en juego la programación. Programar es el acto de construir un programa—un conjunto de instrucciones precisas que le dicen a una computadora qué hacer. Dado que las computadoras son criaturas estúpidas y cuadriculadas, programar resulta ser una tarea fundamentalmente tediosa y frustrante.

-

Por suerte, si puedes superar ese hecho—e incluso disfrutar del rigor de pensar en términos que las máquinas tontas pueden manejar—programar puede ser gratificante. Te permite hacer cosas en segundos que te tomarían una eternidad a mano. Es una forma de hacer que tu herramienta informática haga cosas que antes no podía hacer. Además, se convierte en un maravilloso juego de resolución de acertijos y pensamiento abstracto.

+

Por suerte, si eres capaz de afrontar esto —y también quizá si disfrutas del rigor de pensar en términos que una de estas máquinas pueda entender— programar puede ser gratificante. Te permite hacer en segundos cosas que te tomarían una eternidad a mano. Es una forma de hacer que tu herramienta informática haga cosas que antes no podía hacer. Además, se convierte en un maravilloso juego de resolución de puzles y pensamiento abstracto.

-

La mayoría de la programación se realiza con lenguajes de programación. Un lenguaje de programación es un lenguaje artificialmente construido utilizado para instruir a las computadoras. Es interesante que la forma más efectiva que hemos encontrado para comunicarnos con una computadora se base tanto en la forma en que nos comunicamos entre nosotros. Al igual que los idiomas humanos, los lenguajes informáticos permiten combinar palabras y frases de nuevas formas, lo que permite expresar conceptos cada vez más nuevos.

+

La mayoría de la programación se realiza con lenguajes de programación. Un lenguaje de programación es un lenguaje artificialmente construido utilizado para dar instrucciones a las computadoras. Es interesante que la forma más efectiva que hemos encontrado para comunicarnos con una computadora se base tanto en la forma en que nos comunicamos entre nosotros. Al igual que los idiomas humanos, los lenguajes informáticos permiten combinar palabras y frases de nuevas maneras, permitiendo expresar nuevos conceptos que no se habían expresado antes.

-

En un momento dado, las interfaces basadas en lenguaje, como los prompts de BASIC y DOS de los años 1980 y 1990, eran el principal método de interactuar con las computadoras. Para el uso informático rutinario, estas se han reemplazado en gran medida por interfaces visuales, que son más fáciles de aprender pero ofrecen menos libertad. Pero si sabes dónde buscar, los lenguajes todavía están ahí. Uno de ellos, JavaScript, está integrado en cada navegador web moderno—y por lo tanto está disponible en casi todos los dispositivos.

+

En un momento dado, las interfaces basadas en lenguaje, como los prompts de BASIC y DOS de los años 1980 y 1990, eran el principal método de interacción con las computadoras. n el uso del día a día, estas se han reemplazado en gran medida por interfaces visuales, que son más fáciles de aprender, aunque ofrecen menos libertad. No obstante, si sabes dónde mirar, los lenguajes de programación siguen ahí. Uno de ellos, JavaScript, está integrado en cada navegador web moderno —y por tanto está disponible en casi todos los dispositivos.

-

Este libro intentará que te familiarices lo suficiente con este lenguaje para hacer cosas útiles y entretenidas con él.

+

Este libro intentará que te familiarices lo suficiente con este lenguaje como para hacer cosas útiles y entretenidas con él.

Sobre la programación

-

Además de explicar JavaScript, presentaré los principios básicos de la programación. Resulta que programar es difícil. Las reglas fundamentales son simples y claras, pero los programas construidos sobre estas reglas tienden a volverse lo suficientemente complejos como para introducir sus propias reglas y complejidades. Estás construyendo tu propio laberinto, de alguna manera, y fácilmente puedes perderte en él.

+

Además de explicar JavaScript, presentaré los principios básicos de la programación. Resulta que programar es una tarea difícil. Las reglas fundamentales son simples y claras, pero los programas construidos sobre estas reglas tienden a volverse lo suficientemente complejos como para introducir sus propias reglas y complejidades. De algún modo, estás construyendo tu propio laberinto, y es fácil que te pierdas en él.

-

Habrá momentos en los que leer este libro resulte terriblemente frustrante. Si eres nuevo en la programación, habrá mucho material nuevo que asimilar. Gran parte de este material luego se combinará de maneras que requieren que hagas conexiones adicionales.

+

Habrá momentos en los que leer este libro resulte terriblemente frustrante. Si eres nuevo en la programación, habrá mucho material nuevo que asimilar. Gran parte de este material luego se combinará de maneras que requierirán que hagas nuevas conexiones mentales.

-

Depende de ti hacer el esfuerzo necesario. Cuando te cueste seguir el libro, no saques conclusiones precipitadas sobre tus propias capacidades. Estás bien, simplemente necesitas seguir adelante. Tómate un descanso, vuelve a leer algo de material y asegúrate de leer y comprender los programas de ejemplo y los ejercicios. Aprender es un trabajo duro, pero todo lo que aprendas será tuyo y facilitará aún más el aprendizaje futuro.

+

Depende de ti hacer el esfuerzo necesario. Cuando te cueste seguir el libro, no saques conclusiones precipitadas sobre tus propias capacidades. Está todo bien —simplemente necesitas seguir adelante. Tómate un descanso, vuelve a leer algo de material y asegúrate de leer y comprender los programas de ejemplo y los ejercicios. Aprender es un trabajo duro, pero todo lo que aprendas será tuyo y facilitará aún más el aprendizaje futuro.

@@ -48,19 +48,19 @@

-

Un programa es muchas cosas. Es un trozo de texto escrito por un programador, es la fuerza directiva que hace que la computadora haga lo que hace, es información en la memoria de la computadora, y al mismo tiempo controla las acciones realizadas en esta memoria. Las analogías que intentan comparar los programas con objetos familiares tienden a quedarse cortas. Una comparación vagamente adecuada es comparar un programa con una máquina: suelen estar implicadas muchas partes separadas y, para hacer que todo funcione, debemos considerar las formas en que estas partes se interconectan y contribuyen a la operación del conjunto.

+

Un programa es muchas cosas. Es un trozo de texto escrito por un programador, es la fuerza directriz que hace que la computadora haga lo que hace, es información en la memoria de la computadora, y al mismo tiempo controla las acciones realizadas en esta memoria. Las analogías que intentan comparar los programas con objetos familiares tienden a quedarse cortas. Una comparación vagamente adecuada es comparar un programa con una máquina: suelen estar formadas por muchas partes separadas y, para hacer que todo funcione, debemos considerar las formas en que estas partes se interconectan y contribuyen a la operación del conjunto.

-

Una computadora es una máquina física que actúa como anfitriona de estas máquinas inmateriales. Las computadoras mismas solo pueden hacer cosas increíblemente sencillas. La razón por la que son tan útiles es que hacen estas cosas a una velocidad increíblemente alta. Un programa puede combinar ingeniosamente un número enorme de estas acciones simples para hacer cosas muy complicadas.

+

Una computadora es una máquina física que actúa como anfitriona de estas máquinas inmateriales. Una computadora por si sola solo es capaz de hacer cosas estúpidamente sencillas. La razón por la que son tan útiles es que hacen estas cosas a una velocidad increíblemente alta. Un programa puede combinar ingeniosamente un número enorme de estas acciones simples para hacer cosas muy complicadas.

-

Un programa es una construcción del pensamiento. Es gratuito de construir, es liviano y crece fácilmente bajo nuestras manos al teclear. Pero a medida que un programa crece, también lo hace su complejidad. La habilidad de programar es la habilidad de construir programas que no te confundan a ti mismo. Los mejores programas son aquellos que logran hacer algo interesante mientras siguen siendo fáciles de entender.

+

Un programa es una construcción del pensamiento. No tiene coste ni peso, y crece fácilmente según tecleamos. Pero a medida que un programa crece, también lo hace su complejidad. La habilidad de programar es la habilidad de construir programas que no te confundan a ti mismo. Los mejores programas son aquellos que logran hacer algo interesante mientras siguen siendo fáciles de entender.

-

Algunos programadores creen que esta complejidad se gestiona mejor utilizando solo un conjunto pequeño de técnicas bien comprendidas en sus programas. Han compuesto reglas estrictas (“mejores prácticas”) que prescriben la forma que deberían tener los programas y se mantienen cuidadosamente dentro de su pequeña zona segura.

+

Algunos programadores creen que esta complejidad se gestiona mejor utilizando solo un puñado de técnicas conocidas en sus programas. Han creado reglas estrictas (“mejores prácticas”) que prescriben la forma que deberían tener los programas y se mantienen diligentemente dentro de su pequeño espacio seguro.

-

Esto no solo es aburrido, es inefectivo. A menudo, nuevos problemas requieren soluciones nuevas. El campo de la programación es joven y aún se está desarrollando rápidamente, y es lo suficientemente variado como para tener espacio para enfoques radicalmente diferentes. Hay muchos errores terribles que cometer en el diseño de programas, y deberías ir y cometerlos al menos una vez para entenderlos. Una noción de cómo es un buen programa se desarrolla con la práctica, no se aprende de una lista de reglas.

+

Esto no solo es aburrido sino que es ineficaz. A menudo, nuevos problemas requieren soluciones nuevas. El campo de la programación es joven y sin embargo se está desarrollando rápidamente, con variedad suficiente como para adoptar enfoques radicalmente distintos. Hay muchos errores terribles que cometer en el diseño de un programa, y deberías cometerlos al menos una vez para entenderlos. Una noción de cómo es un buen programa se desarrolla con la práctica, no se aprende de una lista de reglas.

Por qué importa el lenguaje

-

Al principio, en los inicios de la informática, no existían los lenguajes de programación. Los programas lucían algo así:

+

Al principio, en los inicios de la informática, no existían los lenguajes de programación. Los programas tenían una pinta como la siguiente:

00110001 00000000 00000000
 00110001 00000001 00000001
@@ -72,9 +72,9 @@ 

-

Este es un programa para sumar los números del 1 al 10 y mostrar el resultado: 1 + 2 + ... + 10 = 55. Podría ejecutarse en una máquina hipotética simple. Para programar los primeros ordenadores, era necesario configurar grandes conjuntos de interruptores en la posición correcta o perforar agujeros en tiras de cartón y alimentarlos al ordenador. Puedes imaginar lo tedioso y propenso a errores que era este procedimiento. Incluso escribir programas simples requería mucha astucia y disciplina. Los complejos eran casi inconcebibles.

+

Este es un programa para sumar los números del 1 al 10 y mostrar el resultado: 1 + 2 + ... + 10 = 55. Podría ejecutarse en una máquina hipotética simple. Para programar los primeros ordenadores, era necesario configurar grandes conjuntos de interruptores en la posición correcta o perforar agujeros en tiras de cartón y dárselos a la computadora. Ya te puedes imaginar lo tedioso y propenso a errores que era este procedimiento. Incluso escribir programas simples requería de mucha astucia y disciplina. Los complejos eran casi inconcebibles.

-

Por supuesto, introducir manualmente estos patrones arcanos de bits (los unos y ceros) hacía que el programador se sintiera como un mago poderoso. Y eso debe valer algo en términos de satisfacción laboral.

+

Por supuesto, introducir manualmente estos misteriosos patrones de bits (los unos y ceros) hacía que el programador se sintiera como un mago poderoso. Y eso debe valer algo en términos de satisfacción laboral.

Cada línea del programa anterior contiene una única instrucción. Podría escribirse en español de la siguiente manera:

@@ -118,7 +118,7 @@

-

Aunque eso ya es más legible que la sopa de bits, sigue siendo bastante confusa. Usar nombres en lugar de números para las instrucciones y las ubicaciones de memoria ayuda:

+

Aunque eso ya es más legible que la sopa de bits anterior, sigue siendo bastante confuso. Usar nombres en lugar de números para las instrucciones y las ubicaciones de memoria ayuda:

 Establecer “total” en 0.
  Establecer “count” en 1.
@@ -132,7 +132,7 @@ 

-

¿Puedes ver cómo funciona el programa en este punto? Las dos primeras líneas asignan los valores iniciales a dos ubicaciones de memoria: total se utilizará para construir el resultado de la computación, y count llevará la cuenta del número que estamos observando en ese momento. Las líneas que utilizan compare probablemente sean las más confusas. El programa quiere ver si count es igual a 11 para decidir si puede dejar de ejecutarse. Debido a que nuestra máquina hipotética es bastante primitiva, solo puede comprobar si un número es cero y tomar una decisión en función de ese valor. Por lo tanto, utiliza la ubicación de memoria etiquetada como compare para calcular el valor de count - 11 y tomar una decisión basada en ese valor. Las siguientes dos líneas suman el valor de count al resultado e incrementan count en 1 cada vez que el programa decide que count aún no es 11. Aquí está el mismo programa en JavaScript:

+

¿Puedes ver cómo funciona el programa en este punto? Las dos primeras líneas asignan los valores iniciales a dos ubicaciones de memoria: total se utilizará para construir el resultado de la suma, y count llevará la cuenta del número que estamos observando en ese momento. Las líneas que utilizan compare probablemente sean las más confusas. El programa quiere ver si count es igual a 11 para decidir si puede parar de ejecutarse. Debido a que nuestra máquina hipotética es bastante primitiva, solo puede comprobar si un número es cero y tomar una decisión en función de ese valor. Por lo tanto, utiliza la ubicación de memoria etiquetada como compare para calcular el valor de count - 11 y tomar una decisión basada en el resultado. Las siguientes dos líneas suman el valor de count al resultado e incrementan count en 1 cada vez que el programa decide que count aún no vale 11. Aquí está el mismo programa en JavaScript:

let total = 0, count = 1;
 while (count <= 10) {
@@ -142,60 +142,62 @@ 

// → 55

-

Esta versión nos proporciona algunas mejoras. Lo más importante es que ya no es necesario especificar la forma en que queremos que el programa salte hacia adelante y hacia atrás; la construcción while se encarga de eso. Continúa ejecutando el bloque (entre llaves) debajo de él siempre y cuando se cumpla la condición que se le ha dado. Esa condición es count <= 10, lo que significa “el recuento es menor o igual a 10”. Ya no tenemos que crear un valor temporal y compararlo con cero, lo cual era simplemente un detalle no interesante. Parte del poder de los lenguajes de programación es que pueden encargarse de los detalles no interesantes por nosotros.

+

Esta versión nos proporciona algunas mejoras más. Lo más importante es que ya no es necesario especificar la forma en que queremos que el programa salte hacia adelante y hacia atrás; la construcción while se encarga de eso. Continúa ejecutando el bloque (entre llaves) debajo de él siempre y cuando se cumpla la condición que se le ha dado. Esa condición es count <= 10, lo que significa “el recuento es menor o igual a 10”. Ya no tenemos que crear un valor temporal y compararlo con cero, lo cual era simplemente un detalle carente de interés. Parte del poder de los lenguajes de programación es que pueden encargarse de los detalles que no nos interesan.

-

Al final del programa, después de que la construcción while haya terminado, se utiliza la operación console.log para escribir el resultado.

+

Al final del programa, después de que la construcción while haya terminado, se utiliza la operación console.log para mostrar el resultado.

-

Finalmente, así es como podría verse el programa si tuviéramos a nuestra disposición las operaciones convenientes rango y suma, que respectivamente crean una colección de números dentro de un rango y calculan la suma de una colección de números:

+

Finalmente, así es como podría verse el programa si tuviéramos a nuestra disposición las útiles operaciones rango y suma, que crean una colección de números dentro de un rango y calculan la suma de una colección de números, respectivamente:

console.log(suma(rango(1, 10)));
 // → 55
-

La moraleja de esta historia es que el mismo programa puede expresarse de formas largas y cortas, ilegibles y legibles. La primera versión del programa era extremadamente críptica, mientras que esta última es casi en inglés: registra (log) la suma del rango de números del 1 al 10. (Veremos en capítulos posteriores cómo definir operaciones como suma y rango.)

+

La moraleja de esta historia es que un mismo programa puede expresarse de formas largas y cortas, ilegibles y legibles. La primera versión del programa era extremadamente críptica, mientras que esta última es casi hablar en inglés: registra (log) la suma del rango de números del 1 al 10 (veremos en capítulos posteriores cómo definir operaciones como suma y rango).

-

Un buen lenguaje de programación ayuda al programador al permitirle hablar sobre las acciones que la computadora debe realizar a un nivel más alto. Ayuda a omitir detalles, proporciona bloques de construcción convenientes (como while y console.log), te permite definir tus propios bloques de construcción (como suma y rango), y hace que esos bloques sean fáciles de componer.

+

Un buen lenguaje de programación ayuda al programador, al permitirle hablar sobre las acciones que la computadora debe realizar a un más alto nivel. Ayuda a omitir detalles, proporciona bloques de construcción convenientes (como while y console.log), te permite definir tus propios bloques de construcción (como suma y rango), y hace que esos bloques sean fáciles de componer.

¿Qué es JavaScript?

-

JavaScript fue introducido en 1995 como una forma de agregar programas a páginas web en el navegador Netscape Navigator. Desde entonces, el lenguaje ha sido adoptado por todos los demás navegadores web gráficos principales. Ha hecho posibles aplicaciones web modernas, es decir, aplicaciones con las que puedes interactuar directamente sin tener que recargar la página para cada acción. JavaScript también se utiliza en sitios web más tradicionales para proporcionar distintas formas de interactividad e ingenio.

+

JavaScript fue introducido en 1995 como una forma de agregar programas a páginas web en el navegador Netscape Navigator. Desde entonces, el lenguaje ha sido adoptado por todos los demás principales navegadores web gráficos. Ha hecho posibles aplicaciones web modernas, es decir, aplicaciones con las que puedes interactuar directamente sin tener que recargar la página para cada acción. JavaScript también se utiliza en sitios web más tradicionales para proporcionar distintas formas de interactividad e ingenio.

-

Es importante tener en cuenta que JavaScript casi no tiene nada que ver con el lenguaje de programación llamado Java. El nombre similar fue inspirado por consideraciones de marketing en lugar de un buen juicio. Cuando se estaba introduciendo JavaScript, el lenguaje Java se estaba comercializando mucho y ganaba popularidad. Alguien pensó que era una buena idea intentar aprovechar este éxito. Ahora estamos atrapados con el nombre.

+

Es importante mencionar que JavaScript no tiene casi nada que ver con el lenguaje de programación llamado Java. La elección de un nombre tan parecido se debe más a consideraciones de marketing que a un buen criterio. Cuando se estaba introduciendo JavaScript, el lenguaje Java se estaba comercializando mucho y ganaba popularidad. Alguien pensó que era una buena idea intentar aprovechar este éxito y ahora tenemos que quedarnos con el nombre.

-

Después de su adopción fuera de Netscape, se escribió un documento estándar para describir la forma en que debería funcionar el lenguaje JavaScript para que las diversas piezas de software que afirmaban soportar JavaScript pudieran asegurarse de que realmente proporcionaban el mismo lenguaje. Esto se llama el estándar ECMAScript, según la organización Ecma International que llevó a cabo la estandarización. En la práctica, los términos ECMAScript y JavaScript se pueden usar indistintamente, son dos nombres para el mismo lenguaje.

+

Después de su adopción fuera de Netscape, se redactó un documento estándar para describir cómo debería funcionar el lenguaje JavaScript, de manera que los diferentes programas que decían soportar JavaScript pudieran asegurarse de que realmente proporcionaban el mismo lenguaje. A esto se le llama el estándar ECMAScript, en honor a la organización Ecma International que llevó a cabo la estandarización. En la práctica, los términos ECMAScript y JavaScript se pueden usar de manera intercambiable; son dos nombres para el mismo lenguaje.

-

Hay quienes dirán cosas terribles sobre JavaScript. Muchas de esas cosas son ciertas. Cuando me pidieron que escribiera algo en JavaScript por primera vez, rápidamente llegué a detestarlo. Aceptaba casi cualquier cosa que escribía pero lo interpretaba de una manera completamente diferente a lo que yo quería decir. Esto tenía mucho que ver con el hecho de que no tenía ni idea de lo que estaba haciendo, por supuesto, pero hay un problema real aquí: JavaScript es ridículamente liberal en lo que permite. La idea detrás de este diseño era que haría la programación en JavaScript más fácil para principiantes. En realidad, esto hace que encontrar problemas en tus programas sea más difícil porque el sistema no te los señalará.

+

Hay quienes dirán cosas terribles sobre JavaScript. Muchas de ellas son ciertas. Cuando me pidieron que escribiera algo en JavaScript por primera vez, empecé a detestarlo rápidamente. Aceptaba casi cualquier cosa que escribía pero lo interpretaba de una manera completamente diferente a lo que yo quería decir. Esto tenía mucho que ver con el hecho de que yo no tenía ni idea de lo que estaba haciendo, por supuesto, pero hay un problema real aquí: JavaScript es ridículamente flexible en lo que permite. La idea detrás de este diseño era que haría la programación en JavaScript más fácil para principiantes. En realidad, esto hace que encontrar problemas en tus programas sea más difícil porque el sistema no te los va a señalar.

Esta flexibilidad también tiene sus ventajas. Deja espacio para técnicas imposibles en lenguajes más rígidos y permite un estilo de programación agradable e informal. Después de aprender el lenguaje adecuadamente y trabajar con él durante un tiempo, ha llegado a realmente gustarme JavaScript.

-

Ha habido varias versiones de JavaScript. La versión ECMAScript 3 fue la versión ampliamente soportada durante el ascenso al dominio de JavaScript, aproximadamente entre 2000 y 2010. Durante este tiempo, se estaba trabajando en una versión ambiciosa 4, la cual planeaba una serie de mejoras y extensiones radicales al lenguaje. Cambiar un lenguaje vivo y ampliamente utilizado de esa manera resultó ser políticamente difícil, y el trabajo en la versión 4 fue abandonado en 2008. Una versión 5, mucho menos ambiciosa, que solo realizaba algunas mejoras no controversiales, salió en 2009. En 2015, salió la versión 6, una actualización importante que incluía algunas de las ideas previstas para la versión 4. Desde entonces, hemos tenido nuevas actualizaciones pequeñas cada año.

+

Ha habido varias versiones de JavaScript. La versión ECMAScript 3 fue la versión más respaldada durante el ascenso al dominio de JavaScript, aproximadamente entre 2000 y 2010. Durante este tiempo, se estaba trabajando en una ambiciosa versión 4, la cual planeaba una serie de mejoras y extensiones radicales del lenguaje. Cambiar un lenguaje vivo y ampliamente utilizado de esa manera resultó ser políticamente difícil, y el trabajo en la versión 4 se abandonó en 2008. Una mucho menos ambiciosa versión 5, que solo realizaba algunas mejoras poco controvertidas, se lanzó en 2009. En 2015, salió la versión 6, una actualización importante que incluía algunas de las ideas previstas para la versión 4. Desde entonces, hemos tenido nuevas pequeñas actualizaciones cada año.

-

El hecho de que JavaScript esté evolucionando significa que los navegadores tienen que mantenerse constantemente al día. Si estás usando un navegador más antiguo, es posible que no admita todas las funciones. Los diseñadores del lenguaje se aseguran de no realizar cambios que puedan romper programas existentes, por lo que los nuevos navegadores aún pueden ejecutar programas antiguos. En este libro, estoy utilizando la versión 2023 de JavaScript.

+

El hecho de que JavaScript esté evolucionando significa que los navegadores tienen que mantenerse constantemente al día. Si estás usando un navegador más antiguo, es posible que no admita todas las funciones. Los diseñadores del lenguaje se aseguran de no realizar cambios que puedan romper programas ya existentes, por lo que los nuevos navegadores aún pueden ejecutar programas antiguos. En este libro, estoy utilizando la versión 2023 de JavaScript.

Los navegadores web no son las únicas plataformas en las que se utiliza JavaScript. Algunas bases de datos, como MongoDB y CouchDB, utilizan JavaScript como su lenguaje de secuencias de comandos y consulta. Varias plataformas para programación de escritorio y servidores, especialmente el proyecto Node.js (el tema del Capítulo 20), proporcionan un entorno para programar en JavaScript fuera del navegador.

-

Código y qué hacer con él

+

Código, y qué hacer con él

-

El código es el texto que constituye los programas. La mayoría de los capítulos en este libro contienen bastante código. Creo que leer código y escribir código son partes indispensables de aprender a programar. Intenta no solo echar un vistazo a los ejemplos, léelos atentamente y entiéndelos. Esto puede ser lento y confuso al principio, pero te prometo que pronto le tomarás la mano. Lo mismo ocurre con los ejercicios. No des por sentado que los entiendes hasta que hayas escrito realmente una solución que funcione.

+

El código es el texto que constituye los programas. La mayoría de los capítulos en este libro contienen bastante código. Creo que leer código y escribir código son partes indispensables de aprender a programar. Intenta no solo mirar por encima los ejemplos, léelos atentamente y entiéndelos. Esto puede ser lento y confuso al principio, pero te prometo que pronto le pillarás el truco. Lo mismo ocurre con los ejercicios. No des por sentado que los entiendes hasta que hayas escrito una solución que realmente funcione.

Te recomiendo que pruebes tus soluciones a los ejercicios en un intérprete de JavaScript real. De esta manera, obtendrás comentarios inmediatos sobre si lo que estás haciendo funciona, y, espero, te tentarán a experimentar y a ir más allá de los ejercicios.

Cuando leas este libro en tu navegador, puedes editar (y ejecutar) todos los programas de ejemplo haciendo clic en ellos.

-

Ejecutar los programas definidos en este libro fuera del sitio web del libro requiere cierto cuidado. Muchos ejemplos son independientes y deberían funcionar en cualquier entorno de JavaScript. Pero el código en los capítulos posteriores a menudo está escrito para un entorno específico (navegador o Node.js) y solo puede ejecutarse allí. Además, muchos capítulos definen programas más grandes, y las piezas de código que aparecen en ellos dependen unas de otras o de archivos externos. El sandbox en el sitio web proporciona enlaces a archivos ZIP que contienen todos los scripts y archivos de datos necesarios para ejecutar el código de un capítulo dado.

+

Ejecutar los programas definidos en este libro fuera del sitio web del libro requiere cierto cuidado. Muchos ejemplos son independientes y deberían funcionar en cualquier entorno de JavaScript. Pero el código en los capítulos posteriores a menudo está escrito para un entorno específico (navegador o Node.js) y solo puede ejecutarse allí. Además, muchos capítulos definen programas más grandes, y los trozos de código que aparecen en ellos dependen unos de otros, o de archivos externos. El sandbox en el sitio web proporciona enlaces a archivos ZIP que contienen todos los scripts y archivos de datos necesarios para ejecutar el código de un capítulo dado.

Visión general de este libro

-

Este libro consta aproximadamente de tres partes. Los primeros 12 capítulos tratan sobre el lenguaje JavaScript. Los siguientes siete capítulos son acerca de los navegadores web y la forma en que se utiliza JavaScript para programarlos. Por último, dos capítulos están dedicados a Node.js, otro entorno para programar en JavaScript. Hay cinco capítulos de proyectos en el libro que describen programas de ejemplo más grandes para darte una idea de la programación real.

+

Este libro consta aproximadamente de tres partes. Los primeros 12 capítulos tratan sobre el lenguaje JavaScript. Los siguientes siete capítulos son acerca de los navegadores web y la forma en que se utiliza JavaScript para programarlos. Por último, se dedican dos capítulos a Node.js, otro entorno para programar en JavaScript. Hay cinco capítulos de proyectos en el libro que describen programas de ejemplo más grandes para darte una idea de programación de verdad.

-

La parte del lenguaje del libro comienza con cuatro capítulos que introducen la estructura básica del lenguaje JavaScript. Discuten las estructuras de control (como la palabra while que viste en esta introducción), las funciones (escribir tus propios bloques de construcción) y las estructuras de datos. Después de estos, serás capaz de escribir programas básicos. Luego, los Capítulos 5 y 6 introducen técnicas para usar funciones y objetos para escribir código más abstracto y mantener la complejidad bajo control. Después de un primer capítulo del proyecto que construye un robot de entrega rudimentario, la parte del lenguaje del libro continúa con capítulos sobre manejo de errores y corrección de errores, expresiones regulares (una herramienta importante para trabajar con texto), modularidad (otra defensa contra la complejidad) y programación asíncrona (tratando con eventos que toman tiempo). El segundo capítulo del proyecto, donde implementamos un lenguaje de programación, concluye la primera parte del libro.

+

La parte del libro sobre el lenguaje comienza con cuatro capítulos que introducen la estructura básica del lenguaje JavaScript. En ellos, se discuten las estructuras de control (como la palabra while que viste en esta introducción), las funciones (escribir tus propios bloques de construcción) y las estructuras de datos. Después de estos, serás capaz de escribir programas básicos. Luego, los Capítulos 5 y 6 introducen técnicas para usar funciones y objetos para escribir código más abstracto y mantener la complejidad bajo control.

-

La segunda parte del libro, de los capítulos 13 a 19, describe las herramientas a las que tiene acceso JavaScript en un navegador. Aprenderás a mostrar cosas en la pantalla (Capítulos 14 y 17), responder a la entrada del usuario (Capítulo 15) y comunicarte a través de la red (Capítulo 18). Nuevamente hay dos capítulos de proyecto en esta parte, construyendo un juego de plataformas y un programa de pintura de píxeles.

+

Después de un primer capítulo de proyecto en el que se construye un robot de entrega rudimentario, la parte del libro sobre lenguaje continúa con capítulos acerca de manejo de errores y corrección de errores, expresiones regulares (una herramienta importante para trabajar con texto), modularidad (otra defensa contra la complejidad) y programación asíncrona (tratando con eventos que llevan tiempo). El segundo capítulo de proyecto, donde implementamos un lenguaje de programación, cierra la primera parte del libro.

-

El Capítulo 20 describe Node.js, y el Capítulo 21 construye un pequeño sitio web utilizando esa herramienta.

+

La segunda parte del libro, de los capítulos 13 a 19, describe las herramientas a las que tiene acceso JavaScript en un navegador. Aprenderás a mostrar cosas en la pantalla (Capítulos 14 y 17), responder a la entrada del usuario (Capítulo 15) y comunicarte a través de la red (Capítulo 18). Nuevamente hay dos capítulos de proyecto en esta parte que consisten construir un juego de plataformas y un programa de pintura de píxeles, respectivamente.

+ +

En el Capítulo 20 se describe Node.js, y en el Capítulo 21 se construye un pequeño sitio web utilizando esta herramienta.

Convenciones tipográficas

-

En este libro, el texto escrito en una fuente monoespaciada representará elementos de programas. A veces estos son fragmentos autosuficientes, y a veces simplemente se refieren a partes de un programa cercano. Los programas (de los cuales ya has visto algunos) se escriben de la siguiente manera:

+

En este libro, el texto escrito en una fuente monoespaciada representará partes de programas. A veces serán fragmentos autosuficientes, y a veces simplemente se referirán a partes de un programa que se acabe de comentar. Los programas (de los cuales ya has visto algunos) se escriben de la siguiente manera:

function factorial(n) {
   if (n == 0) {
@@ -205,7 +207,7 @@ 

-

A veces, para mostrar la salida que produce un programa, la salida esperada se escribe después, con dos barras inclinadas y una flecha al frente.

+

A veces, para mostrar la salida que produce un programa, la salida esperada se escribe después, con dos barras diagonales y una flecha en frente.

console.log(factorial(8));
 // → 40320
diff --git a/html/01_values.html b/html/01_values.html index 56c1e7db..a7b182d1 100644 --- a/html/01_values.html +++ b/html/01_values.html @@ -20,7 +20,7 @@

Valores, Tipos y Operadores

Una foto de un mar de bits
-

En el mundo de la computadora, solo existe data. Puedes leer data, modificar data, crear nueva data, pero aquello que no es data no puede ser mencionado. Toda esta data se almacena como largas secuencias de bits y, por lo tanto, es fundamentalmente similar.

+

En el mundo de la computadora, solo existen datos. Puedes leer datos, modificar datos, crear nuevos datos, pero aquello que no son datos no puede ser mencionado. Todos estos datos se almacenan como largas secuencias de bits y, por lo tanto, es fundamentalmente similar.

Los bits son cualquier tipo de cosas de dos valores, generalmente descritos como ceros y unos. Dentro de la computadora, toman formas como una carga eléctrica alta o baja, una señal fuerte o débil, o un punto brillante u opaco en la superficie de un CD. Cualquier pieza de información discreta puede reducirse a una secuencia de ceros y unos y por lo tanto representarse en bits.

@@ -51,7 +51,7 @@

La memoria de la computadora solía ser mucho más pequeña, y la gente solía utilizar grupos de 8 o 16 bits para representar sus números. Era fácil tener un desbordamiento accidental con números tan pequeños, terminando con un número que no encajaba en la cantidad dada de bits. Hoy en día, incluso las computadoras que caben en tu bolsillo tienen mucha memoria, por lo que puedes utilizar trozos de 64 bits y solo necesitas preocuparte por el desbordamiento cuando lidias con números realmente astronómicos.

-

Sin embargo, no todos los números enteros menores que 18 mil trillones encajan en un número de JavaScript. Esos bits también almacenan números negativos, por lo que un bit indica el signo del número. Un problema más grande es representar números no enteros. Para hacer esto, algunos de los bits se utilizan para almacenar la posición del punto decimal. El número entero máximo real que se puede almacenar está más en el rango de 9 cuatrillones (15 ceros), que sigue siendo increíblemente grande.

+

Sin embargo, no todos los números enteros menores que 18 mil trillones encajan en un número de JavaScript. Esos bits también almacenan números negativos, por lo que un bit indica el signo del número. Un problema más grande es representar números no enteros. Para hacer esto, algunos de los bits se utilizan para almacenar la posición del punto decimal. El número entero máximo real que se puede almacenar está más en el rango de 9 mil billones (15 ceros), que sigue siendo increíblemente grande.

Los números fraccionarios se escriben usando un punto:

@@ -63,7 +63,7 @@

Eso es 2.998 × 108 = 299,800,000.

-

Los cálculos con números enteros (también llamados enteros) que son más pequeños que los mencionados 9 cuatrillones siempre serán precisos. Desafortunadamente, los cálculos con números fraccionarios generalmente no lo son. Así como π (pi) no puede expresarse con precisión mediante un número finito de dígitos decimales, muchos números pierden algo de precisión cuando solo están disponibles 64 bits para almacenarlos. Es una lástima, pero solo causa problemas prácticos en situaciones específicas. Lo importante es ser consciente de esto y tratar los números digitales fraccionarios como aproximaciones, no como valores precisos.

+

Se garantiza que los cálculos con números enteros (también llamados enteros) menores que los 9 mil billones antes mencionados siempre serán precisos. Desafortunadamente, los cálculos con números fraccionarios generalmente no lo son. Así como π (pi) no puede expresarse con precisión mediante un número finito de dígitos decimales, muchos números pierden algo de precisión cuando solo están disponibles 64 bits para almacenarlos. Es una lástima, pero solo causa problemas prácticos en situaciones específicas. Lo importante es ser consciente de esto y tratar los números digitales fraccionarios como aproximaciones, no como valores precisos.

Aritmética

@@ -73,7 +73,7 @@

Los símbolos + y * se llaman operadores. El primero representa la suma y el segundo representa la multiplicación. Colocar un operador entre dos valores aplicará ese operador a esos valores y producirá un nuevo valor.

-

¿Significa este ejemplo “Sumar 4 y 100, y luego multiplicar el resultado por 11”, o se realiza primero la multiplicación antes de la suma? Como habrás adivinado, la multiplicación se realiza primero. Como en matemáticas, puedes cambiar esto envolviendo la suma entre paréntesis:

+

¿Significa este ejemplo “Sumar 4 y 100, y luego multiplicar el resultado por 11”, o se realiza primero la multiplicación antes de la suma? Como habrás adivinado, la multiplicación se realiza primero. Pero igual que en matemáticas, puedes cambiar esto envolviendo la suma entre paréntesis:

(100 + 4) * 11
@@ -89,7 +89,7 @@

Hay tres valores especiales en JavaScript que se consideran números pero no se comportan como números normales. Los dos primeros son Infinity y -Infinity, que representan el infinito positivo y negativo. Infinity - 1 sigue siendo Infinity, y así sucesivamente. Sin embargo, no confíes demasiado en los cálculos basados en infinito. No es matemáticamente sólido y rápidamente te llevará al siguiente número especial: NaN.

-

NaN significa “no es un número”, aunque es un valor del tipo numérico. Obtendrás este resultado cuando, por ejemplo, intentes calcular 0 / 0 (cero dividido por cero), Infinity - Infinity, u cualquier otra operación numérica que no produzca un resultado significativo.

+

NaN significa “no es un número”, aunque es un valor del tipo numérico. Obtendrás este resultado cuando, por ejemplo, intentes calcular 0 / 0 (cero dividido por cero), Infinity - Infinity, o cualquier otra operación numérica que no produzca un resultado significativo.

Cadenas

@@ -118,9 +118,9 @@

Las cadenas también deben ser modeladas como una serie de bits para poder existir dentro de la computadora. La forma en que JavaScript lo hace se basa en el estándar Unicode. Este estándar asigna un número a prácticamente cada carácter que puedas necesitar, incluidos los caracteres griegos, árabes, japoneses, armenios, y así sucesivamente. Si tenemos un número para cada carácter, una cadena puede ser descrita por una secuencia de números. Y eso es lo que hace JavaScript.

-

Sin embargo, hay una complicación: la representación de JavaScript utiliza 16 bits por elemento de cadena, lo que puede describir hasta 216 caracteres diferentes. Sin embargo, Unicode define más caracteres que eso —aproximadamente el doble, en este momento. Por lo tanto, algunos caracteres, como muchos emoji, ocupan dos “posiciones de caracteres” en las cadenas de JavaScript. Volveremos a esto en el Capítulo 5.

+

Pero hay una complicación: la representación de JavaScript utiliza 16 bits por elemento de cadena, lo que puede describir hasta 216 caracteres diferentes. Sin embargo, Unicode define más caracteres que eso (aproximadamente el doble), en este momento. Por lo tanto, algunos caracteres, como muchos emoji, ocupan dos “posiciones de caracteres” en las cadenas de JavaScript. Volveremos a esto en el Capítulo 5.

-

Las cadenas no se pueden dividir, multiplicar o restar. El operador + se puede usar en ellas, no para sumar, sino para concatenar —unir dos cadenas. La siguiente línea producirá la cadena "concatenar":

+

Las cadenas no se pueden dividir, multiplicar o restar. El operador + se puede usar en ellas, no para sumar, sino para concatenar (unir dos cadenas). La siguiente línea producirá la cadena "concatenar":

"con" + "cat" + "e" + "nar"
diff --git a/html/ejs.js b/html/ejs.js index 15aea209..9e9f6a72 100644 --- a/html/ejs.js +++ b/html/ejs.js @@ -1 +1 @@ -!function(t){"function"==typeof define&&define.amd?define(t):t()}((function(){"use strict";class t{lineAt(t){if(t<0||t>this.length)throw new RangeError(`Invalid position ${t} in document of length ${this.length}`);return this.lineInner(t,!1,1,0)}line(t){if(t<1||t>this.lines)throw new RangeError(`Invalid line number ${t} in ${this.lines}-line document`);return this.lineInner(t,!0,1,0)}replace(t,e,n){[t,e]=h(this,t,e);let r=[];return this.decompose(0,t,r,2),n.length&&n.decompose(0,n.length,r,3),this.decompose(e,this.length,r,1),i.from(r,this.length-(e-t)+n.length)}append(t){return this.replace(this.length,this.length,t)}slice(t,e=this.length){[t,e]=h(this,t,e);let n=[];return this.decompose(t,e,n,0),i.from(n,e-t)}eq(t){if(t==this)return!0;if(t.length!=this.length||t.lines!=this.lines)return!1;let e=this.scanIdentical(t,1),i=this.length-this.scanIdentical(t,-1),n=new s(this),r=new s(t);for(let t=e,s=e;;){if(n.next(t),r.next(t),t=0,n.lineBreak!=r.lineBreak||n.done!=r.done||n.value!=r.value)return!1;if(s+=n.value.length,n.done||s>=i)return!0}}iter(t=1){return new s(this,t)}iterRange(t,e=this.length){return new o(this,t,e)}iterLines(t,e){let i;if(null==t)i=this.iter();else{null==e&&(e=this.lines+1);let n=this.line(t).from;i=this.iterRange(n,Math.max(n,e==this.lines+1?this.length:e<=1?0:this.line(e-1).to))}return new a(i)}toString(){return this.sliceString(0)}toJSON(){let t=[];return this.flatten(t),t}constructor(){}static of(n){if(0==n.length)throw new RangeError("A document must have at least one line");return 1!=n.length||n[0]?n.length<=32?new e(n):i.from(e.split(n,[])):t.empty}}class e extends t{constructor(t,e=function(t){let e=-1;for(let i of t)e+=i.length+1;return e}(t)){super(),this.text=t,this.length=e}get lines(){return this.text.length}get children(){return null}lineInner(t,e,i,n){for(let r=0;;r++){let s=this.text[r],o=n+s.length;if((e?i:o)>=t)return new l(n,o,i,s);n=o+1,i++}}decompose(t,i,s,o){let a=t<=0&&i>=this.length?this:new e(r(this.text,t,i),Math.min(i,this.length)-Math.max(0,t));if(1&o){let t=s.pop(),i=n(a.text,t.text.slice(),0,a.length);if(i.length<=32)s.push(new e(i,t.length+a.length));else{let t=i.length>>1;s.push(new e(i.slice(0,t)),new e(i.slice(t)))}}else s.push(a)}replace(t,s,o){if(!(o instanceof e))return super.replace(t,s,o);[t,s]=h(this,t,s);let a=n(this.text,n(o.text,r(this.text,0,t)),s),l=this.length+o.length-(s-t);return a.length<=32?new e(a,l):i.from(e.split(a,[]),l)}sliceString(t,e=this.length,i="\n"){[t,e]=h(this,t,e);let n="";for(let r=0,s=0;r<=e&&st&&s&&(n+=i),tr&&(n+=o.slice(Math.max(0,t-r),e-r)),r=a+1}return n}flatten(t){for(let e of this.text)t.push(e)}scanIdentical(){return 0}static split(t,i){let n=[],r=-1;for(let s of t)n.push(s),r+=s.length+1,32==n.length&&(i.push(new e(n,r)),n=[],r=-1);return r>-1&&i.push(new e(n,r)),i}}class i extends t{constructor(t,e){super(),this.children=t,this.length=e,this.lines=0;for(let e of t)this.lines+=e.lines}lineInner(t,e,i,n){for(let r=0;;r++){let s=this.children[r],o=n+s.length,a=i+s.lines-1;if((e?a:o)>=t)return s.lineInner(t,e,i,n);n=o+1,i=a+1}}decompose(t,e,i,n){for(let r=0,s=0;s<=e&&r=s){let r=n&((s<=t?1:0)|(a>=e?2:0));s>=t&&a<=e&&!r?i.push(o):o.decompose(t-s,e-s,i,r)}s=a+1}}replace(t,e,n){if([t,e]=h(this,t,e),n.lines=s&&e<=a){let l=o.replace(t-s,e-s,n),h=this.lines-o.lines+l.lines;if(l.lines>4&&l.lines>h>>6){let s=this.children.slice();return s[r]=l,new i(s,this.length-(e-t)+n.length)}return super.replace(s,a,l)}s=a+1}return super.replace(t,e,n)}sliceString(t,e=this.length,i="\n"){[t,e]=h(this,t,e);let n="";for(let r=0,s=0;rt&&r&&(n+=i),ts&&(n+=o.sliceString(t-s,e-s,i)),s=a+1}return n}flatten(t){for(let e of this.children)e.flatten(t)}scanIdentical(t,e){if(!(t instanceof i))return 0;let n=0,[r,s,o,a]=e>0?[0,0,this.children.length,t.children.length]:[this.children.length-1,t.children.length-1,-1,-1];for(;;r+=e,s+=e){if(r==o||s==a)return n;let i=this.children[r],l=t.children[s];if(i!=l)return n+i.scanIdentical(l,e);n+=i.length+1}}static from(t,n=t.reduce(((t,e)=>t+e.length+1),-1)){let r=0;for(let e of t)r+=e.lines;if(r<32){let i=[];for(let e of t)e.flatten(i);return new e(i,n)}let s=Math.max(32,r>>5),o=s<<1,a=s>>1,l=[],h=0,c=-1,u=[];function p(t){let n;if(t.lines>o&&t instanceof i)for(let e of t.children)p(e);else t.lines>a&&(h>a||!h)?(d(),l.push(t)):t instanceof e&&h&&(n=u[u.length-1])instanceof e&&t.lines+n.lines<=32?(h+=t.lines,c+=t.length+1,u[u.length-1]=new e(n.text.concat(t.text),n.length+1+t.length)):(h+t.lines>s&&d(),h+=t.lines,c+=t.length+1,u.push(t))}function d(){0!=h&&(l.push(1==u.length?u[0]:i.from(u,c)),c=-1,h=u.length=0)}for(let e of t)p(e);return d(),1==l.length?l[0]:new i(l,n)}}function n(t,e,i=0,n=1e9){for(let r=0,s=0,o=!0;s=i&&(l>n&&(a=a.slice(0,n-r)),r0?1:(t instanceof e?t.text.length:t.children.length)<<1]}nextInner(t,i){for(this.done=this.lineBreak=!1;;){let n=this.nodes.length-1,r=this.nodes[n],s=this.offsets[n],o=s>>1,a=r instanceof e?r.text.length:r.children.length;if(o==(i>0?a:0)){if(0==n)return this.done=!0,this.value="",this;i>0&&this.offsets[n-1]++,this.nodes.pop(),this.offsets.pop()}else if((1&s)==(i>0?0:1)){if(this.offsets[n]+=i,0==t)return this.lineBreak=!0,this.value="\n",this;t--}else if(r instanceof e){let e=r.text[o+(i<0?-1:0)];if(this.offsets[n]+=i,e.length>Math.max(0,t))return this.value=0==t?e:i>0?e.slice(t):e.slice(0,e.length-t),this;t-=e.length}else{let s=r.children[o+(i<0?-1:0)];t>s.length?(t-=s.length,this.offsets[n]+=i):(i<0&&this.offsets[n]--,this.nodes.push(s),this.offsets.push(i>0?1:(s instanceof e?s.text.length:s.children.length)<<1))}}}next(t=0){return t<0&&(this.nextInner(-t,-this.dir),t=this.value.length),this.nextInner(t,this.dir)}}class o{constructor(t,e,i){this.value="",this.done=!1,this.cursor=new s(t,e>i?-1:1),this.pos=e>i?t.length:0,this.from=Math.min(e,i),this.to=Math.max(e,i)}nextInner(t,e){if(e<0?this.pos<=this.from:this.pos>=this.to)return this.value="",this.done=!0,this;t+=Math.max(0,e<0?this.pos-this.to:this.from-this.pos);let i=e<0?this.pos-this.from:this.to-this.pos;t>i&&(t=i),i-=t;let{value:n}=this.cursor.next(t);return this.pos+=(n.length+t)*e,this.value=n.length<=i?n:e<0?n.slice(n.length-i):n.slice(0,i),this.done=!this.value,this}next(t=0){return t<0?t=Math.max(t,this.from-this.pos):t>0&&(t=Math.min(t,this.to-this.pos)),this.nextInner(t,this.cursor.dir)}get lineBreak(){return this.cursor.lineBreak&&""!=this.value}}class a{constructor(t){this.inner=t,this.afterBreak=!0,this.value="",this.done=!1}next(t=0){let{done:e,lineBreak:i,value:n}=this.inner.next(t);return e&&this.afterBreak?(this.value="",this.afterBreak=!1):e?(this.done=!0,this.value=""):i?this.afterBreak?this.value="":(this.afterBreak=!0,this.next()):(this.value=n,this.afterBreak=!1),this}get lineBreak(){return!1}}"undefined"!=typeof Symbol&&(t.prototype[Symbol.iterator]=function(){return this.iter()},s.prototype[Symbol.iterator]=o.prototype[Symbol.iterator]=a.prototype[Symbol.iterator]=function(){return this});class l{constructor(t,e,i,n){this.from=t,this.to=e,this.number=i,this.text=n}get length(){return this.to-this.from}}function h(t,e,i){return[e=Math.max(0,Math.min(t.length,e)),Math.max(e,Math.min(t.length,i))]}let c="lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map((t=>t?parseInt(t,36):1));for(let t=1;tt)return c[e-1]<=t;return!1}function p(t){return t>=127462&&t<=127487}const d=8205;function f(t,e,i=!0,n=!0){return(i?O:m)(t,e,n)}function O(t,e,i){if(e==t.length)return e;e&&g(t.charCodeAt(e))&&y(t.charCodeAt(e-1))&&e--;let n=x(t,e);for(e+=S(n);e=0&&p(x(t,n));)i++,n-=2;if(i%2==0)break;e+=2}}}return e}function m(t,e,i){for(;e>0;){let n=O(t,e-2,i);if(n=56320&&t<57344}function y(t){return t>=55296&&t<56320}function x(t,e){let i=t.charCodeAt(e);if(!y(i)||e+1==t.length)return i;let n=t.charCodeAt(e+1);return g(n)?n-56320+(i-55296<<10)+65536:i}function S(t){return t<65536?1:2}const w=/\r\n?|\n/;var b=function(t){return t[t.Simple=0]="Simple",t[t.TrackDel=1]="TrackDel",t[t.TrackBefore=2]="TrackBefore",t[t.TrackAfter=3]="TrackAfter",t}(b||(b={}));class v{constructor(t){this.sections=t}get length(){let t=0;for(let e=0;et)return r+(t-n);r+=o}else{if(i!=b.Simple&&l>=t&&(i==b.TrackDel&&nt||i==b.TrackBefore&&nt))return null;if(l>t||l==t&&e<0&&!o)return t==n||e<0?r:r+a;r+=a}n=l}if(t>n)throw new RangeError(`Position ${t} is out of range for changeset of length ${n}`);return r}touchesRange(t,e=t){for(let i=0,n=0;i=0&&n<=e&&r>=t)return!(ne)||"cover";n=r}return!1}toString(){let t="";for(let e=0;e=0?":"+n:"")}return t}toJSON(){return this.sections}static fromJSON(t){if(!Array.isArray(t)||t.length%2||t.some((t=>"number"!=typeof t)))throw new RangeError("Invalid JSON representation of ChangeDesc");return new v(t)}static create(t){return new v(t)}}class k extends v{constructor(t,e){super(t),this.inserted=e}apply(t){if(this.length!=t.length)throw new RangeError("Applying change set to a document with the wrong length");return $(this,((e,i,n,r,s)=>t=t.replace(n,n+(i-e),s)),!1),t}mapDesc(t,e=!1){return Z(this,t,e,!0)}invert(e){let i=this.sections.slice(),n=[];for(let r=0,s=0;r=0){i[r]=a,i[r+1]=o;let l=r>>1;for(;n.length0&&P(i,e,r.text),r.forward(t),o+=t}let l=t[s++];for(;o>1].toJSON()))}return t}static of(e,i,n){let r=[],s=[],o=0,a=null;function l(t=!1){if(!t&&!r.length)return;oa||e<0||a>i)throw new RangeError(`Invalid change range ${e} to ${a} (in doc of length ${i})`);let u=c?"string"==typeof c?t.of(c.split(n||w)):c:t.empty,p=u.length;if(e==a&&0==p)return;eo&&Q(r,e-o,-1),Q(r,a-e,p),P(s,r,u),o=a}}(e),l(!a),a}static empty(t){return new k(t?[t,-1]:[],[])}static fromJSON(e){if(!Array.isArray(e))throw new RangeError("Invalid JSON representation of ChangeSet");let i=[],n=[];for(let r=0;re&&"string"!=typeof t)))throw new RangeError("Invalid JSON representation of ChangeSet");if(1==s.length)i.push(s[0],0);else{for(;n.length=0&&i<=0&&i==t[r+1]?t[r]+=e:0==e&&0==t[r]?t[r+1]+=i:n?(t[r]+=e,t[r+1]+=i):t.push(e,i)}function P(e,i,n){if(0==n.length)return;let r=i.length-2>>1;if(r>1])),!(n||a==e.sections.length||e.sections[a+1]<0);)l=e.sections[a++],h=e.sections[a++];i(s,c,o,u,p),s=c,o=u}}}function Z(t,e,i,n=!1){let r=[],s=n?[]:null,o=new T(t),a=new T(e);for(let t=-1;;)if(-1==o.ins&&-1==a.ins){let t=Math.min(o.len,a.len);Q(r,t,-1),o.forward(t),a.forward(t)}else if(a.ins>=0&&(o.ins<0||t==o.i||0==o.off&&(a.len=0&&t=0)){if(o.done&&a.done)return s?k.createSet(r,s):v.create(r);throw new Error("Mismatched change set lengths")}{let e=0,i=o.len;for(;i;)if(-1==a.ins){let t=Math.min(i,a.len);e+=t,i-=t,a.forward(t)}else{if(!(0==a.ins&&a.lene||o.ins>=0&&o.len>e)&&(t||n.length>i),s.forward2(e),o.forward(e)}}else Q(n,0,o.ins,t),r&&P(r,n,o.text),o.next()}}class T{constructor(t){this.set=t,this.i=0,this.next()}next(){let{sections:t}=this.set;this.i>1;return i>=e.length?t.empty:e[i]}textBit(e){let{inserted:i}=this.set,n=this.i-2>>1;return n>=i.length&&!e?t.empty:i[n].slice(this.off,null==e?void 0:this.off+e)}forward(t){t==this.len?this.next():(this.len-=t,this.off+=t)}forward2(t){-1==this.ins?this.forward(t):t==this.ins?this.next():(this.ins-=t,this.off+=t)}}class A{constructor(t,e,i){this.from=t,this.to=e,this.flags=i}get anchor(){return 32&this.flags?this.to:this.from}get head(){return 32&this.flags?this.from:this.to}get empty(){return this.from==this.to}get assoc(){return 8&this.flags?-1:16&this.flags?1:0}get bidiLevel(){let t=7&this.flags;return 7==t?null:t}get goalColumn(){let t=this.flags>>6;return 16777215==t?void 0:t}map(t,e=-1){let i,n;return this.empty?i=n=t.mapPos(this.from,e):(i=t.mapPos(this.from,1),n=t.mapPos(this.to,-1)),i==this.from&&n==this.to?this:new A(i,n,this.flags)}extend(t,e=t){if(t<=this.anchor&&e>=this.anchor)return _.range(t,e);let i=Math.abs(t-this.anchor)>Math.abs(e-this.anchor)?t:e;return _.range(this.anchor,i)}eq(t,e=!1){return!(this.anchor!=t.anchor||this.head!=t.head||e&&this.empty&&this.assoc!=t.assoc)}toJSON(){return{anchor:this.anchor,head:this.head}}static fromJSON(t){if(!t||"number"!=typeof t.anchor||"number"!=typeof t.head)throw new RangeError("Invalid JSON representation for SelectionRange");return _.range(t.anchor,t.head)}static create(t,e,i){return new A(t,e,i)}}class _{constructor(t,e){this.ranges=t,this.mainIndex=e}map(t,e=-1){return t.empty?this:_.create(this.ranges.map((i=>i.map(t,e))),this.mainIndex)}eq(t,e=!1){if(this.ranges.length!=t.ranges.length||this.mainIndex!=t.mainIndex)return!1;for(let i=0;it.toJSON())),main:this.mainIndex}}static fromJSON(t){if(!t||!Array.isArray(t.ranges)||"number"!=typeof t.main||t.main>=t.ranges.length)throw new RangeError("Invalid JSON representation for EditorSelection");return new _(t.ranges.map((t=>A.fromJSON(t))),t.main)}static single(t,e=t){return new _([_.range(t,e)],0)}static create(t,e=0){if(0==t.length)throw new RangeError("A selection needs at least one range");for(let i=0,n=0;nt?8:0)|r)}static normalized(t,e=0){let i=t[e];t.sort(((t,e)=>t.from-e.from)),e=t.indexOf(i);for(let i=1;in.head?_.range(o,s):_.range(s,o))}}return new _(t,e)}}function E(t,e){for(let i of t.ranges)if(i.to>e)throw new RangeError("Selection points outside of document")}let X=0;class R{constructor(t,e,i,n,r){this.combine=t,this.compareInput=e,this.compare=i,this.isStatic=n,this.id=X++,this.default=t([]),this.extensions="function"==typeof r?r(this):r}get reader(){return this}static define(t={}){return new R(t.combine||(t=>t),t.compareInput||((t,e)=>t===e),t.compare||(t.combine?(t,e)=>t===e:V),!!t.static,t.enables)}of(t){return new Y([],this,0,t)}compute(t,e){if(this.isStatic)throw new Error("Can't compute a static facet");return new Y(t,this,1,e)}computeN(t,e){if(this.isStatic)throw new Error("Can't compute a static facet");return new Y(t,this,2,e)}from(t,e){return e||(e=t=>t),this.compute([t],(i=>e(i.field(t))))}}function V(t,e){return t==e||t.length==e.length&&t.every(((t,i)=>t===e[i]))}class Y{constructor(t,e,i,n){this.dependencies=t,this.facet=e,this.type=i,this.value=n,this.id=X++}dynamicSlot(t){var e;let i=this.value,n=this.facet.compareInput,r=this.id,s=t[r]>>1,o=2==this.type,a=!1,l=!1,h=[];for(let i of this.dependencies)"doc"==i?a=!0:"selection"==i?l=!0:1&(null!==(e=t[i.id])&&void 0!==e?e:1)||h.push(t[i.id]);return{create:t=>(t.values[s]=i(t),1),update(t,e){if(a&&e.docChanged||l&&(e.docChanged||e.selection)||q(t,h)){let e=i(t);if(o?!W(e,t.values[s],n):!n(e,t.values[s]))return t.values[s]=e,1}return 0},reconfigure:(t,e)=>{let a,l=e.config.address[r];if(null!=l){let r=tt(e,l);if(this.dependencies.every((i=>i instanceof R?e.facet(i)===t.facet(i):!(i instanceof D)||e.field(i,!1)==t.field(i,!1)))||(o?W(a=i(t),r,n):n(a=i(t),r)))return t.values[s]=r,0}else a=i(t);return t.values[s]=a,1}}}}function W(t,e,i){if(t.length!=e.length)return!1;for(let n=0;nt[e.id])),r=i.map((t=>t.type)),s=n.filter((t=>!(1&t))),o=t[e.id]>>1;function a(t){let i=[];for(let e=0;et===e),t);return t.provide&&(e.provides=t.provide(e)),e}create(t){let e=t.facet(j).find((t=>t.field==this));return((null==e?void 0:e.create)||this.createF)(t)}slot(t){let e=t[this.id]>>1;return{create:t=>(t.values[e]=this.create(t),1),update:(t,i)=>{let n=t.values[e],r=this.updateF(n,i);return this.compareF(n,r)?0:(t.values[e]=r,1)},reconfigure:(t,i)=>null!=i.config.address[this.id]?(t.values[e]=i.field(this),0):(t.values[e]=this.create(t),1)}}init(t){return[this,j.of({field:this,create:t})]}get extension(){return this}}const M=4,N=3,z=2,B=1;function L(t){return e=>new U(e,t)}const G={highest:L(0),high:L(B),default:L(z),low:L(N),lowest:L(M)};class U{constructor(t,e){this.inner=t,this.prec=e}}class F{of(t){return new H(this,t)}reconfigure(t){return F.reconfigure.of({compartment:this,extension:t})}get(t){return t.config.compartments.get(this)}}class H{constructor(t,e){this.compartment=t,this.inner=e}}class K{constructor(t,e,i,n,r,s){for(this.base=t,this.compartments=e,this.dynamicSlots=i,this.address=n,this.staticValues=r,this.facets=s,this.statusTemplate=[];this.statusTemplate.length>1]}static resolve(t,e,i){let n=[],r=Object.create(null),s=new Map;for(let i of function(t,e,i){let n=[[],[],[],[],[]],r=new Map;function s(t,o){let a=r.get(t);if(null!=a){if(a<=o)return;let e=n[a].indexOf(t);e>-1&&n[a].splice(e,1),t instanceof H&&i.delete(t.compartment)}if(r.set(t,o),Array.isArray(t))for(let e of t)s(e,o);else if(t instanceof H){if(i.has(t.compartment))throw new RangeError("Duplicate use of compartment in extensions");let n=e.get(t.compartment)||t.inner;i.set(t.compartment,n),s(n,o)}else if(t instanceof U)s(t.inner,t.prec);else if(t instanceof D)n[o].push(t),t.provides&&s(t.provides,o);else if(t instanceof Y)n[o].push(t),t.facet.extensions&&s(t.facet.extensions,z);else{let e=t.extension;if(!e)throw new Error(`Unrecognized extension value in extension set (${t}). This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.`);s(e,o)}}return s(t,z),n.reduce(((t,e)=>t.concat(e)))}(t,e,s))i instanceof D?n.push(i):(r[i.facet.id]||(r[i.facet.id]=[])).push(i);let o=Object.create(null),a=[],l=[];for(let t of n)o[t.id]=l.length<<1,l.push((e=>t.slot(e)));let h=null==i?void 0:i.config.facets;for(let t in r){let e=r[t],n=e[0].facet,s=h&&h[t]||[];if(e.every((t=>0==t.type)))if(o[n.id]=a.length<<1|1,V(s,e))a.push(i.facet(n));else{let t=n.combine(e.map((t=>t.value)));a.push(i&&n.compare(t,i.facet(n))?i.facet(n):t)}else{for(let t of e)0==t.type?(o[t.id]=a.length<<1|1,a.push(t.value)):(o[t.id]=l.length<<1,l.push((e=>t.dynamicSlot(e))));o[n.id]=l.length<<1,l.push((t=>I(t,n,e)))}}let c=l.map((t=>t(o)));return new K(t,s,c,o,a,r)}}function J(t,e){if(1&e)return 2;let i=e>>1,n=t.status[i];if(4==n)throw new Error("Cyclic dependency between fields and/or facets");if(2&n)return n;t.status[i]=4;let r=t.computeSlot(t,t.config.dynamicSlots[i]);return t.status[i]=2|r}function tt(t,e){return 1&e?t.config.staticValues[e>>1]:t.values[e>>1]}const et=R.define(),it=R.define({combine:t=>t.some((t=>t)),static:!0}),nt=R.define({combine:t=>t.length?t[0]:void 0,static:!0}),rt=R.define(),st=R.define(),ot=R.define(),at=R.define({combine:t=>!!t.length&&t[0]});class lt{constructor(t,e){this.type=t,this.value=e}static define(){return new ht}}class ht{of(t){return new lt(this,t)}}class ct{constructor(t){this.map=t}of(t){return new ut(this,t)}}class ut{constructor(t,e){this.type=t,this.value=e}map(t){let e=this.type.map(this.value,t);return void 0===e?void 0:e==this.value?this:new ut(this.type,e)}is(t){return this.type==t}static define(t={}){return new ct(t.map||(t=>t))}static mapEffects(t,e){if(!t.length)return t;let i=[];for(let n of t){let t=n.map(e);t&&i.push(t)}return i}}ut.reconfigure=ut.define(),ut.appendConfig=ut.define();class pt{constructor(t,e,i,n,r,s){this.startState=t,this.changes=e,this.selection=i,this.effects=n,this.annotations=r,this.scrollIntoView=s,this._doc=null,this._state=null,i&&E(i,e.newLength),r.some((t=>t.type==pt.time))||(this.annotations=r.concat(pt.time.of(Date.now())))}static create(t,e,i,n,r,s){return new pt(t,e,i,n,r,s)}get newDoc(){return this._doc||(this._doc=this.changes.apply(this.startState.doc))}get newSelection(){return this.selection||this.startState.selection.map(this.changes)}get state(){return this._state||this.startState.applyTransaction(this),this._state}annotation(t){for(let e of this.annotations)if(e.type==t)return e.value}get docChanged(){return!this.changes.empty}get reconfigured(){return this.startState.config!=this.state.config}isUserEvent(t){let e=this.annotation(pt.userEvent);return!(!e||!(e==t||e.length>t.length&&e.slice(0,t.length)==t&&"."==e[t.length]))}}function dt(t,e){let i=[];for(let n=0,r=0;;){let s,o;if(n=t[n]))s=t[n++],o=t[n++];else{if(!(r=0;r--){let s=i[r](t);s&&Object.keys(s).length&&(n=ft(n,Ot(e,s,t.changes.newLength),!0))}return n==t?t:pt.create(e,t.changes,t.selection,n.effects,n.annotations,n.scrollIntoView)}(i?function(t){let e=t.startState,i=!0;for(let n of e.facet(rt)){let e=n(t);if(!1===e){i=!1;break}Array.isArray(e)&&(i=!0===i?e:dt(i,e))}if(!0!==i){let n,r;if(!1===i)r=t.changes.invertedDesc,n=k.empty(e.doc.length);else{let e=t.changes.filter(i);n=e.changes,r=e.filtered.mapDesc(e.changes).invertedDesc}t=pt.create(e,n,t.selection&&t.selection.map(r),ut.mapEffects(t.effects,r),t.annotations,t.scrollIntoView)}let n=e.facet(st);for(let i=n.length-1;i>=0;i--){let r=n[i](t);t=r instanceof pt?r:Array.isArray(r)&&1==r.length&&r[0]instanceof pt?r[0]:mt(e,yt(r),!1)}return t}(r):r)}pt.time=lt.define(),pt.userEvent=lt.define(),pt.addToHistory=lt.define(),pt.remote=lt.define();const gt=[];function yt(t){return null==t?gt:Array.isArray(t)?t:[t]}var xt=function(t){return t[t.Word=0]="Word",t[t.Space=1]="Space",t[t.Other=2]="Other",t}(xt||(xt={}));const St=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;let wt;try{wt=new RegExp("[\\p{Alphabetic}\\p{Number}_]","u")}catch(t){}function bt(t){return e=>{if(!/\S/.test(e))return xt.Space;if(function(t){if(wt)return wt.test(t);for(let e=0;e"€"&&(i.toUpperCase()!=i.toLowerCase()||St.test(i)))return!0}return!1}(e))return xt.Word;for(let i=0;i-1)return xt.Word;return xt.Other}}class vt{constructor(t,e,i,n,r,s){this.config=t,this.doc=e,this.selection=i,this.values=n,this.status=t.statusTemplate.slice(),this.computeSlot=r,s&&(s._state=this);for(let t=0;tr.set(e,t))),i=null),r.set(e.value.compartment,e.value.extension)):e.is(ut.reconfigure)?(i=null,n=e.value):e.is(ut.appendConfig)&&(i=null,n=yt(n).concat(e.value));if(i)e=t.startState.values.slice();else{i=K.resolve(n,r,this),e=new vt(i,this.doc,this.selection,i.dynamicSlots.map((()=>null)),((t,e)=>e.reconfigure(t,this)),null).values}let s=t.startState.facet(it)?t.newSelection:t.newSelection.asSingle();new vt(i,t.newDoc,s,e,((e,i)=>i.update(e,t)),t)}replaceSelection(t){return"string"==typeof t&&(t=this.toText(t)),this.changeByRange((e=>({changes:{from:e.from,to:e.to,insert:t},range:_.cursor(e.from+t.length)})))}changeByRange(t){let e=this.selection,i=t(e.ranges[0]),n=this.changes(i.changes),r=[i.range],s=yt(i.effects);for(let i=1;ir.spec.fromJSON(s,t))))}return vt.create({doc:t.doc,selection:_.fromJSON(t.selection),extensions:e.extensions?n.concat([e.extensions]):n})}static create(e={}){let i=K.resolve(e.extensions||[],new Map),n=e.doc instanceof t?e.doc:t.of((e.doc||"").split(i.staticFacet(vt.lineSeparator)||w)),r=e.selection?e.selection instanceof _?e.selection:_.single(e.selection.anchor,e.selection.head):_.single(0);return E(r,n.length),i.staticFacet(it)||(r=r.asSingle()),new vt(i,n,r,i.dynamicSlots.map((()=>null)),((t,e)=>e.create(t)),null)}get tabSize(){return this.facet(vt.tabSize)}get lineBreak(){return this.facet(vt.lineSeparator)||"\n"}get readOnly(){return this.facet(at)}phrase(t,...e){for(let e of this.facet(vt.phrases))if(Object.prototype.hasOwnProperty.call(e,t)){t=e[t];break}return e.length&&(t=t.replace(/\$(\$|\d*)/g,((t,i)=>{if("$"==i)return"$";let n=+(i||1);return!n||n>e.length?t:e[n-1]}))),t}languageDataAt(t,e,i=-1){let n=[];for(let r of this.facet(et))for(let s of r(this,e,i))Object.prototype.hasOwnProperty.call(s,t)&&n.push(s[t]);return n}charCategorizer(t){return bt(this.languageDataAt("wordChars",t).join(""))}wordAt(t){let{text:e,from:i,length:n}=this.doc.lineAt(t),r=this.charCategorizer(t),s=t-i,o=t-i;for(;s>0;){let t=f(e,s,!1);if(r(e.slice(t,s))!=xt.Word)break;s=t}for(;ot.length?t[0]:4}),vt.lineSeparator=nt,vt.readOnly=at,vt.phrases=R.define({compare(t,e){let i=Object.keys(t),n=Object.keys(e);return i.length==n.length&&i.every((i=>t[i]==e[i]))}}),vt.languageData=et,vt.changeFilter=rt,vt.transactionFilter=st,vt.transactionExtender=ot,F.reconfigure=ut.define();class Qt{eq(t){return this==t}range(t,e=t){return Pt.create(t,e,this)}}Qt.prototype.startSide=Qt.prototype.endSide=0,Qt.prototype.point=!1,Qt.prototype.mapMode=b.TrackDel;let Pt=class t{constructor(t,e,i){this.from=t,this.to=e,this.value=i}static create(e,i,n){return new t(e,i,n)}};function $t(t,e){return t.from-e.from||t.value.startSide-e.value.startSide}class Zt{constructor(t,e,i,n){this.from=t,this.to=e,this.value=i,this.maxPoint=n}get length(){return this.to[this.to.length-1]}findIndex(t,e,i,n=0){let r=i?this.to:this.from;for(let s=n,o=r.length;;){if(s==o)return s;let n=s+o>>1,a=r[n]-t||(i?this.value[n].endSide:this.value[n].startSide)-e;if(n==s)return a>=0?s:o;a>=0?o=n:s=n+1}}between(t,e,i,n){for(let r=this.findIndex(e,-1e9,!0),s=this.findIndex(i,1e9,!1,r);rh||l==h&&c.startSide>0&&c.endSide<=0)continue;(h-l||c.endSide-c.startSide)<0||(s<0&&(s=l),c.point&&(o=Math.max(o,h-l)),i.push(c),n.push(l-s),r.push(h-s))}return{mapped:i.length?new Zt(n,r,i,o):null,pos:s}}}class Ct{constructor(t,e,i,n){this.chunkPos=t,this.chunk=e,this.nextLayer=i,this.maxPoint=n}static create(t,e,i,n){return new Ct(t,e,i,n)}get length(){let t=this.chunk.length-1;return t<0?0:Math.max(this.chunkEnd(t),this.nextLayer.length)}get size(){if(this.isEmpty)return 0;let t=this.nextLayer.size;for(let e of this.chunk)t+=e.value.length;return t}chunkEnd(t){return this.chunkPos[t]+this.chunk[t].length}update(t){let{add:e=[],sort:i=!1,filterFrom:n=0,filterTo:r=this.length}=t,s=t.filter;if(0==e.length&&!s)return this;if(i&&(e=e.slice().sort($t)),this.isEmpty)return e.length?Ct.of(e):this;let o=new _t(this,null,-1).goto(0),a=0,l=[],h=new Tt;for(;o.value||a=0){let t=e[a++];h.addInner(t.from,t.to,t.value)||l.push(t)}else 1==o.rangeIndex&&o.chunkIndexthis.chunkEnd(o.chunkIndex)||ro.to||r=r&&t<=r+s.length&&!1===s.between(r,t-r,e-r,i))return}this.nextLayer.between(t,e,i)}}iter(t=0){return Et.from([this]).goto(t)}get isEmpty(){return this.nextLayer==this}static iter(t,e=0){return Et.from(t).goto(e)}static compare(t,e,i,n,r=-1){let s=t.filter((t=>t.maxPoint>0||!t.isEmpty&&t.maxPoint>=r)),o=e.filter((t=>t.maxPoint>0||!t.isEmpty&&t.maxPoint>=r)),a=At(s,o,i),l=new Rt(s,a,r),h=new Rt(o,a,r);i.iterGaps(((t,e,i)=>Vt(l,t,h,e,i,n))),i.empty&&0==i.length&&Vt(l,0,h,0,0,n)}static eq(t,e,i=0,n){null==n&&(n=999999999);let r=t.filter((t=>!t.isEmpty&&e.indexOf(t)<0)),s=e.filter((e=>!e.isEmpty&&t.indexOf(e)<0));if(r.length!=s.length)return!1;if(!r.length)return!0;let o=At(r,s),a=new Rt(r,o,0).goto(i),l=new Rt(s,o,0).goto(i);for(;;){if(a.to!=l.to||!Yt(a.active,l.active)||a.point&&(!l.point||!a.point.eq(l.point)))return!1;if(a.to>n)return!0;a.next(),l.next()}}static spans(t,e,i,n,r=-1){let s=new Rt(t,null,r).goto(e),o=e,a=s.openStart;for(;;){let t=Math.min(s.to,i);if(s.point){let i=s.activeForPoint(s.to),r=s.pointFromo&&(n.span(o,t,s.active,a),a=s.openEnd(t));if(s.to>i)return a+(s.point&&s.to>i?1:0);o=s.to,s.next()}}static of(t,e=!1){let i=new Tt;for(let n of t instanceof Pt?[t]:e?function(t){if(t.length>1)for(let e=t[0],i=1;i0)return t.slice().sort($t);e=n}return t}(t):t)i.add(n.from,n.to,n.value);return i.finish()}static join(t){if(!t.length)return Ct.empty;let e=t[t.length-1];for(let i=t.length-2;i>=0;i--)for(let n=t[i];n!=Ct.empty;n=n.nextLayer)e=new Ct(n.chunkPos,n.chunk,e,Math.max(n.maxPoint,e.maxPoint));return e}}Ct.empty=new Ct([],[],null,-1),Ct.empty.nextLayer=Ct.empty;class Tt{finishChunk(t){this.chunks.push(new Zt(this.from,this.to,this.value,this.maxPoint)),this.chunkPos.push(this.chunkStart),this.chunkStart=-1,this.setMaxPoint=Math.max(this.setMaxPoint,this.maxPoint),this.maxPoint=-1,t&&(this.from=[],this.to=[],this.value=[])}constructor(){this.chunks=[],this.chunkPos=[],this.chunkStart=-1,this.last=null,this.lastFrom=-1e9,this.lastTo=-1e9,this.from=[],this.to=[],this.value=[],this.maxPoint=-1,this.setMaxPoint=-1,this.nextLayer=null}add(t,e,i){this.addInner(t,e,i)||(this.nextLayer||(this.nextLayer=new Tt)).add(t,e,i)}addInner(t,e,i){let n=t-this.lastTo||i.startSide-this.last.endSide;if(n<=0&&(t-this.lastFrom||i.startSide-this.last.startSide)<0)throw new Error("Ranges must be added sorted by `from` position and `startSide`");return!(n<0)&&(250==this.from.length&&this.finishChunk(!0),this.chunkStart<0&&(this.chunkStart=t),this.from.push(t-this.chunkStart),this.to.push(e-this.chunkStart),this.last=i,this.lastFrom=t,this.lastTo=e,this.value.push(i),i.point&&(this.maxPoint=Math.max(this.maxPoint,e-t)),!0)}addChunk(t,e){if((t-this.lastTo||e.value[0].startSide-this.last.endSide)<0)return!1;this.from.length&&this.finishChunk(!0),this.setMaxPoint=Math.max(this.setMaxPoint,e.maxPoint),this.chunks.push(e),this.chunkPos.push(t);let i=e.value.length-1;return this.last=e.value[i],this.lastFrom=e.from[i]+t,this.lastTo=e.to[i]+t,!0}finish(){return this.finishInner(Ct.empty)}finishInner(t){if(this.from.length&&this.finishChunk(!1),0==this.chunks.length)return t;let e=Ct.create(this.chunkPos,this.chunks,this.nextLayer?this.nextLayer.finishInner(t):t,this.setMaxPoint);return this.from=null,e}}function At(t,e,i){let n=new Map;for(let e of t)for(let t=0;t=this.minPoint)break}}}setRangeIndex(t){if(t==this.layer.chunk[this.chunkIndex].value.length){if(this.chunkIndex++,this.skip)for(;this.chunkIndex=i&&n.push(new _t(s,e,i,r));return 1==n.length?n[0]:new Et(n)}get startSide(){return this.value?this.value.startSide:0}goto(t,e=-1e9){for(let i of this.heap)i.goto(t,e);for(let t=this.heap.length>>1;t>=0;t--)Xt(this.heap,t);return this.next(),this}forward(t,e){for(let i of this.heap)i.forward(t,e);for(let t=this.heap.length>>1;t>=0;t--)Xt(this.heap,t);(this.to-t||this.value.endSide-e)<0&&this.next()}next(){if(0==this.heap.length)this.from=this.to=1e9,this.value=null,this.rank=-1;else{let t=this.heap[0];this.from=t.from,this.to=t.to,this.value=t.value,this.rank=t.rank,t.value&&t.next(),Xt(this.heap,0)}}}function Xt(t,e){for(let i=t[e];;){let n=1+(e<<1);if(n>=t.length)break;let r=t[n];if(n+1=0&&(r=t[n+1],n++),i.compare(r)<0)break;t[n]=i,t[e]=r,e=n}}class Rt{constructor(t,e,i){this.minPoint=i,this.active=[],this.activeTo=[],this.activeRank=[],this.minActive=-1,this.point=null,this.pointFrom=0,this.pointRank=0,this.to=-1e9,this.endSide=0,this.openStart=-1,this.cursor=Et.from(t,e,i)}goto(t,e=-1e9){return this.cursor.goto(t,e),this.active.length=this.activeTo.length=this.activeRank.length=0,this.minActive=-1,this.to=t,this.endSide=e,this.openStart=-1,this.next(),this}forward(t,e){for(;this.minActive>-1&&(this.activeTo[this.minActive]-t||this.active[this.minActive].endSide-e)<0;)this.removeActive(this.minActive);this.cursor.forward(t,e)}removeActive(t){Wt(this.active,t),Wt(this.activeTo,t),Wt(this.activeRank,t),this.minActive=It(this.active,this.activeTo)}addActive(t){let e=0,{value:i,to:n,rank:r}=this.cursor;for(;e0;)e++;qt(this.active,e,i),qt(this.activeTo,e,n),qt(this.activeRank,e,r),t&&qt(t,e,this.cursor.from),this.minActive=It(this.active,this.activeTo)}next(){let t=this.to,e=this.point;this.point=null;let i=this.openStart<0?[]:null;for(;;){let n=this.minActive;if(n>-1&&(this.activeTo[n]-this.cursor.from||this.active[n].endSide-this.cursor.startSide)<0){if(this.activeTo[n]>t){this.to=this.activeTo[n],this.endSide=this.active[n].endSide;break}this.removeActive(n),i&&Wt(i,n)}else{if(!this.cursor.value){this.to=this.endSide=1e9;break}if(this.cursor.from>t){this.to=this.cursor.from,this.endSide=this.cursor.startSide;break}{let t=this.cursor.value;if(t.point){if(!(e&&this.cursor.to==this.to&&this.cursor.from=0&&i[e]=0&&!(this.activeRank[i]t||this.activeTo[i]==t&&this.active[i].endSide>=this.point.endSide)&&e.push(this.active[i]);return e.reverse()}openEnd(t){let e=0;for(let i=this.activeTo.length-1;i>=0&&this.activeTo[i]>t;i--)e++;return e}}function Vt(t,e,i,n,r,s){t.goto(e),i.goto(n);let o=n+r,a=n,l=n-e;for(;;){let e=t.to+l-i.to||t.endSide-i.endSide,n=e<0?t.to+l:i.to,r=Math.min(n,o);if(t.point||i.point?t.point&&i.point&&(t.point==i.point||t.point.eq(i.point))&&Yt(t.activeForPoint(t.to),i.activeForPoint(i.to))||s.comparePoint(a,r,t.point,i.point):r>a&&!Yt(t.active,i.active)&&s.compareRange(a,r,t.active,i.active),n>o)break;a=n,e<=0&&t.next(),e>=0&&i.next()}}function Yt(t,e){if(t.length!=e.length)return!1;for(let i=0;i=e;i--)t[i+1]=t[i];t[e]=i}function It(t,e){let i=-1,n=1e9;for(let r=0;rt.map((t=>e.replace(/&/,t))))).reduce(((t,e)=>t.concat(e))),o,s);else if(o&&"object"==typeof o){if(!l)throw new RangeError("The value of a property ("+i+") should be a primitive value.");r(n(i),o,a,h)}else null!=o&&a.push(i.replace(/_.*/,"").replace(/[A-Z]/g,(t=>"-"+t.toLowerCase()))+": "+o+";")}(a.length||h)&&s.push((!i||l||o?t:t.map(i)).join(", ")+" {"+a.join(" ")+"}")}for(let e in t)r(n(e),t[e],this.rules)}getRules(){return this.rules.join("\n")}static newName(){let t=Nt[Dt]||1;return Nt[Dt]=t+1,"ͼ"+t.toString(36)}static mount(t,e,i){let n=t[Mt],r=i&&i.nonce;n?r&&n.setNonce(r):n=new Lt(t,r),n.mount(Array.isArray(e)?e:[e],t)}}let Bt=new Map;class Lt{constructor(t,e){let i=t.ownerDocument||t,n=i.defaultView;if(!t.head&&t.adoptedStyleSheets&&n.CSSStyleSheet){let e=Bt.get(i);if(e)return t[Mt]=e;this.sheet=new n.CSSStyleSheet,Bt.set(i,this)}else this.styleTag=i.createElement("style"),e&&this.styleTag.setAttribute("nonce",e);this.modules=[],t[Mt]=this}mount(t,e){let i=this.sheet,n=0,r=0;for(let e=0;e-1&&(this.modules.splice(o,1),r--,o=-1),-1==o){if(this.modules.splice(r++,0,s),i)for(let t=0;t",191:"?",192:"~",219:"{",220:"|",221:"}",222:'"'},Ft="undefined"!=typeof navigator&&/Mac/.test(navigator.platform),Ht="undefined"!=typeof navigator&&/MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent),Kt=0;Kt<10;Kt++)Gt[48+Kt]=Gt[96+Kt]=String(Kt);for(Kt=1;Kt<=24;Kt++)Gt[Kt+111]="F"+Kt;for(Kt=65;Kt<=90;Kt++)Gt[Kt]=String.fromCharCode(Kt+32),Ut[Kt]=String.fromCharCode(Kt);for(var Jt in Gt)Ut.hasOwnProperty(Jt)||(Ut[Jt]=Gt[Jt]);function te(t){let e;return e=11==t.nodeType?t.getSelection?t:t.ownerDocument:t,e.getSelection()}function ee(t,e){return!!e&&(t==e||t.contains(1!=e.nodeType?e.parentNode:e))}function ie(t,e){if(!e.anchorNode)return!1;try{return ee(t,e.anchorNode)}catch(t){return!1}}function ne(t){return 3==t.nodeType?Oe(t,0,t.nodeValue.length).getClientRects():1==t.nodeType?t.getClientRects():[]}function re(t,e,i,n){return!!i&&(oe(t,e,i,n,-1)||oe(t,e,i,n,1))}function se(t){for(var e=0;;e++)if(!(t=t.previousSibling))return e}function oe(t,e,i,n,r){for(;;){if(t==i&&e==n)return!0;if(e==(r<0?0:ae(t))){if("DIV"==t.nodeName)return!1;let i=t.parentNode;if(!i||1!=i.nodeType)return!1;e=se(t)+(r<0?0:1),t=i}else{if(1!=t.nodeType)return!1;if(1==(t=t.childNodes[e+(r<0?-1:0)]).nodeType&&"false"==t.contentEditable)return!1;e=r<0?ae(t):0}}}function ae(t){return 3==t.nodeType?t.nodeValue.length:t.childNodes.length}function le(t,e){let i=e?t.left:t.right;return{left:i,right:i,top:t.top,bottom:t.bottom}}function he(t){return{left:0,right:t.innerWidth,top:0,bottom:t.innerHeight}}function ce(t,e){let i=e.width/t.offsetWidth,n=e.height/t.offsetHeight;return(i>.995&&i<1.005||!isFinite(i)||Math.abs(e.width-t.offsetWidth)<1)&&(i=1),(n>.995&&n<1.005||!isFinite(n)||Math.abs(e.height-t.offsetHeight)<1)&&(n=1),{scaleX:i,scaleY:n}}class ue{constructor(){this.anchorNode=null,this.anchorOffset=0,this.focusNode=null,this.focusOffset=0}eq(t){return this.anchorNode==t.anchorNode&&this.anchorOffset==t.anchorOffset&&this.focusNode==t.focusNode&&this.focusOffset==t.focusOffset}setRange(t){let{anchorNode:e,focusNode:i}=t;this.set(e,Math.min(t.anchorOffset,e?ae(e):0),i,Math.min(t.focusOffset,i?ae(i):0))}set(t,e,i,n){this.anchorNode=t,this.anchorOffset=e,this.focusNode=i,this.focusOffset=n}}let pe,de=null;function fe(t){if(t.setActive)return t.setActive();if(de)return t.focus(de);let e=[];for(let i=t;i&&(e.push(i,i.scrollTop,i.scrollLeft),i!=i.ownerDocument);i=i.parentNode);if(t.focus(null==de?{get preventScroll(){return de={preventScroll:!0},!0}}:void 0),!de){de=!1;for(let t=0;tMath.max(1,t.scrollHeight-t.clientHeight-4)}class xe{constructor(t,e,i=!0){this.node=t,this.offset=e,this.precise=i}static before(t,e){return new xe(t.parentNode,se(t),e)}static after(t,e){return new xe(t.parentNode,se(t)+1,e)}}const Se=[];class we{constructor(){this.parent=null,this.dom=null,this.flags=2}get overrideDOMText(){return null}get posAtStart(){return this.parent?this.parent.posBefore(this):0}get posAtEnd(){return this.posAtStart+this.length}posBefore(t){let e=this.posAtStart;for(let i of this.children){if(i==t)return e;e+=i.length+i.breakAfter}throw new RangeError("Invalid child in posBefore")}posAfter(t){return this.posBefore(t)+t.length}sync(t,e){if(2&this.flags){let i,n=this.dom,r=null;for(let s of this.children){if(7&s.flags){if(!s.dom&&(i=r?r.nextSibling:n.firstChild)){let t=we.get(i);(!t||!t.parent&&t.canReuseDOM(s))&&s.reuseDOM(i)}s.sync(t,e),s.flags&=-8}if(i=r?r.nextSibling:n.firstChild,e&&!e.written&&e.node==n&&i!=s.dom&&(e.written=!0),s.dom.parentNode==n)for(;i&&i!=s.dom;)i=be(i);else n.insertBefore(s.dom,i);r=s.dom}for(i=r?r.nextSibling:n.firstChild,i&&e&&e.node==n&&(e.written=!0);i;)i=be(i)}else if(1&this.flags)for(let i of this.children)7&i.flags&&(i.sync(t,e),i.flags&=-8)}reuseDOM(t){}localPosFromDOM(t,e){let i;if(t==this.dom)i=this.dom.childNodes[e];else{let n=0==ae(t)?0:0==e?-1:1;for(;;){let e=t.parentNode;if(e==this.dom)break;0==n&&e.firstChild!=e.lastChild&&(n=t==e.firstChild?-1:1),t=e}i=n<0?t:t.nextSibling}if(i==this.dom.firstChild)return 0;for(;i&&!we.get(i);)i=i.nextSibling;if(!i)return this.length;for(let t=0,e=0;;t++){let n=this.children[t];if(n.dom==i)return e;e+=n.length+n.breakAfter}}domBoundsAround(t,e,i=0){let n=-1,r=-1,s=-1,o=-1;for(let a=0,l=i,h=i;ae)return i.domBoundsAround(t,e,l);if(c>=t&&-1==n&&(n=a,r=l),l>e&&i.dom.parentNode==this.dom){s=a,o=h;break}h=c,l=c+i.breakAfter}return{from:r,to:o<0?i+this.length:o,startDOM:(n?this.children[n-1].dom.nextSibling:null)||this.dom.firstChild,endDOM:s=0?this.children[s].dom:null}}markDirty(t=!1){this.flags|=2,this.markParentsDirty(t)}markParentsDirty(t){for(let e=this.parent;e;e=e.parent){if(t&&(e.flags|=2),1&e.flags)return;e.flags|=1,t=!1}}setParent(t){this.parent!=t&&(this.parent=t,7&this.flags&&this.markParentsDirty(!0))}setDOM(t){this.dom!=t&&(this.dom&&(this.dom.cmView=null),this.dom=t,t.cmView=this)}get rootView(){for(let t=this;;){let e=t.parent;if(!e)return t;t=e}}replaceChildren(t,e,i=Se){this.markDirty();for(let n=t;nthis.pos||t==this.pos&&(e>0||0==this.i||this.children[this.i-1].breakAfter))return this.off=t-this.pos,this;let i=this.children[--this.i];this.pos-=i.length+i.breakAfter}}}function ke(t,e,i,n,r,s,o,a,l){let{children:h}=t,c=h.length?h[e]:null,u=s.length?s[s.length-1]:null,p=u?u.breakAfter:o;if(!(e==n&&c&&!o&&!p&&s.length<2&&c.merge(i,r,s.length?u:null,0==i,a,l))){if(n0&&(!o&&s.length&&c.merge(i,c.length,s[0],!1,a,0)?c.breakAfter=s.shift().breakAfter:(i2);var Ye={mac:Ve||/Mac/.test(Pe.platform),windows:/Win/.test(Pe.platform),linux:/Linux|X11/.test(Pe.platform),ie:Ae,ie_version:Ce?$e.documentMode||6:Te?+Te[1]:Ze?+Ze[1]:0,gecko:_e,gecko_version:_e?+(/Firefox\/(\d+)/.exec(Pe.userAgent)||[0,0])[1]:0,chrome:!!Ee,chrome_version:Ee?+Ee[1]:0,ios:Ve,android:/Android\b/.test(Pe.userAgent),webkit:Xe,safari:Re,webkit_version:Xe?+(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent)||[0,0])[1]:0,tabSize:null!=$e.documentElement.style.tabSize?"tab-size":"-moz-tab-size"};class We extends we{constructor(t){super(),this.text=t}get length(){return this.text.length}createDOM(t){this.setDOM(t||document.createTextNode(this.text))}sync(t,e){this.dom||this.createDOM(),this.dom.nodeValue!=this.text&&(e&&e.node==this.dom&&(e.written=!0),this.dom.nodeValue=this.text)}reuseDOM(t){3==t.nodeType&&this.createDOM(t)}merge(t,e,i){return!(8&this.flags||i&&(!(i instanceof We)||this.length-(e-t)+i.length>256||8&i.flags))&&(this.text=this.text.slice(0,t)+(i?i.text:"")+this.text.slice(e),this.markDirty(),!0)}split(t){let e=new We(this.text.slice(t));return this.text=this.text.slice(0,t),this.markDirty(),e.flags|=8&this.flags,e}localPosFromDOM(t,e){return t==this.dom?e:e?this.text.length:0}domAtPos(t){return new xe(this.dom,t)}domBoundsAround(t,e,i){return{from:i,to:i+this.length,startDOM:this.dom,endDOM:this.dom.nextSibling}}coordsAt(t,e){return function(t,e,i){let n=t.nodeValue.length;e>n&&(e=n);let r=e,s=e,o=0;0==e&&i<0||e==n&&i>=0?Ye.chrome||Ye.gecko||(e?(r--,o=1):s=0)?0:a.length-1];Ye.safari&&!o&&0==l.width&&(l=Array.prototype.find.call(a,(t=>t.width))||l);return o?le(l,o<0):l||null}(this.dom,t,e)}}class qe extends we{constructor(t,e=[],i=0){super(),this.mark=t,this.children=e,this.length=i;for(let t of e)t.setParent(this)}setAttrs(t){if(ge(t),this.mark.class&&(t.className=this.mark.class),this.mark.attrs)for(let e in this.mark.attrs)t.setAttribute(e,this.mark.attrs[e]);return t}canReuseDOM(t){return super.canReuseDOM(t)&&!(8&(this.flags|t.flags))}reuseDOM(t){t.nodeName==this.mark.tagName.toUpperCase()&&(this.setDOM(t),this.flags|=6)}sync(t,e){this.dom?4&this.flags&&this.setAttrs(this.dom):this.setDOM(this.setAttrs(document.createElement(this.mark.tagName))),super.sync(t,e)}merge(t,e,i,n,r,s){return(!i||!(!(i instanceof qe&&i.mark.eq(this.mark))||t&&r<=0||et&&e.push(i=t&&(n=r),i=o,r++}let s=this.length-t;return this.length=t,n>-1&&(this.children.length=n,this.markDirty()),new qe(this.mark,e,s)}domAtPos(t){return De(this,t)}coordsAt(t,e){return Ne(this,t,e)}}class Ie extends we{static create(t,e,i){return new Ie(t,e,i)}constructor(t,e,i){super(),this.widget=t,this.length=e,this.side=i,this.prevWidget=null}split(t){let e=Ie.create(this.widget,this.length-t,this.side);return this.length-=t,e}sync(t){this.dom&&this.widget.updateDOM(this.dom,t)||(this.dom&&this.prevWidget&&this.prevWidget.destroy(this.dom),this.prevWidget=null,this.setDOM(this.widget.toDOM(t)),this.widget.editable||(this.dom.contentEditable="false"))}getSide(){return this.side}merge(t,e,i,n,r,s){return!(i&&(!(i instanceof Ie&&this.widget.compare(i.widget))||t>0&&r<=0||e0)?xe.before(this.dom):xe.after(this.dom,t==this.length)}domBoundsAround(){return null}coordsAt(t,e){let i=this.widget.coordsAt(this.dom,t,e);if(i)return i;let n=this.dom.getClientRects(),r=null;if(!n.length)return null;let s=this.side?this.side<0:t>0;for(let e=s?n.length-1:0;r=n[e],!(t>0?0==e:e==n.length-1||r.top0?xe.before(this.dom):xe.after(this.dom)}localPosFromDOM(){return 0}domBoundsAround(){return null}coordsAt(t){return this.dom.getBoundingClientRect()}get overrideDOMText(){return t.empty}get isHidden(){return!0}}function De(t,e){let i=t.dom,{children:n}=t,r=0;for(let t=0;rt&&e0;t--){let e=n[t-1];if(e.dom.parentNode==i)return e.domAtPos(e.length)}for(let t=r;t0&&e instanceof qe&&r.length&&(n=r[r.length-1])instanceof qe&&n.mark.eq(e.mark)?Me(n,e.children[0],i-1):(r.push(e),e.setParent(t)),t.length+=e.length}function Ne(t,e,i){let n=null,r=-1,s=null,o=-1;!function t(e,a){for(let l=0,h=0;l=a&&(c.children.length?t(c,a-h):(!s||s.isHidden&&i>0)&&(u>a||h==u&&c.getSide()>0)?(s=c,o=a-h):(h-1?1:0)!=r.length-(i&&r.indexOf(i)>-1?1:0))return!1;for(let s of n)if(s!=i&&(-1==r.indexOf(s)||t[s]!==e[s]))return!1;return!0}function Ge(t,e,i){let n=!1;if(e)for(let r in e)i&&r in i||(n=!0,"style"==r?t.style.cssText="":t.removeAttribute(r));if(i)for(let r in i)e&&e[r]==i[r]||(n=!0,"style"==r?t.style.cssText=i[r]:t.setAttribute(r,i[r]));return n}function Ue(t){let e=Object.create(null);for(let i=0;i0&&0==this.children[i-1].length;)this.children[--i].destroy();return this.children.length=i,this.markDirty(),this.length=t,e}transferDOM(t){this.dom&&(this.markDirty(),t.setDOM(this.dom),t.prevAttrs=void 0===this.prevAttrs?this.attrs:this.prevAttrs,this.prevAttrs=void 0,this.dom=null)}setDeco(t){Le(this.attrs,t)||(this.dom&&(this.prevAttrs=this.attrs,this.markDirty()),this.attrs=t)}append(t,e){Me(this,t,e)}addLineDeco(t){let e=t.spec.attributes,i=t.spec.class;e&&(this.attrs=ze(e,this.attrs||{})),i&&(this.attrs=ze({class:i},this.attrs||{}))}domAtPos(t){return De(this,t)}reuseDOM(t){"DIV"==t.nodeName&&(this.setDOM(t),this.flags|=6)}sync(t,e){var i;this.dom?4&this.flags&&(ge(this.dom),this.dom.className="cm-line",this.prevAttrs=this.attrs?null:void 0):(this.setDOM(document.createElement("div")),this.dom.className="cm-line",this.prevAttrs=this.attrs?null:void 0),void 0!==this.prevAttrs&&(Ge(this.dom,this.prevAttrs,this.attrs),this.dom.classList.add("cm-line"),this.prevAttrs=void 0),super.sync(t,e);let n=this.dom.lastChild;for(;n&&we.get(n)instanceof qe;)n=n.lastChild;if(!(n&&this.length&&("BR"==n.nodeName||0!=(null===(i=we.get(n))||void 0===i?void 0:i.isEditable)||Ye.ios&&this.children.some((t=>t instanceof We))))){let t=document.createElement("BR");t.cmIgnore=!0,this.dom.appendChild(t)}}measureTextSize(){if(0==this.children.length||this.length>20)return null;let t,e=0;for(let i of this.children){if(!(i instanceof We)||/[^ -~]/.test(i.text))return null;let n=ne(i.dom);if(1!=n.length)return null;e+=n[0].width,t=n[0].height}return e?{lineHeight:this.dom.getBoundingClientRect().height,charWidth:e/this.length,textHeight:t}:null}coordsAt(t,e){let i=Ne(this,t,e);if(!this.children.length&&i&&this.parent){let{heightOracle:t}=this.parent.view.viewState,e=i.bottom-i.top;if(Math.abs(e-t.lineHeight)<2&&t.textHeight=e){if(r instanceof Fe)return r;if(s>e)break}n=s+r.breakAfter}return null}}class He extends we{constructor(t,e,i){super(),this.widget=t,this.length=e,this.deco=i,this.breakAfter=0,this.prevWidget=null}merge(t,e,i,n,r,s){return!(i&&(!(i instanceof He&&this.widget.compare(i.widget))||t>0&&r<=0||e0)}}class Ke{eq(t){return!1}updateDOM(t,e){return!1}compare(t){return this==t||this.constructor==t.constructor&&this.eq(t)}get estimatedHeight(){return-1}get lineBreaks(){return 0}ignoreEvent(t){return!0}coordsAt(t,e,i){return null}get isHidden(){return!1}get editable(){return!1}destroy(t){}}var Je=function(t){return t[t.Text=0]="Text",t[t.WidgetBefore=1]="WidgetBefore",t[t.WidgetAfter=2]="WidgetAfter",t[t.WidgetRange=3]="WidgetRange",t}(Je||(Je={}));class ti extends Qt{constructor(t,e,i,n){super(),this.startSide=t,this.endSide=e,this.widget=i,this.spec=n}get heightRelevant(){return!1}static mark(t){return new ei(t)}static widget(t){let e=Math.max(-1e4,Math.min(1e4,t.side||0)),i=!!t.block;return e+=i&&!t.inlineOrder?e>0?3e8:-4e8:e>0?1e8:-1e8,new ni(t,e,e,i,t.widget||null,!1)}static replace(t){let e,i,n=!!t.block;if(t.isBlockGap)e=-5e8,i=4e8;else{let{start:r,end:s}=ri(t,n);e=(r?n?-3e8:-1:5e8)-1,i=1+(s?n?2e8:1:-6e8)}return new ni(t,e,i,n,t.widget||null,!0)}static line(t){return new ii(t)}static set(t,e=!1){return Ct.of(t,e)}hasHeight(){return!!this.widget&&this.widget.estimatedHeight>-1}}ti.none=Ct.empty;class ei extends ti{constructor(t){let{start:e,end:i}=ri(t);super(e?-1:5e8,i?1:-6e8,null,t),this.tagName=t.tagName||"span",this.class=t.class||"",this.attrs=t.attributes||null}eq(t){var e,i;return this==t||t instanceof ei&&this.tagName==t.tagName&&(this.class||(null===(e=this.attrs)||void 0===e?void 0:e.class))==(t.class||(null===(i=t.attrs)||void 0===i?void 0:i.class))&&Le(this.attrs,t.attrs,"class")}range(t,e=t){if(t>=e)throw new RangeError("Mark decorations may not be empty");return super.range(t,e)}}ei.prototype.point=!1;class ii extends ti{constructor(t){super(-2e8,-2e8,null,t)}eq(t){return t instanceof ii&&this.spec.class==t.spec.class&&Le(this.spec.attributes,t.spec.attributes)}range(t,e=t){if(e!=t)throw new RangeError("Line decoration ranges must be zero-length");return super.range(t,e)}}ii.prototype.mapMode=b.TrackBefore,ii.prototype.point=!0;class ni extends ti{constructor(t,e,i,n,r,s){super(e,i,r,t),this.block=n,this.isReplace=s,this.mapMode=n?e<=0?b.TrackBefore:b.TrackAfter:b.TrackDel}get type(){return this.startSide!=this.endSide?Je.WidgetRange:this.startSide<=0?Je.WidgetBefore:Je.WidgetAfter}get heightRelevant(){return this.block||!!this.widget&&(this.widget.estimatedHeight>=5||this.widget.lineBreaks>0)}eq(t){return t instanceof ni&&(e=this.widget,i=t.widget,e==i||!!(e&&i&&e.compare(i)))&&this.block==t.block&&this.startSide==t.startSide&&this.endSide==t.endSide;var e,i}range(t,e=t){if(this.isReplace&&(t>e||t==e&&this.startSide>0&&this.endSide<=0))throw new RangeError("Invalid range for replacement decoration");if(!this.isReplace&&e!=t)throw new RangeError("Widget decorations can only have zero-length ranges");return super.range(t,e)}}function ri(t,e=!1){let{inclusiveStart:i,inclusiveEnd:n}=t;return null==i&&(i=t.inclusive),null==n&&(n=t.inclusive),{start:null!=i?i:e,end:null!=n?n:e}}function si(t,e,i,n=0){let r=i.length-1;r>=0&&i[r]+n>=t?i[r]=Math.max(i[r],e):i.push(t,e)}ni.prototype.point=!0;class oi{constructor(t,e,i,n){this.doc=t,this.pos=e,this.end=i,this.disallowBlockEffectsFor=n,this.content=[],this.curLine=null,this.breakAtStart=0,this.pendingBuffer=0,this.bufferMarks=[],this.atCursorPos=!0,this.openStart=-1,this.openEnd=-1,this.text="",this.textOff=0,this.cursor=t.iter(),this.skip=e}posCovered(){if(0==this.content.length)return!this.breakAtStart&&this.doc.lineAt(this.pos).from!=this.pos;let t=this.content[this.content.length-1];return!(t.breakAfter||t instanceof He&&t.deco.endSide<0)}getLine(){return this.curLine||(this.content.push(this.curLine=new Fe),this.atCursorPos=!0),this.curLine}flushBuffer(t=this.bufferMarks){this.pendingBuffer&&(this.curLine.append(ai(new je(-1),t),t.length),this.pendingBuffer=0)}addBlockWidget(t){this.flushBuffer(),this.curLine=null,this.content.push(t)}finish(t){this.pendingBuffer&&t<=this.bufferMarks.length?this.flushBuffer():this.pendingBuffer=0,this.posCovered()||t&&this.content.length&&this.content[this.content.length-1]instanceof He||this.getLine()}buildText(t,e,i){for(;t>0;){if(this.textOff==this.text.length){let{value:e,lineBreak:i,done:n}=this.cursor.next(this.skip);if(this.skip=0,n)throw new Error("Ran out of text content when drawing inline views");if(i){this.posCovered()||this.getLine(),this.content.length?this.content[this.content.length-1].breakAfter=1:this.breakAtStart=1,this.flushBuffer(),this.curLine=null,this.atCursorPos=!0,t--;continue}this.text=e,this.textOff=0}let n=Math.min(this.text.length-this.textOff,t,512);this.flushBuffer(e.slice(e.length-i)),this.getLine().append(ai(new We(this.text.slice(this.textOff,this.textOff+n)),e),i),this.atCursorPos=!0,this.textOff+=n,t-=n,i=0}}span(t,e,i,n){this.buildText(e-t,i,n),this.pos=e,this.openStart<0&&(this.openStart=n)}point(t,e,i,n,r,s){if(this.disallowBlockEffectsFor[s]&&i instanceof ni){if(i.block)throw new RangeError("Block decorations may not be specified via plugins");if(e>this.doc.lineAt(this.pos).to)throw new RangeError("Decorations that replace line breaks may not be specified via plugins")}let o=e-t;if(i instanceof ni)if(i.block)i.startSide>0&&!this.posCovered()&&this.getLine(),this.addBlockWidget(new He(i.widget||li.block,o,i));else{let s=Ie.create(i.widget||li.inline,o,o?0:i.startSide),a=this.atCursorPos&&!s.isEditable&&r<=n.length&&(t0),l=!s.isEditable&&(tn.length||i.startSide<=0),h=this.getLine();2!=this.pendingBuffer||a||s.isEditable||(this.pendingBuffer=0),this.flushBuffer(n),a&&(h.append(ai(new je(1),n),r),r=n.length+Math.max(0,r-n.length)),h.append(ai(s,n),r),this.atCursorPos=l,this.pendingBuffer=l?tn.length?1:2:0,this.pendingBuffer&&(this.bufferMarks=n.slice())}else this.doc.lineAt(this.pos).from==this.pos&&this.getLine().addLineDeco(i);o&&(this.textOff+o<=this.text.length?this.textOff+=o:(this.skip+=o-(this.text.length-this.textOff),this.text="",this.textOff=0),this.pos=e),this.openStart<0&&(this.openStart=r)}static build(t,e,i,n,r){let s=new oi(t,e,i,r);return s.openEnd=Ct.spans(n,e,i,s),s.openStart<0&&(s.openStart=s.openEnd),s.finish(s.openEnd),s}}function ai(t,e){for(let i of e)t=new qe(i,[t],t.length);return t}class li extends Ke{constructor(t){super(),this.tag=t}eq(t){return t.tag==this.tag}toDOM(){return document.createElement(this.tag)}updateDOM(t){return t.nodeName.toLowerCase()==this.tag}get isHidden(){return!0}}li.inline=new li("span"),li.block=new li("div");var hi=function(t){return t[t.LTR=0]="LTR",t[t.RTL=1]="RTL",t}(hi||(hi={}));const ci=hi.LTR,ui=hi.RTL;function pi(t){let e=[];for(let i=0;i=e){if(o.level==i)return s;(r<0||(0!=n?n<0?o.frome:t[r].level>o.level))&&(r=s)}}if(r<0)throw new RangeError("Index out of range");return r}}function Si(t,e){if(t.length!=e.length)return!1;for(let i=0;il&&o.push(new xi(l,f.from,p)),vi(t,f.direction==ci!=!(p%2)?n+1:n,r,f.inner,f.from,f.to,o),l=f.to}d=f.to}else{if(d==i||(e?wi[d]!=a:wi[d]==a))break;d++}u?bi(t,l,d,n+1,r,u,o):le;){let i=!0,c=!1;if(!h||l>s[h-1].to){let t=wi[l-1];t!=a&&(i=!1,c=16==t)}let u=i||1!=a?null:[],p=i?n:n+1,d=l;t:for(;;)if(h&&d==s[h-1].to){if(c)break t;let f=s[--h];if(!i)for(let t=f.from,i=h;;){if(t==e)break t;if(!i||s[i-1].to!=t){if(wi[t-1]==a)break t;break}t=s[--i].from}if(u)u.push(f);else{f.to=0;t-=3)if(mi[t+1]==-i){let e=mi[t+2],i=2&e?r:4&e?1&e?s:r:0;i&&(wi[o]=wi[mi[t]]=i),a=t;break}}else{if(189==mi.length)break;mi[a++]=o,mi[a++]=e,mi[a++]=l}else if(2==(n=wi[o])||1==n){let t=n==r;l=t?0:1;for(let e=a-3;e>=0;e-=3){let i=mi[e+2];if(2&i)break;if(t)mi[e+2]|=2;else{if(4&i)break;mi[e+2]|=4}}}}}(t,r,s,n,a),function(t,e,i,n){for(let r=0,s=n;r<=i.length;r++){let o=r?i[r-1].to:t,a=rl;)e==s&&(e=i[--n].from,s=n?i[n-1].to:t),wi[--e]=c;l=o}else s=o,l++}}}(r,s,n,a),bi(t,r,s,e,i,n,o)}function ki(t){return[new xi(0,t,0)]}let Qi="";function Pi(t,e,i,n,r){var s;let o=n.head-t.from,a=xi.find(e,o,null!==(s=n.bidiLevel)&&void 0!==s?s:-1,n.assoc),l=e[a],h=l.side(r,i);if(o==h){let t=a+=r?1:-1;if(t<0||t>=e.length)return null;l=e[a=t],o=l.side(!r,i),h=l.side(r,i)}let c=f(t.text,o,l.forward(r,i));(cl.to)&&(c=h),Qi=t.text.slice(Math.min(o,c),Math.max(o,c));let u=a==(r?e.length-1:0)?null:e[a+(r?1:-1)];return u&&c==h&&u.level+(r?0:1)t.some((t=>t))}),Vi=R.define({combine:t=>t.some((t=>t))});class Yi{constructor(t,e="nearest",i="nearest",n=5,r=5,s=!1){this.range=t,this.y=e,this.x=i,this.yMargin=n,this.xMargin=r,this.isSnapshot=s}map(t){return t.empty?this:new Yi(this.range.map(t),this.y,this.x,this.yMargin,this.xMargin,this.isSnapshot)}clip(t){return this.range.to<=t.doc.length?this:new Yi(_.cursor(t.doc.length),this.y,this.x,this.yMargin,this.xMargin,this.isSnapshot)}}const Wi=ut.define({map:(t,e)=>t.map(e)});function qi(t,e,i){let n=t.facet(Ai);n.length?n[0](e):window.onerror?window.onerror(String(e),i,void 0,void 0,e):i?console.error(i+":",e):console.error(e)}const Ii=R.define({combine:t=>!t.length||t[0]});let ji=0;const Di=R.define();class Mi{constructor(t,e,i,n,r){this.id=t,this.create=e,this.domEventHandlers=i,this.domEventObservers=n,this.extension=r(this)}static define(t,e){const{eventHandlers:i,eventObservers:n,provide:r,decorations:s}=e||{};return new Mi(ji++,t,i,n,(t=>{let e=[Di.of(t)];return s&&e.push(Li.of((e=>{let i=e.plugin(t);return i?s(i):ti.none}))),r&&e.push(r(t)),e}))}static fromClass(t,e){return Mi.define((e=>new t(e)),e)}}class Ni{constructor(t){this.spec=t,this.mustUpdate=null,this.value=null}update(t){if(this.value){if(this.mustUpdate){let t=this.mustUpdate;if(this.mustUpdate=null,this.value.update)try{this.value.update(t)}catch(e){if(qi(t.state,e,"CodeMirror plugin crashed"),this.value.destroy)try{this.value.destroy()}catch(t){}this.deactivate()}}}else if(this.spec)try{this.value=this.spec.create(t)}catch(e){qi(t.state,e,"CodeMirror plugin crashed"),this.deactivate()}return this}destroy(t){var e;if(null===(e=this.value)||void 0===e?void 0:e.destroy)try{this.value.destroy()}catch(e){qi(t.state,e,"CodeMirror plugin crashed")}}deactivate(){this.spec=this.value=null}}const zi=R.define(),Bi=R.define(),Li=R.define(),Gi=R.define(),Ui=R.define(),Fi=R.define();function Hi(t,e){let i=t.state.facet(Fi);if(!i.length)return i;let n=i.map((e=>e instanceof Function?e(t):e)),r=[];return Ct.spans(n,e.from,e.to,{point(){},span(t,i,n,s){let o=t-e.from,a=i-e.from,l=r;for(let t=n.length-1;t>=0;t--,s--){let i,r=n[t].spec.bidiIsolate;if(null==r&&(r=$i(e.text,o,a)),s>0&&l.length&&(i=l[l.length-1]).to==o&&i.direction==r)i.to=a,l=i.inner;else{let t={from:o,to:a,direction:r,inner:[]};l.push(t),l=t.inner}}}}),r}const Ki=R.define();function Ji(t){let e=0,i=0,n=0,r=0;for(let s of t.state.facet(Ki)){let o=s(t);o&&(null!=o.left&&(e=Math.max(e,o.left)),null!=o.right&&(i=Math.max(i,o.right)),null!=o.top&&(n=Math.max(n,o.top)),null!=o.bottom&&(r=Math.max(r,o.bottom)))}return{left:e,right:i,top:n,bottom:r}}const tn=R.define();class en{constructor(t,e,i,n){this.fromA=t,this.toA=e,this.fromB=i,this.toB=n}join(t){return new en(Math.min(this.fromA,t.fromA),Math.max(this.toA,t.toA),Math.min(this.fromB,t.fromB),Math.max(this.toB,t.toB))}addToSet(t){let e=t.length,i=this;for(;e>0;e--){let n=t[e-1];if(!(n.fromA>i.toA)){if(n.toAh)break;r+=2}if(!a)return i;new en(a.fromA,a.toA,a.fromB,a.toB).addToSet(i),s=a.toA,o=a.toB}}}class nn{constructor(t,e,i){this.view=t,this.state=e,this.transactions=i,this.flags=0,this.startState=t.state,this.changes=k.empty(this.startState.doc.length);for(let t of i)this.changes=this.changes.compose(t.changes);let n=[];this.changes.iterChangedRanges(((t,e,i,r)=>n.push(new en(t,e,i,r)))),this.changedRanges=n}static create(t,e,i){return new nn(t,e,i)}get viewportChanged(){return(4&this.flags)>0}get heightChanged(){return(2&this.flags)>0}get geometryChanged(){return this.docChanged||(10&this.flags)>0}get focusChanged(){return(1&this.flags)>0}get docChanged(){return!this.changes.empty}get selectionSet(){return this.transactions.some((t=>t.selection))}get empty(){return 0==this.flags&&0==this.transactions.length}}class rn extends we{get length(){return this.view.state.doc.length}constructor(t){super(),this.view=t,this.decorations=[],this.dynamicDecorationMap=[!1],this.domChanged=null,this.hasComposition=null,this.markedForComposition=new Set,this.compositionBarrier=ti.none,this.minWidth=0,this.minWidthFrom=0,this.minWidthTo=0,this.impreciseAnchor=null,this.impreciseHead=null,this.forceSelection=!1,this.lastUpdate=Date.now(),this.setDOM(t.contentDOM),this.children=[new Fe],this.children[0].setParent(this),this.updateDeco(),this.updateInner([new en(0,0,0,t.state.doc.length)],0,null)}update(t){var e;let i=t.changedRanges;this.minWidth>0&&i.length&&(i.every((({fromA:t,toA:e})=>ethis.minWidthTo))?(this.minWidthFrom=t.changes.mapPos(this.minWidthFrom,1),this.minWidthTo=t.changes.mapPos(this.minWidthTo,1)):this.minWidth=this.minWidthFrom=this.minWidthTo=0);let n=-1;this.view.inputState.composing>=0&&((null===(e=this.domChanged)||void 0===e?void 0:e.newSel)?n=this.domChanged.newSel.head:function(t,e){let i=!1;e&&t.iterChangedRanges(((t,n)=>{te.from&&(i=!0)}));return i}(t.changes,this.hasComposition)||t.selectionSet||(n=t.state.selection.main.head));let r=n>-1?function(t,e,i){let n=an(t,i);if(!n)return null;let{node:r,from:s,to:o}=n,a=r.nodeValue;if(/[\n\r]/.test(a))return null;if(t.state.doc.sliceString(n.from,n.to)!=a)return null;let l=e.invertedDesc,h=new en(l.mapPos(s),l.mapPos(o),s,o),c=[];for(let e=r.parentNode;;e=e.parentNode){let i=we.get(e);if(i instanceof qe)c.push({node:e,deco:i.mark});else{if(i instanceof Fe||"DIV"==e.nodeName&&e.parentNode==t.contentDOM)return{range:h,text:r,marks:c,line:e};if(e==t.contentDOM)return null;c.push({node:e,deco:new ei({inclusive:!0,attributes:Ue(e),tagName:e.tagName.toLowerCase()})})}}}(this.view,t.changes,n):null;if(this.domChanged=null,this.hasComposition){this.markedForComposition.clear();let{from:e,to:n}=this.hasComposition;i=new en(e,n,t.changes.mapPos(e,-1),t.changes.mapPos(n,1)).addToSet(i.slice())}this.hasComposition=r?{from:r.range.fromB,to:r.range.toB}:null,(Ye.ie||Ye.chrome)&&!r&&t&&t.state.doc.lines!=t.startState.doc.lines&&(this.forceSelection=!0);let s=function(t,e,i){let n=new hn;return Ct.compare(t,e,i,n),n.changes}(this.decorations,this.updateDeco(),t.changes);return i=en.extendWithRanges(i,s),!!(7&this.flags||0!=i.length)&&(this.updateInner(i,t.startState.doc.length,r),t.transactions.length&&(this.lastUpdate=Date.now()),!0)}updateInner(t,e,i){this.view.viewState.mustMeasureContent=!0,this.updateChildren(t,e,i);let{observer:n}=this.view;n.ignore((()=>{this.dom.style.height=this.view.viewState.contentHeight/this.view.scaleY+"px",this.dom.style.flexBasis=this.minWidth?this.minWidth+"px":"";let t=Ye.chrome||Ye.ios?{node:n.selectionRange.focusNode,written:!1}:void 0;this.sync(this.view,t),this.flags&=-8,t&&(t.written||n.selectionRange.focusNode!=t.node)&&(this.forceSelection=!0),this.dom.style.height=""})),this.markedForComposition.forEach((t=>t.flags&=-9));let r=[];if(this.view.viewport.from||this.view.viewport.to=0?n[t]:null;if(!e)break;let s,o,a,l,{fromA:h,toA:c,fromB:u,toB:p}=e;if(i&&i.range.fromBu){let t=oi.build(this.view.state.doc,u,i.range.fromB,this.decorations,this.dynamicDecorationMap),e=oi.build(this.view.state.doc,i.range.toB,p,this.decorations,this.dynamicDecorationMap);o=t.breakAtStart,a=t.openStart,l=e.openEnd;let n=this.compositionView(i);e.breakAtStart?n.breakAfter=1:e.content.length&&n.merge(n.length,n.length,e.content[0],!1,e.openStart,0)&&(n.breakAfter=e.content[0].breakAfter,e.content.shift()),t.content.length&&n.merge(0,0,t.content[t.content.length-1],!0,0,t.openEnd)&&t.content.pop(),s=t.content.concat(n).concat(e.content)}else({content:s,breakAtStart:o,openStart:a,openEnd:l}=oi.build(this.view.state.doc,u,p,this.decorations,this.dynamicDecorationMap));let{i:d,off:f}=r.findPos(c,1),{i:O,off:m}=r.findPos(h,-1);ke(this,O,m,d,f,s,o,a,l)}i&&this.fixCompositionDOM(i)}compositionView(t){let e=new We(t.text.nodeValue);e.flags|=8;for(let{deco:i}of t.marks)e=new qe(i,[e],e.length);let i=new Fe;return i.append(e,0),i}fixCompositionDOM(t){let e=(t,e)=>{e.flags|=8|(e.children.some((t=>7&t.flags))?1:0),this.markedForComposition.add(e);let i=we.get(t);i&&i!=e&&(i.dom=null),e.setDOM(t)},i=this.childPos(t.range.fromB,1),n=this.children[i.i];e(t.line,n);for(let r=t.marks.length-1;r>=-1;r--)i=n.childPos(i.off,1),n=n.children[i.i],e(r>=0?t.marks[r].node:t.text,n)}updateSelection(t=!1,e=!1){!t&&this.view.observer.selectionRange.focusNode||this.view.observer.readSelectionRange();let i=this.view.root.activeElement,n=i==this.dom,r=!n&&ie(this.dom,this.view.observer.selectionRange)&&!(i&&this.dom.contains(i));if(!(n||e||r))return;let s=this.forceSelection;this.forceSelection=!1;let o=this.view.state.selection.main,a=this.moveToLine(this.domAtPos(o.anchor)),l=o.empty?a:this.moveToLine(this.domAtPos(o.head));if(Ye.gecko&&o.empty&&!this.hasComposition&&(1==(h=a).node.nodeType&&h.node.firstChild&&(0==h.offset||"false"==h.node.childNodes[h.offset-1].contentEditable)&&(h.offset==h.node.childNodes.length||"false"==h.node.childNodes[h.offset].contentEditable))){let t=document.createTextNode("");this.view.observer.ignore((()=>a.node.insertBefore(t,a.node.childNodes[a.offset]||null))),a=l=new xe(t,0),s=!0}var h;let c=this.view.observer.selectionRange;!s&&c.focusNode&&(re(a.node,a.offset,c.anchorNode,c.anchorOffset)&&re(l.node,l.offset,c.focusNode,c.focusOffset)||this.suppressWidgetCursorChange(c,o))||(this.view.observer.ignore((()=>{Ye.android&&Ye.chrome&&this.dom.contains(c.focusNode)&&function(t,e){for(let i=t;i&&i!=e;i=i.assignedSlot||i.parentNode)if(1==i.nodeType&&"false"==i.contentEditable)return!0;return!1}(c.focusNode,this.dom)&&(this.dom.blur(),this.dom.focus({preventScroll:!0}));let t=te(this.view.root);if(t)if(o.empty){if(Ye.gecko){let t=(e=a.node,n=a.offset,1!=e.nodeType?0:(n&&"false"==e.childNodes[n-1].contentEditable?1:0)|(no.head&&([a,l]=[l,a]),e.setEnd(l.node,l.offset),e.setStart(a.node,a.offset),t.removeAllRanges(),t.addRange(e)}else;var e,n;r&&this.view.root.activeElement==this.dom&&(this.dom.blur(),i&&i.focus())})),this.view.observer.setSelectionRange(a,l)),this.impreciseAnchor=a.precise?null:new xe(c.anchorNode,c.anchorOffset),this.impreciseHead=l.precise?null:new xe(c.focusNode,c.focusOffset)}suppressWidgetCursorChange(t,e){return this.hasComposition&&e.empty&&!this.compositionBarrier.size&&re(t.focusNode,t.focusOffset,t.anchorNode,t.anchorOffset)&&this.posFromDOM(t.focusNode,t.focusOffset)==e.head}enforceCursorAssoc(){if(this.hasComposition)return;let{view:t}=this,e=t.state.selection.main,i=te(t.root),{anchorNode:n,anchorOffset:r}=t.observer.selectionRange;if(!(i&&e.empty&&e.assoc&&i.modify))return;let s=Fe.find(this,e.head);if(!s)return;let o=s.posAtStart;if(e.head==o||e.head==o+s.length)return;let a=this.coordsAt(e.head,-1),l=this.coordsAt(e.head,1);if(!a||!l||a.bottom>l.top)return;let h=this.domAtPos(e.head+e.assoc);i.collapse(h.node,h.offset),i.modify("move",e.assoc<0?"forward":"backward","lineboundary"),t.observer.readSelectionRange();let c=t.observer.selectionRange;t.docView.posFromDOM(c.anchorNode,c.anchorOffset)!=e.from&&i.collapse(n,r)}moveToLine(t){let e,i=this.dom;if(t.node!=i)return t;for(let n=t.offset;!e&&n=0;n--){let t=we.get(i.childNodes[n]);t instanceof Fe&&(e=t.domAtPos(t.length))}return e?new xe(e.node,e.offset,!0):t}nearest(t){for(let e=t;e;){let t=we.get(e);if(t&&t.rootView==this)return t;e=e.parentNode}return null}posFromDOM(t,e){let i=this.nearest(t);if(!i)throw new RangeError("Trying to find position for a DOM position outside of the document");return i.localPosFromDOM(t,e)+i.posAtStart}domAtPos(t){let{i:e,off:i}=this.childCursor().findPos(t,-1);for(;e=0;s--){let o=this.children[s],a=r-o.breakAfter,l=a-o.length;if(at||o.covers(1))&&(!i||o instanceof Fe&&!(i instanceof Fe&&e>=0))&&(i=o,n=l),r=l}return i?i.coordsAt(t-n,e):null}coordsForChar(t){let{i:e,off:i}=this.childPos(t,1),n=this.children[e];if(!(n instanceof Fe))return null;for(;n.children.length;){let{i:t,off:e}=n.childPos(i,1);for(;;t++){if(t==n.children.length)return null;if((n=n.children[t]).length)break}i=e}if(!(n instanceof We))return null;let r=f(n.text,i);if(r==i)return null;let s=Oe(n.dom,i,r).getClientRects();for(let t=0;tMath.max(this.view.scrollDOM.clientWidth,this.minWidth)+1,o=-1,a=this.view.textDirection==hi.LTR;for(let t=0,l=0;ln)break;if(t>=i){let i=h.dom.getBoundingClientRect();if(e.push(i.height),s){let e=h.dom.lastChild,n=e?ne(e):[];if(n.length){let e=n[n.length-1],s=a?e.right-i.left:i.right-e.left;s>o&&(o=s,this.minWidth=r,this.minWidthFrom=t,this.minWidthTo=c)}}}t=c+h.breakAfter}return e}textDirectionAt(t){let{i:e}=this.childPos(t,1);return"rtl"==getComputedStyle(this.children[e].dom).direction?hi.RTL:hi.LTR}measureTextSize(){for(let t of this.children)if(t instanceof Fe){let e=t.measureTextSize();if(e)return e}let t,e,i,n=document.createElement("div");return n.className="cm-line",n.style.width="99999px",n.style.position="absolute",n.textContent="abc def ghi jkl mno pqr stu",this.view.observer.ignore((()=>{this.dom.appendChild(n);let r=ne(n.firstChild)[0];t=n.getBoundingClientRect().height,e=r?r.width/27:7,i=r?r.height:t,n.remove()})),{lineHeight:t,charWidth:e,textHeight:i}}childCursor(t=this.length){let e=this.children.length;return e&&(t-=this.children[--e].length),new ve(this.children,t,e)}computeBlockGapDeco(){let t=[],e=this.view.viewState;for(let i=0,n=0;;n++){let r=n==e.viewports.length?null:e.viewports[n],s=r?r.from-1:this.length;if(s>i){let n=(e.lineBlockAt(s).bottom-e.lineBlockAt(i).top)/this.view.scaleY;t.push(ti.replace({widget:new on(n),block:!0,inclusive:!0,isBlockGap:!0}).range(i,s))}if(!r)break;i=r.to+1}return ti.set(t)}updateDeco(){let t=1,e=this.view.state.facet(Li).map((e=>(this.dynamicDecorationMap[t++]="function"==typeof e)?e(this.view):e)),i=!1,n=this.view.state.facet(Gi).map(((t,e)=>{let n="function"==typeof t;return n&&(i=!0),n?t(this.view):t}));for(n.length&&(this.dynamicDecorationMap[t++]=i,e.push(Ct.join(n))),this.decorations=[this.compositionBarrier,...e,this.computeBlockGapDeco(),this.view.viewState.lineGapDeco];t{r.point?i=!1:r.endSide<0&&ei.anchor?-1:1);if(!n)return;!i.empty&&(e=this.coordsAt(i.anchor,i.anchor>i.head?-1:1))&&(n={left:Math.min(n.left,e.left),top:Math.min(n.top,e.top),right:Math.max(n.right,e.right),bottom:Math.max(n.bottom,e.bottom)});let r=Ji(this.view),s={left:n.left-r.left,top:n.top-r.top,right:n.right+r.right,bottom:n.bottom+r.bottom},{offsetWidth:o,offsetHeight:a}=this.view.scrollDOM;!function(t,e,i,n,r,s,o,a){let l=t.ownerDocument,h=l.defaultView||window;for(let c=t,u=!1;c&&!u;)if(1==c.nodeType){let t,p=c==l.body,d=1,f=1;if(p)t=he(h);else{if(/^(fixed|sticky)$/.test(getComputedStyle(c).position)&&(u=!0),c.scrollHeight<=c.clientHeight&&c.scrollWidth<=c.clientWidth){c=c.assignedSlot||c.parentNode;continue}let e=c.getBoundingClientRect();({scaleX:d,scaleY:f}=ce(c,e)),t={left:e.left,right:e.left+c.clientWidth*d,top:e.top,bottom:e.top+c.clientHeight*f}}let O=0,m=0;if("nearest"==r)e.top0&&e.bottom>t.bottom+m&&(m=e.bottom-t.bottom+m+o)):e.bottom>t.bottom&&(m=e.bottom-t.bottom+o,i<0&&e.top-m0&&e.right>t.right+O&&(O=e.right-t.right+O+s)):e.right>t.right&&(O=e.right-t.right+s,i<0&&e.left0))break;i=i.childNodes[n-1],n=ae(i)}if(i>=0)for(let n=t,r=e;;){if(3==n.nodeType)return{node:n,offset:r};if(!(1==n.nodeType&&r=0))break;n=n.childNodes[r],r=0}return null}let hn=class{constructor(){this.changes=[]}compareRange(t,e){si(t,e,this.changes)}comparePoint(t,e){si(t,e,this.changes)}};function cn(t,e){return e.left>t?e.left-t:Math.max(0,t-e.right)}function un(t,e){return e.top>t?e.top-t:Math.max(0,t-e.bottom)}function pn(t,e){return t.tope.top+1}function dn(t,e){return et.bottom?{top:t.top,left:t.left,right:t.right,bottom:e}:t}function On(t,e,i){let n,r,s,o,a,l,h,c,u=!1;for(let p=t.firstChild;p;p=p.nextSibling){let t=ne(p);for(let d=0;dm||o==m&&s>O){n=p,r=f,s=O,o=m;let a=m?i0?d0)}0==O?i>f.bottom&&(!h||h.bottomf.top)&&(l=p,c=f):h&&pn(h,f)?h=fn(h,f.bottom):c&&pn(c,f)&&(c=dn(c,f.top))}}if(h&&h.bottom>=i?(n=a,r=h):c&&c.top<=i&&(n=l,r=c),!n)return{node:t,offset:0};let p=Math.max(r.left,Math.min(r.right,e));return 3==n.nodeType?mn(n,p,i):u&&"false"!=n.contentEditable?On(n,p,i):{node:t,offset:Array.prototype.indexOf.call(t.childNodes,n)+(e>=(r.left+r.right)/2?1:0)}}function mn(t,e,i){let n=t.nodeValue.length,r=-1,s=1e9,o=0;for(let a=0;ai?h.top-i:i-h.bottom)-1;if(h.left-1<=e&&h.right+1>=e&&c=(h.left+h.right)/2,n=i;if(Ye.chrome||Ye.gecko){Oe(t,a).getBoundingClientRect().left==h.right&&(n=!i)}if(c<=0)return{node:t,offset:a+(n?1:0)};r=a+(n?1:0),s=c}}}return{node:t,offset:r>-1?r:o>0?t.nodeValue.length:0}}function gn(t,e,i,n=-1){var r,s;let o,a=t.contentDOM.getBoundingClientRect(),l=a.top+t.viewState.paddingTop,{docHeight:h}=t.viewState,{x:c,y:u}=e,p=u-l;if(p<0)return 0;if(p>h)return t.state.doc.length;for(let e=t.viewState.heightOracle.textHeight/2,r=!1;o=t.elementAtHeight(p),o.type!=Je.Text;)for(;p=n>0?o.bottom+e:o.top-e,!(p>=0&&p<=h);){if(r)return i?null:0;r=!0,n=-n}u=l+p;let d=o.from;if(dt.viewport.to)return t.viewport.to==t.state.doc.length?t.state.doc.length:i?null:yn(t,a,o,c,u);let f=t.dom.ownerDocument,O=t.root.elementFromPoint?t.root:f,m=O.elementFromPoint(c,u);m&&!t.contentDOM.contains(m)&&(m=null),m||(c=Math.max(a.left+1,Math.min(a.right-1,c)),m=O.elementFromPoint(c,u),m&&!t.contentDOM.contains(m)&&(m=null));let g,y=-1;if(m&&0!=(null===(r=t.docView.nearest(m))||void 0===r?void 0:r.isEditable))if(f.caretPositionFromPoint){let t=f.caretPositionFromPoint(c,u);t&&({offsetNode:g,offset:y}=t)}else if(f.caretRangeFromPoint){let e=f.caretRangeFromPoint(c,u);e&&(({startContainer:g,startOffset:y}=e),(!t.contentDOM.contains(g)||Ye.safari&&function(t,e,i){let n;if(3!=t.nodeType||e!=(n=t.nodeValue.length))return!1;for(let e=t.nextSibling;e;e=e.nextSibling)if(1!=e.nodeType||"BR"!=e.nodeName)return!1;return Oe(t,n-1,n).getBoundingClientRect().left>i}(g,y,c)||Ye.chrome&&function(t,e,i){if(0!=e)return!1;for(let e=t;;){let t=e.parentNode;if(!t||1!=t.nodeType||t.firstChild!=e)return!1;if(t.classList.contains("cm-line"))break;e=t}let n=1==t.nodeType?t.getBoundingClientRect():Oe(t,0,Math.max(t.nodeValue.length,1)).getBoundingClientRect();return i-n.left>5}(g,y,c))&&(g=void 0))}if(!g||!t.docView.dom.contains(g)){let e=Fe.find(t.docView,d);if(!e)return p>o.top+o.height/2?o.to:o.from;({node:g,offset:y}=On(e.dom,c,u))}let x=t.docView.nearest(g);if(!x)return null;if(x.isWidget&&1==(null===(s=x.dom)||void 0===s?void 0:s.nodeType)){let t=x.dom.getBoundingClientRect();return e.y1.5*t.defaultLineHeight){let e=t.viewState.heightOracle.textHeight;s+=Math.floor((r-i.top-.5*(t.defaultLineHeight-e))/e)*t.viewState.heightOracle.lineLength}let o=t.state.sliceDoc(i.from,i.to);return i.from+function(t,e,i,n){for(let n=0,r=0;;){if(r>=e)return n;if(n==t.length)break;r+=9==t.charCodeAt(n)?i-r%i:1,n=f(t,n)}return!0===n?-1:t.length}(o,s,t.state.tabSize)}function xn(t,e){let i=t.lineBlockAt(e);if(Array.isArray(i.type))for(let t of i.type)if(t.to>e||t.to==e&&(t.to==i.to||t.type==Je.Text))return t;return i}function Sn(t,e,i,n){let r=t.state.doc.lineAt(e.head),s=t.bidiSpans(r),o=t.textDirectionAt(r.from);for(let a=e,l=null;;){let e=Pi(r,s,o,a,i),h=Qi;if(!e){if(r.number==(i?t.state.doc.lines:1))return a;h="\n",r=t.state.doc.line(r.number+(i?1:-1)),s=t.bidiSpans(r),e=t.visualLineSide(r,!i)}if(l){if(!l(h))return a}else{if(!n)return e;l=n(h)}a=e}}function wn(t,e,i){for(;;){let n=0;for(let r of t)r.between(e-1,e+1,((t,r,s)=>{if(e>t&&ee(t))),i.from,e.head>i.from?-1:1);return n==i.from?i:_.cursor(n,nnull)),Ye.gecko&&function(t){Fn.has(t)||(Fn.add(t),t.addEventListener("copy",(()=>{})),t.addEventListener("cut",(()=>{})))}(t.contentDOM.ownerDocument)}handleEvent(t){(function(t,e){if(!e.bubbles)return!0;if(e.defaultPrevented)return!1;for(let i,n=e.target;n!=t.contentDOM;n=n.parentNode)if(!n||11==n.nodeType||(i=we.get(n))&&i.ignoreEvent(e))return!1;return!0})(this.view,t)&&!this.ignoreDuringComposition(t)&&("keydown"==t.type&&this.keydown(t)||this.runHandlers(t.type,t))}runHandlers(t,e){let i=this.handlers[t];if(i){for(let t of i.observers)t(this.view,e);for(let t of i.handlers){if(e.defaultPrevented)break;if(t(this.view,e)){e.preventDefault();break}}}}ensureHandlers(t){let e=Qn(t),i=this.handlers,n=this.view.contentDOM;for(let t in e)if("scroll"!=t){let r=!e[t].handlers.length,s=i[t];s&&r!=!s.handlers.length&&(n.removeEventListener(t,this.handleEvent),s=null),s||n.addEventListener(t,this.handleEvent,{passive:r})}for(let t in i)"scroll"==t||e[t]||n.removeEventListener(t,this.handleEvent);this.handlers=e}keydown(t){if(this.lastKeyCode=t.keyCode,this.lastKeyTime=Date.now(),9==t.keyCode&&Date.now()e.keyCode==t.keyCode)))&&!t.ctrlKey||$n.indexOf(t.key)>-1&&t.ctrlKey&&!t.shiftKey)?(229!=t.keyCode&&this.view.observer.forceFlush(),!1):(this.pendingIOSKey=e||t,setTimeout((()=>this.flushIOSKey()),250),!0)}flushIOSKey(){let t=this.pendingIOSKey;return!!t&&(this.pendingIOSKey=void 0,me(this.view.contentDOM,t.key,t.keyCode))}ignoreDuringComposition(t){return!!/^key/.test(t.type)&&(this.composing>0||!!(Ye.safari&&!Ye.ios&&this.compositionPendingKey&&Date.now()-this.compositionEndedAt<100)&&(this.compositionPendingKey=!1,!0))}startMouseSelection(t){this.mouseSelection&&this.mouseSelection.destroy(),this.mouseSelection=t}update(t){this.mouseSelection&&this.mouseSelection.update(t),this.draggedContent&&t.docChanged&&(this.draggedContent=this.draggedContent.map(t.changes)),t.transactions.length&&(this.lastKeyCode=this.lastSelectionTime=0)}destroy(){this.mouseSelection&&this.mouseSelection.destroy()}}function kn(t,e){return(i,n)=>{try{return e.call(t,n,i)}catch(t){qi(i.state,t)}}}function Qn(t){let e=Object.create(null);function i(t){return e[t]||(e[t]={observers:[],handlers:[]})}for(let e of t){let t=e.spec;if(t&&t.domEventHandlers)for(let n in t.domEventHandlers){let r=t.domEventHandlers[n];r&&i(n).handlers.push(kn(e.value,r))}if(t&&t.domEventObservers)for(let n in t.domEventObservers){let r=t.domEventObservers[n];r&&i(n).observers.push(kn(e.value,r))}}for(let t in An)i(t).handlers.push(An[t]);for(let t in _n)i(t).observers.push(_n[t]);return e}const Pn=[{key:"Backspace",keyCode:8,inputType:"deleteContentBackward"},{key:"Enter",keyCode:13,inputType:"insertParagraph"},{key:"Enter",keyCode:13,inputType:"insertLineBreak"},{key:"Delete",keyCode:46,inputType:"deleteContentForward"}],$n="dthko",Zn=[16,17,18,20,91,92,224,225];function Cn(t){return.7*Math.max(0,t)+8}class Tn{constructor(t,e,i,n){this.view=t,this.startEvent=e,this.style=i,this.mustSelect=n,this.scrollSpeed={x:0,y:0},this.scrolling=-1,this.lastEvent=e,this.scrollParent=function(t){let e=t.ownerDocument;for(let i=t.parentNode;i&&i!=e.body;)if(1==i.nodeType){if(i.scrollHeight>i.clientHeight||i.scrollWidth>i.clientWidth)return i;i=i.assignedSlot||i.parentNode}else{if(11!=i.nodeType)break;i=i.host}return null}(t.contentDOM),this.atoms=t.state.facet(Ui).map((e=>e(t)));let r=t.contentDOM.ownerDocument;r.addEventListener("mousemove",this.move=this.move.bind(this)),r.addEventListener("mouseup",this.up=this.up.bind(this)),this.extend=e.shiftKey,this.multiple=t.state.facet(vt.allowMultipleSelections)&&function(t,e){let i=t.state.facet(Zi);return i.length?i[0](e):Ye.mac?e.metaKey:e.ctrlKey}(t,e),this.dragging=!(!function(t,e){let{main:i}=t.state.selection;if(i.empty)return!1;let n=te(t.root);if(!n||0==n.rangeCount)return!0;let r=n.getRangeAt(0).getClientRects();for(let t=0;t=e.clientX&&i.top<=e.clientY&&i.bottom>=e.clientY)return!0}return!1}(t,e)||1!=Nn(e))&&null}start(t){!1===this.dragging&&this.select(t)}move(t){var e,i,n;if(0==t.buttons)return this.destroy();if(this.dragging||null==this.dragging&&(i=this.startEvent,n=t,Math.max(Math.abs(i.clientX-n.clientX),Math.abs(i.clientY-n.clientY))<10))return;this.select(this.lastEvent=t);let r=0,s=0,o=(null===(e=this.scrollParent)||void 0===e?void 0:e.getBoundingClientRect())||{left:0,top:0,right:this.view.win.innerWidth,bottom:this.view.win.innerHeight},a=Ji(this.view);t.clientX-a.left<=o.left+6?r=-Cn(o.left-t.clientX):t.clientX+a.right>=o.right-6&&(r=Cn(t.clientX-o.right)),t.clientY-a.top<=o.top+6?s=-Cn(o.top-t.clientY):t.clientY+a.bottom>=o.bottom-6&&(s=Cn(t.clientY-o.bottom)),this.setScrollSpeed(r,s)}up(t){null==this.dragging&&this.select(this.lastEvent),this.dragging||t.preventDefault(),this.destroy()}destroy(){this.setScrollSpeed(0,0);let t=this.view.contentDOM.ownerDocument;t.removeEventListener("mousemove",this.move),t.removeEventListener("mouseup",this.up),this.view.inputState.mouseSelection=this.view.inputState.draggedContent=null}setScrollSpeed(t,e){this.scrollSpeed={x:t,y:e},t||e?this.scrolling<0&&(this.scrolling=setInterval((()=>this.scroll()),50)):this.scrolling>-1&&(clearInterval(this.scrolling),this.scrolling=-1)}scroll(){this.scrollParent?(this.scrollParent.scrollLeft+=this.scrollSpeed.x,this.scrollParent.scrollTop+=this.scrollSpeed.y):this.view.win.scrollBy(this.scrollSpeed.x,this.scrollSpeed.y),!1===this.dragging&&this.select(this.lastEvent)}skipAtoms(t){let e=null;for(let i=0;ithis.select(this.lastEvent)),20)}}const An=Object.create(null),_n=Object.create(null),En=Ye.ie&&Ye.ie_version<15||Ye.ios&&Ye.webkit_version<604;function Xn(t,e){let i,{state:n}=t,r=1,s=n.toText(e),o=s.lines==n.selection.ranges.length;if(null!=Bn&&n.selection.ranges.every((t=>t.empty))&&Bn==s.toString()){let t=-1;i=n.changeByRange((i=>{let a=n.doc.lineAt(i.from);if(a.from==t)return{range:i};t=a.from;let l=n.toText((o?s.line(r++).text:e)+n.lineBreak);return{changes:{from:a.from,insert:l},range:_.cursor(i.from+l.length)}}))}else i=o?n.changeByRange((t=>{let e=s.line(r++);return{changes:{from:t.from,to:t.to,insert:e.text},range:_.cursor(t.from+e.length)}})):n.replaceSelection(s);t.dispatch(i,{userEvent:"input.paste",scrollIntoView:!0})}function Rn(t,e,i,n){if(1==n)return _.cursor(e,i);if(2==n)return function(t,e,i=1){let n=t.charCategorizer(e),r=t.doc.lineAt(e),s=e-r.from;if(0==r.length)return _.cursor(e);0==s?i=1:s==r.length&&(i=-1);let o=s,a=s;i<0?o=f(r.text,s,!1):a=f(r.text,s);let l=n(r.text.slice(o,a));for(;o>0;){let t=f(r.text,o,!1);if(n(r.text.slice(t,o))!=l)break;o=t}for(;a{t.inputState.lastScrollTop=t.scrollDOM.scrollTop,t.inputState.lastScrollLeft=t.scrollDOM.scrollLeft},An.keydown=(t,e)=>(t.inputState.setSelectionOrigin("select"),27==e.keyCode&&(t.inputState.lastEscPress=Date.now()),!1),_n.touchstart=(t,e)=>{t.inputState.lastTouchTime=Date.now(),t.inputState.setSelectionOrigin("select.pointer")},_n.touchmove=t=>{t.inputState.setSelectionOrigin("select.pointer")},An.mousedown=(t,e)=>{if(t.observer.flush(),t.inputState.lastTouchTime>Date.now()-2e3)return!1;let i=null;for(let n of t.state.facet(Ti))if(i=n(t,e),i)break;if(i||0!=e.button||(i=function(t,e){let i=qn(t,e),n=Nn(e),r=t.state.selection;return{update(t){t.docChanged&&(i.pos=t.changes.mapPos(i.pos),r=r.map(t.changes))},get(e,s,o){let a,l=qn(t,e),h=Rn(t,l.pos,l.bias,n);if(i.pos!=l.pos&&!s){let e=Rn(t,i.pos,i.bias,n),r=Math.min(e.from,h.from),s=Math.max(e.to,h.to);h=r1&&(a=function(t,e){for(let i=0;i=e)return _.create(t.ranges.slice(0,i).concat(t.ranges.slice(i+1)),t.mainIndex==i?0:t.mainIndex-(t.mainIndex>i?1:0))}return null}(r,l.pos))?a:o?r.addRange(h):_.create([h])}}}(t,e)),i){let n=!t.hasFocus;t.inputState.startMouseSelection(new Tn(t,e,i,n)),n&&t.observer.ignore((()=>fe(t.contentDOM)));let r=t.inputState.mouseSelection;if(r)return r.start(e),!1===r.dragging}return!1};let Vn=(t,e)=>t>=e.top&&t<=e.bottom,Yn=(t,e,i)=>Vn(e,i)&&t>=i.left&&t<=i.right;function Wn(t,e,i,n){let r=Fe.find(t.docView,e);if(!r)return 1;let s=e-r.posAtStart;if(0==s)return 1;if(s==r.length)return-1;let o=r.coordsAt(s,-1);if(o&&Yn(i,n,o))return-1;let a=r.coordsAt(s,1);return a&&Yn(i,n,a)?1:o&&Vn(n,o)?-1:1}function qn(t,e){let i=t.posAtCoords({x:e.clientX,y:e.clientY},!1);return{pos:i,bias:Wn(t,i,e.clientX,e.clientY)}}const In=Ye.ie&&Ye.ie_version<=11;let jn=null,Dn=0,Mn=0;function Nn(t){if(!In)return t.detail;let e=jn,i=Mn;return jn=t,Mn=Date.now(),Dn=!e||i>Date.now()-400&&Math.abs(e.clientX-t.clientX)<2&&Math.abs(e.clientY-t.clientY)<2?(Dn+1)%3:1}function zn(t,e,i,n){if(!i)return;let r=t.posAtCoords({x:e.clientX,y:e.clientY},!1),{draggedContent:s}=t.inputState,o=n&&s&&function(t,e){let i=t.state.facet(Ci);return i.length?i[0](e):Ye.mac?!e.altKey:!e.ctrlKey}(t,e)?{from:s.from,to:s.to}:null,a={from:r,insert:i},l=t.state.changes(o?[o,a]:a);t.focus(),t.dispatch({changes:l,selection:{anchor:l.mapPos(r,-1),head:l.mapPos(r,1)},userEvent:o?"move.drop":"input.drop"}),t.inputState.draggedContent=null}An.dragstart=(t,e)=>{let{selection:{main:i}}=t.state;if(e.target.draggable){let n=t.docView.nearest(e.target);if(n&&n.isWidget){let t=n.posAtStart,e=t+n.length;(t>=i.to||e<=i.from)&&(i=_.range(t,e))}}let{inputState:n}=t;return n.mouseSelection&&(n.mouseSelection.dragging=!0),n.draggedContent=i,e.dataTransfer&&(e.dataTransfer.setData("Text",t.state.sliceDoc(i.from,i.to)),e.dataTransfer.effectAllowed="copyMove"),!1},An.dragend=t=>(t.inputState.draggedContent=null,!1),An.drop=(t,e)=>{if(!e.dataTransfer)return!1;if(t.state.readOnly)return!0;let i=e.dataTransfer.files;if(i&&i.length){let n=Array(i.length),r=0,s=()=>{++r==i.length&&zn(t,e,n.filter((t=>null!=t)).join(t.state.lineBreak),!1)};for(let t=0;t{/[\x00-\x08\x0e-\x1f]{2}/.test(e.result)||(n[t]=e.result),s()},e.readAsText(i[t])}return!0}{let i=e.dataTransfer.getData("Text");if(i)return zn(t,e,i,!0),!0}return!1},An.paste=(t,e)=>{if(t.state.readOnly)return!0;t.observer.flush();let i=En?null:e.clipboardData;return i?(Xn(t,i.getData("text/plain")||i.getData("text/uri-text")),!0):(function(t){let e=t.dom.parentNode;if(!e)return;let i=e.appendChild(document.createElement("textarea"));i.style.cssText="position: fixed; left: -10000px; top: 10px",i.focus(),setTimeout((()=>{t.focus(),i.remove(),Xn(t,i.value)}),50)}(t),!1)};let Bn=null;An.copy=An.cut=(t,e)=>{let{text:i,ranges:n,linewise:r}=function(t){let e=[],i=[],n=!1;for(let n of t.selection.ranges)n.empty||(e.push(t.sliceDoc(n.from,n.to)),i.push(n));if(!e.length){let r=-1;for(let{from:n}of t.selection.ranges){let s=t.doc.lineAt(n);s.number>r&&(e.push(s.text),i.push({from:s.from,to:Math.min(t.doc.length,s.to+1)})),r=s.number}n=!0}return{text:e.join(t.lineBreak),ranges:i,linewise:n}}(t.state);if(!i&&!r)return!1;Bn=r?i:null,"cut"!=e.type||t.state.readOnly||t.dispatch({changes:n,scrollIntoView:!0,userEvent:"delete.cut"});let s=En?null:e.clipboardData;return s?(s.clearData(),s.setData("text/plain",i),!0):(function(t,e){let i=t.dom.parentNode;if(!i)return;let n=i.appendChild(document.createElement("textarea"));n.style.cssText="position: fixed; left: -10000px; top: 10px",n.value=e,n.focus(),n.selectionEnd=e.length,n.selectionStart=0,setTimeout((()=>{n.remove(),t.focus()}),50)}(t,i),!1)};const Ln=lt.define();function Gn(t,e){let i=[];for(let n of t.facet(Xi)){let r=n(t,e);r&&i.push(r)}return i?t.update({effects:i,annotations:Ln.of(!0)}):null}function Un(t){setTimeout((()=>{let e=t.hasFocus;if(e!=t.inputState.notifiedFocused){let i=Gn(t.state,e);i?t.dispatch(i):t.update([])}}),10)}_n.focus=t=>{t.inputState.lastFocusTime=Date.now(),t.scrollDOM.scrollTop||!t.inputState.lastScrollTop&&!t.inputState.lastScrollLeft||(t.scrollDOM.scrollTop=t.inputState.lastScrollTop,t.scrollDOM.scrollLeft=t.inputState.lastScrollLeft),Un(t)},_n.blur=t=>{t.observer.clearSelectionRange(),Un(t)},_n.compositionstart=_n.compositionupdate=t=>{null==t.inputState.compositionFirstChange&&(t.inputState.compositionFirstChange=!0),t.inputState.composing<0&&(t.inputState.composing=0,t.docView.maybeCreateCompositionBarrier()&&(t.update([]),t.docView.clearCompositionBarrier()))},_n.compositionend=t=>{t.inputState.composing=-1,t.inputState.compositionEndedAt=Date.now(),t.inputState.compositionPendingKey=!0,t.inputState.compositionPendingChange=t.observer.pendingRecords().length>0,t.inputState.compositionFirstChange=null,Ye.chrome&&Ye.android?t.observer.flushSoon():t.inputState.compositionPendingChange?Promise.resolve().then((()=>t.observer.flush())):setTimeout((()=>{t.inputState.composing<0&&t.docView.hasComposition&&t.update([])}),50)},_n.contextmenu=t=>{t.inputState.lastContextMenu=Date.now()},An.beforeinput=(t,e)=>{var i;let n;if(Ye.chrome&&Ye.android&&(n=Pn.find((t=>t.inputType==e.inputType)))&&(t.observer.delayAndroidKey(n.key,n.keyCode),"Backspace"==n.key||"Delete"==n.key)){let e=(null===(i=window.visualViewport)||void 0===i?void 0:i.height)||0;setTimeout((()=>{var i;((null===(i=window.visualViewport)||void 0===i?void 0:i.height)||0)>e+10&&t.hasFocus&&(t.contentDOM.blur(),t.focus())}),100)}return!1};const Fn=new Set;const Hn=["pre-wrap","normal","pre-line","break-spaces"];class Kn{constructor(e){this.lineWrapping=e,this.doc=t.empty,this.heightSamples={},this.lineHeight=14,this.charWidth=7,this.textHeight=14,this.lineLength=30,this.heightChanged=!1}heightForGap(t,e){let i=this.doc.lineAt(e).number-this.doc.lineAt(t).number+1;return this.lineWrapping&&(i+=Math.max(0,Math.ceil((e-t-i*this.lineLength*.5)/this.lineLength))),this.lineHeight*i}heightForLine(t){if(!this.lineWrapping)return this.lineHeight;return(1+Math.max(0,Math.ceil((t-this.lineLength)/(this.lineLength-5))))*this.lineHeight}setDoc(t){return this.doc=t,this}mustRefreshForWrapping(t){return Hn.indexOf(t)>-1!=this.lineWrapping}mustRefreshForHeights(t){let e=!1;for(let i=0;i-1,a=Math.round(e)!=Math.round(this.lineHeight)||this.lineWrapping!=o;if(this.lineWrapping=o,this.lineHeight=e,this.charWidth=i,this.textHeight=n,this.lineLength=r,a){this.heightSamples={};for(let t=0;t0}set outdated(t){this.flags=(t?2:0)|-3&this.flags}setHeight(t,e){this.height!=e&&(Math.abs(this.height-e)>ir&&(t.heightChanged=!0),this.height=e)}replace(t,e,i){return nr.of(i)}decomposeLeft(t,e){e.push(this)}decomposeRight(t,e){e.push(this)}applyChanges(t,e,i,n){let r=this,s=i.doc;for(let o=n.length-1;o>=0;o--){let{fromA:a,toA:l,fromB:h,toB:c}=n[o],u=r.lineAt(a,er.ByPosNoHeight,i.setDoc(e),0,0),p=u.to>=l?u:r.lineAt(l,er.ByPosNoHeight,i,0,0);for(c+=p.to-l,l=p.to;o>0&&u.from<=n[o-1].toA;)a=n[o-1].fromA,h=n[o-1].fromB,o--,a2*r){let r=t[e-1];r.break?t.splice(--e,1,r.left,null,r.right):t.splice(--e,1,r.left,r.right),i+=1+r.break,n-=r.size}else{if(!(r>2*n))break;{let e=t[i];e.break?t.splice(i,1,e.left,null,e.right):t.splice(i,1,e.left,e.right),i+=2+e.break,r-=e.size}}else if(n=r&&s(this.blockAt(0,i,n,r))}updateHeight(t,e=0,i=!1,n){return n&&n.from<=e&&n.more&&this.setHeight(t,n.heights[n.index++]),this.outdated=!1,this}toString(){return`block(${this.length})`}}class sr extends rr{constructor(t,e){super(t,e,null),this.collapsed=0,this.widgetHeight=0,this.breaks=0}blockAt(t,e,i,n){return new tr(n,this.length,i,this.height,this.breaks)}replace(t,e,i){let n=i[0];return 1==i.length&&(n instanceof sr||n instanceof or&&4&n.flags)&&Math.abs(this.length-n.length)<10?(n instanceof or?n=new sr(n.length,this.height):n.height=this.height,this.outdated||(n.outdated=!1),n):nr.of(i)}updateHeight(t,e=0,i=!1,n){return n&&n.from<=e&&n.more?this.setHeight(t,n.heights[n.index++]):(i||this.outdated)&&this.setHeight(t,Math.max(this.widgetHeight,t.heightForLine(this.length-this.collapsed))+this.breaks*t.lineHeight),this.outdated=!1,this}toString(){return`line(${this.length}${this.collapsed?-this.collapsed:""}${this.widgetHeight?":"+this.widgetHeight:""})`}}class or extends nr{constructor(t){super(t,0)}heightMetrics(t,e){let i,n=t.doc.lineAt(e).number,r=t.doc.lineAt(e+this.length).number,s=r-n+1,o=0;if(t.lineWrapping){let e=Math.min(this.height,t.lineHeight*s);i=e/s,this.length>s+1&&(o=(this.height-e)/(this.length-s-1))}else i=this.height/s;return{firstLine:n,lastLine:r,perLine:i,perChar:o}}blockAt(t,e,i,n){let{firstLine:r,lastLine:s,perLine:o,perChar:a}=this.heightMetrics(e,n);if(e.lineWrapping){let r=n+Math.round(Math.max(0,Math.min(1,(t-i)/this.height))*this.length),s=e.doc.lineAt(r),l=o+s.length*a,h=Math.max(i,t-l/2);return new tr(s.from,s.length,h,l,0)}{let n=Math.max(0,Math.min(s-r,Math.floor((t-i)/o))),{from:a,length:l}=e.doc.line(r+n);return new tr(a,l,i+o*n,o,0)}}lineAt(t,e,i,n,r){if(e==er.ByHeight)return this.blockAt(t,i,n,r);if(e==er.ByPosNoHeight){let{from:e,to:n}=i.doc.lineAt(t);return new tr(e,n-e,0,0,0)}let{firstLine:s,perLine:o,perChar:a}=this.heightMetrics(i,r),l=i.doc.lineAt(t),h=o+l.length*a,c=l.number-s,u=n+o*c+a*(l.from-r-c);return new tr(l.from,l.length,Math.max(n,Math.min(u,n+this.height-h)),h,0)}forEachLine(t,e,i,n,r,s){t=Math.max(t,r),e=Math.min(e,r+this.length);let{firstLine:o,perLine:a,perChar:l}=this.heightMetrics(i,r);for(let h=t,c=n;h<=e;){let e=i.doc.lineAt(h);if(h==t){let i=e.number-o;c+=a*i+l*(t-r-i)}let n=a+l*e.length;s(new tr(e.from,e.length,c,n,0)),c+=n,h=e.to+1}}replace(t,e,i){let n=this.length-e;if(n>0){let t=i[i.length-1];t instanceof or?i[i.length-1]=new or(t.length+n):i.push(null,new or(n-1))}if(t>0){let e=i[0];e instanceof or?i[0]=new or(t+e.length):i.unshift(new or(t-1),null)}return nr.of(i)}decomposeLeft(t,e){e.push(new or(t-1),null)}decomposeRight(t,e){e.push(null,new or(this.length-t-1))}updateHeight(t,e=0,i=!1,n){let r=e+this.length;if(n&&n.from<=e+this.length&&n.more){let i=[],s=Math.max(e,n.from),o=-1;for(n.from>e&&i.push(new or(n.from-e-1).updateHeight(t,e));s<=r&&n.more;){let e=t.doc.lineAt(s).length;i.length&&i.push(null);let r=n.heights[n.index++];-1==o?o=r:Math.abs(r-o)>=ir&&(o=-2);let a=new sr(e,r);a.outdated=!1,i.push(a),s+=e+1}s<=r&&i.push(null,new or(r-s).updateHeight(t,s));let a=nr.of(i);return(o<0||Math.abs(a.height-this.height)>=ir||Math.abs(o-this.heightMetrics(t,e).perLine)>=ir)&&(t.heightChanged=!0),a}return(i||this.outdated)&&(this.setHeight(t,t.heightForGap(e,e+this.length)),this.outdated=!1),this}toString(){return`gap(${this.length})`}}class ar extends nr{constructor(t,e,i){super(t.length+e+i.length,t.height+i.height,e|(t.outdated||i.outdated?2:0)),this.left=t,this.right=i,this.size=t.size+i.size}get break(){return 1&this.flags}blockAt(t,e,i,n){let r=i+this.left.height;return to))return l;let h=e==er.ByPosNoHeight?er.ByPosNoHeight:er.ByPos;return a?l.join(this.right.lineAt(o,h,i,s,o)):this.left.lineAt(o,h,i,n,r).join(l)}forEachLine(t,e,i,n,r,s){let o=n+this.left.height,a=r+this.left.length+this.break;if(this.break)t=a&&this.right.forEachLine(t,e,i,o,a,s);else{let l=this.lineAt(a,er.ByPos,i,n,r);t=t&&l.from<=e&&s(l),e>l.to&&this.right.forEachLine(l.to+1,e,i,o,a,s)}}replace(t,e,i){let n=this.left.length+this.break;if(ethis.left.length)return this.balanced(this.left,this.right.replace(t-n,e-n,i));let r=[];t>0&&this.decomposeLeft(t,r);let s=r.length;for(let t of i)r.push(t);if(t>0&&lr(r,s-1),e=i&&e.push(null)),t>i&&this.right.decomposeLeft(t-i,e)}decomposeRight(t,e){let i=this.left.length,n=i+this.break;if(t>=n)return this.right.decomposeRight(t-n,e);t2*e.size||e.size>2*t.size?nr.of(this.break?[t,null,e]:[t,e]):(this.left=t,this.right=e,this.height=t.height+e.height,this.outdated=t.outdated||e.outdated,this.size=t.size+e.size,this.length=t.length+this.break+e.length,this)}updateHeight(t,e=0,i=!1,n){let{left:r,right:s}=this,o=e+r.length+this.break,a=null;return n&&n.from<=e+r.length&&n.more?a=r=r.updateHeight(t,e,i,n):r.updateHeight(t,e,i),n&&n.from<=o+s.length&&n.more?a=s=s.updateHeight(t,o,i,n):s.updateHeight(t,o,i),a?this.balanced(r,s):(this.height=this.left.height+this.right.height,this.outdated=!1,this)}toString(){return this.left+(this.break?" ":"-")+this.right}}function lr(t,e){let i,n;null==t[e]&&(i=t[e-1])instanceof or&&(n=t[e+1])instanceof or&&t.splice(e-1,3,new or(i.length+1+n.length))}class hr{constructor(t,e){this.pos=t,this.oracle=e,this.nodes=[],this.lineStart=-1,this.lineEnd=-1,this.covering=null,this.writtenTo=t}get isCovered(){return this.covering&&this.nodes[this.nodes.length-1]==this.covering}span(t,e){if(this.lineStart>-1){let t=Math.min(e,this.lineEnd),i=this.nodes[this.nodes.length-1];i instanceof sr?i.length+=t-this.pos:(t>this.pos||!this.isCovered)&&this.nodes.push(new sr(t-this.pos,-1)),this.writtenTo=t,e>t&&(this.nodes.push(null),this.writtenTo++,this.lineStart=-1)}this.pos=e}point(t,e,i){if(t=5)&&this.addLineDeco(n,r,s)}else e>t&&this.span(t,e);this.lineEnd>-1&&this.lineEnd-1)return;let{from:t,to:e}=this.oracle.doc.lineAt(this.pos);this.lineStart=t,this.lineEnd=e,this.writtenTot&&this.nodes.push(new sr(this.pos-t,-1)),this.writtenTo=this.pos}blankContent(t,e){let i=new or(e-t);return this.oracle.doc.lineAt(t).to==e&&(i.flags|=4),i}ensureLine(){this.enterLine();let t=this.nodes.length?this.nodes[this.nodes.length-1]:null;if(t instanceof sr)return t;let e=new sr(0,-1);return this.nodes.push(e),e}addBlock(t){this.enterLine();let e=t.deco;e&&e.startSide>0&&!this.isCovered&&this.ensureLine(),this.nodes.push(t),this.writtenTo=this.pos=this.pos+t.length,e&&e.endSide>0&&(this.covering=t)}addLineDeco(t,e,i){let n=this.ensureLine();n.length+=i,n.collapsed+=i,n.widgetHeight=Math.max(n.widgetHeight,t),n.breaks+=e,this.writtenTo=this.pos=this.pos+i}finish(t){let e=0==this.nodes.length?null:this.nodes[this.nodes.length-1];!(this.lineStart>-1)||e instanceof sr||this.isCovered?(this.writtenToi.clientHeight||i.scrollWidth>i.clientWidth)&&"visible"!=n.overflow){let n=i.getBoundingClientRect();s=Math.max(s,n.left),o=Math.min(o,n.right),a=Math.max(a,n.top),l=e==t.parentNode?n.bottom:Math.min(l,n.bottom)}e="absolute"==n.position||"fixed"==n.position?i.offsetParent:i.parentNode}else{if(11!=e.nodeType)break;e=e.host}return{left:s-i.left,right:Math.max(s,o)-i.left,top:a-(i.top+e),bottom:Math.max(a,l)-(i.top+e)}}function pr(t,e){let i=t.getBoundingClientRect();return{left:0,right:i.right-i.left,top:e,bottom:i.bottom-(i.top+e)}}class dr{constructor(t,e,i){this.from=t,this.to=e,this.size=i}static same(t,e){if(t.length!=e.length)return!1;for(let i=0;i"function"!=typeof t&&"cm-lineWrapping"==t.class));this.heightOracle=new Kn(i),this.stateDeco=e.facet(Li).filter((t=>"function"!=typeof t)),this.heightMap=nr.empty().applyChanges(this.stateDeco,t.empty,this.heightOracle.setDoc(e.doc),[new en(0,0,0,e.doc.length)]),this.viewport=this.getViewport(0,null),this.updateViewportLines(),this.updateForViewport(),this.lineGaps=this.ensureLineGaps([]),this.lineGapDeco=ti.set(this.lineGaps.map((t=>t.draw(this,!1)))),this.computeVisibleRanges()}updateForViewport(){let t=[this.viewport],{main:e}=this.state.selection;for(let i=0;i<=1;i++){let n=i?e.head:e.anchor;if(!t.some((({from:t,to:e})=>n>=t&&n<=e))){let{from:e,to:i}=this.lineBlockAt(n);t.push(new mr(e,i))}}this.viewports=t.sort(((t,e)=>t.from-e.from)),this.scaler=this.heightMap.height<=7e6?Sr:new wr(this.heightOracle,this.heightMap,this.viewports)}updateViewportLines(){this.viewportLines=[],this.heightMap.forEachLine(this.viewport.from,this.viewport.to,this.heightOracle.setDoc(this.state.doc),0,0,(t=>{this.viewportLines.push(1==this.scaler.scale?t:br(t,this.scaler))}))}update(t,e=null){this.state=t.state;let i=this.stateDeco;this.stateDeco=this.state.facet(Li).filter((t=>"function"!=typeof t));let n=t.changedRanges,r=en.extendWithRanges(n,function(t,e,i){let n=new cr;return Ct.compare(t,e,i,n,0),n.changes}(i,this.stateDeco,t?t.changes:k.empty(this.state.doc.length))),s=this.heightMap.height,o=this.scrolledToBottom?null:this.scrollAnchorAt(this.scrollTop);this.heightMap=this.heightMap.applyChanges(this.stateDeco,t.startState.doc,this.heightOracle.setDoc(this.state.doc),r),this.heightMap.height!=s&&(t.flags|=2),o?(this.scrollAnchorPos=t.changes.mapPos(o.from,-1),this.scrollAnchorHeight=o.top):(this.scrollAnchorPos=-1,this.scrollAnchorHeight=this.heightMap.height);let a=r.length?this.mapViewport(this.viewport,t.changes):this.viewport;(e&&(e.range.heada.to)||!this.viewportIsAppropriate(a))&&(a=this.getViewport(0,e));let l=!t.changes.empty||2&t.flags||a.from!=this.viewport.from||a.to!=this.viewport.to;this.viewport=a,this.updateForViewport(),l&&this.updateViewportLines(),(this.lineGaps.length||this.viewport.to-this.viewport.from>4e3)&&this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps,t.changes))),t.flags|=this.computeVisibleRanges(),e&&(this.scrollTarget=e),!this.mustEnforceCursorAssoc&&t.selectionSet&&t.view.lineWrapping&&t.state.selection.main.empty&&t.state.selection.main.assoc&&!t.state.facet(Vi)&&(this.mustEnforceCursorAssoc=!0)}measure(e){let i=e.contentDOM,n=window.getComputedStyle(i),r=this.heightOracle,s=n.whiteSpace;this.defaultTextDirection="rtl"==n.direction?hi.RTL:hi.LTR;let o=this.heightOracle.mustRefreshForWrapping(s),a=i.getBoundingClientRect(),l=o||this.mustMeasureContent||this.contentDOMHeight!=a.height;this.contentDOMHeight=a.height,this.mustMeasureContent=!1;let h=0,c=0;if(a.width&&a.height){let{scaleX:t,scaleY:e}=ce(i,a);this.scaleX==t&&this.scaleY==e||(this.scaleX=t,this.scaleY=e,h|=8,o=l=!0)}let u=(parseInt(n.paddingTop)||0)*this.scaleY,p=(parseInt(n.paddingBottom)||0)*this.scaleY;this.paddingTop==u&&this.paddingBottom==p||(this.paddingTop=u,this.paddingBottom=p,h|=10),this.editorWidth!=e.scrollDOM.clientWidth&&(r.lineWrapping&&(l=!0),this.editorWidth=e.scrollDOM.clientWidth,h|=8);let d=e.scrollDOM.scrollTop*this.scaleY;this.scrollTop!=d&&(this.scrollAnchorHeight=-1,this.scrollTop=d),this.scrolledToBottom=ye(e.scrollDOM);let f=(this.printing?pr:ur)(i,this.paddingTop),O=f.top-this.pixelViewport.top,m=f.bottom-this.pixelViewport.bottom;this.pixelViewport=f;let g=this.pixelViewport.bottom>this.pixelViewport.top&&this.pixelViewport.right>this.pixelViewport.left;if(g!=this.inView&&(this.inView=g,g&&(l=!0)),!this.inView&&!this.scrollTarget)return 0;let y=a.width;if(this.contentDOMWidth==y&&this.editorHeight==e.scrollDOM.clientHeight||(this.contentDOMWidth=a.width,this.editorHeight=e.scrollDOM.clientHeight,h|=8),l){let i=e.docView.measureVisibleLineHeights(this.viewport);if(r.mustRefreshForHeights(i)&&(o=!0),o||r.lineWrapping&&Math.abs(y-this.contentDOMWidth)>r.charWidth){let{lineHeight:t,charWidth:n,textHeight:a}=e.docView.measureTextSize();o=t>0&&r.refresh(s,t,n,a,y/n,i),o&&(e.docView.minWidth=0,h|=8)}O>0&&m>0?c=Math.max(O,m):O<0&&m<0&&(c=Math.min(O,m)),r.heightChanged=!1;for(let n of this.viewports){let s=n.from==this.viewport.from?i:e.docView.measureVisibleLineHeights(n);this.heightMap=(o?nr.empty().applyChanges(this.stateDeco,t.empty,this.heightOracle,[new en(0,0,0,e.state.doc.length)]):this.heightMap).updateHeight(r,0,o,new Jn(n.from,s))}r.heightChanged&&(h|=2)}let x=!this.viewportIsAppropriate(this.viewport,c)||this.scrollTarget&&(this.scrollTarget.range.headthis.viewport.to);return x&&(this.viewport=this.getViewport(c,this.scrollTarget)),this.updateForViewport(),(2&h||x)&&this.updateViewportLines(),(this.lineGaps.length||this.viewport.to-this.viewport.from>4e3)&&this.updateLineGaps(this.ensureLineGaps(o?[]:this.lineGaps,e)),h|=this.computeVisibleRanges(),this.mustEnforceCursorAssoc&&(this.mustEnforceCursorAssoc=!1,e.docView.enforceCursorAssoc()),h}get visibleTop(){return this.scaler.fromDOM(this.pixelViewport.top)}get visibleBottom(){return this.scaler.fromDOM(this.pixelViewport.bottom)}getViewport(t,e){let i=.5-Math.max(-.5,Math.min(.5,t/1e3/2)),n=this.heightMap,r=this.heightOracle,{visibleTop:s,visibleBottom:o}=this,a=new mr(n.lineAt(s-1e3*i,er.ByHeight,r,0,0).from,n.lineAt(o+1e3*(1-i),er.ByHeight,r,0,0).to);if(e){let{head:t}=e.range;if(ta.to){let i,s=Math.min(this.editorHeight,this.pixelViewport.bottom-this.pixelViewport.top),o=n.lineAt(t,er.ByPos,r,0,0);i="center"==e.y?(o.top+o.bottom)/2-s/2:"start"==e.y||"nearest"==e.y&&t=o+Math.max(10,Math.min(i,250)))&&n>s-2e3&&r>1,s=n<<1;if(this.defaultTextDirection!=hi.LTR&&!i)return[];let o=[],a=(n,s,l,h)=>{if(s-nn&&tt.from>=l.from&&t.to<=l.to&&Math.abs(t.from-n)t.frome))));if(!p){if(st.from<=s&&t.to>=s))){let t=e.moveToLineBoundary(_.cursor(s),!1,!0).head;t>n&&(s=t)}p=new dr(n,s,this.gapSize(l,n,s,h))}o.push(p)};for(let t of this.viewportLines){if(t.lengtht.from&&a(t.from,r,t,e),ot.draw(this,this.heightOracle.lineWrapping)))))}computeVisibleRanges(){let t=this.stateDeco;this.lineGaps.length&&(t=t.concat(this.lineGapDeco));let e=[];Ct.spans(t,this.viewport.from,this.viewport.to,{span(t,i){e.push({from:t,to:i})},point(){}},20);let i=e.length!=this.visibleRanges.length||this.visibleRanges.some(((t,i)=>t.from!=e[i].from||t.to!=e[i].to));return this.visibleRanges=e,i?4:0}lineBlockAt(t){return t>=this.viewport.from&&t<=this.viewport.to&&this.viewportLines.find((e=>e.from<=t&&e.to>=t))||br(this.heightMap.lineAt(t,er.ByPos,this.heightOracle,0,0),this.scaler)}lineBlockAtHeight(t){return br(this.heightMap.lineAt(this.scaler.fromDOM(t),er.ByHeight,this.heightOracle,0,0),this.scaler)}scrollAnchorAt(t){let e=this.lineBlockAtHeight(t+8);return e.from>=this.viewport.from||this.viewportLines[0].top-t>200?e:this.viewportLines[0]}elementAtHeight(t){return br(this.heightMap.blockAt(this.scaler.fromDOM(t),this.heightOracle,0,0),this.scaler)}get docHeight(){return this.scaler.toDOM(this.heightMap.height)}get contentHeight(){return this.docHeight+this.paddingTop+this.paddingBottom}}class mr{constructor(t,e){this.from=t,this.to=e}}function gr(t,e,i){let n=[],r=t,s=0;return Ct.spans(i,t,e,{span(){},point(t,e){t>r&&(n.push({from:r,to:t}),s+=t-r),r=e}},20),r=1)return e[e.length-1].to;let n=Math.floor(t*i);for(let t=0;;t++){let{from:i,to:r}=e[t],s=r-i;if(n<=s)return i+n;n-=s}}function xr(t,e){let i=0;for(let{from:n,to:r}of t.ranges){if(e<=r){i+=e-n;break}i+=r-n}return i/t.total}const Sr={toDOM:t=>t,fromDOM:t=>t,scale:1};class wr{constructor(t,e,i){let n=0,r=0,s=0;this.viewports=i.map((({from:i,to:r})=>{let s=e.lineAt(i,er.ByPos,t,0,0).top,o=e.lineAt(r,er.ByPos,t,0,0).bottom;return n+=o-s,{from:i,to:r,top:s,bottom:o,domTop:0,domBottom:0}})),this.scale=(7e6-n)/(e.height-n);for(let t of this.viewports)t.domTop=s+(t.top-r)*this.scale,s=t.domBottom=t.domTop+(t.bottom-t.top),r=t.bottom}toDOM(t){for(let e=0,i=0,n=0;;e++){let r=ebr(t,e))):t._content)}const vr=R.define({combine:t=>t.join(" ")}),kr=R.define({combine:t=>t.indexOf(!0)>-1}),Qr=zt.newName(),Pr=zt.newName(),$r=zt.newName(),Zr={"&light":"."+Pr,"&dark":"."+$r};function Cr(t,e,i){return new zt(e,{finish:e=>/&/.test(e)?e.replace(/&\w*/,(e=>{if("&"==e)return t;if(!i||!i[e])throw new RangeError(`Unsupported selector: ${e}`);return i[e]})):t+" "+e})}const Tr=Cr("."+Qr,{"&":{position:"relative !important",boxSizing:"border-box","&.cm-focused":{outline:"1px dotted #212121"},display:"flex !important",flexDirection:"column"},".cm-scroller":{display:"flex !important",alignItems:"flex-start !important",fontFamily:"monospace",lineHeight:1.4,height:"100%",overflowX:"auto",position:"relative",zIndex:0},".cm-content":{margin:0,flexGrow:2,flexShrink:0,display:"block",whiteSpace:"pre",wordWrap:"normal",boxSizing:"border-box",minHeight:"100%",padding:"4px 0",outline:"none","&[contenteditable=true]":{WebkitUserModify:"read-write-plaintext-only"}},".cm-lineWrapping":{whiteSpace_fallback:"pre-wrap",whiteSpace:"break-spaces",wordBreak:"break-word",overflowWrap:"anywhere",flexShrink:1},"&light .cm-content":{caretColor:"black"},"&dark .cm-content":{caretColor:"white"},".cm-line":{display:"block",padding:"0 2px 0 6px"},".cm-layer":{position:"absolute",left:0,top:0,contain:"size style","& > *":{position:"absolute"}},"&light .cm-selectionBackground":{background:"#d9d9d9"},"&dark .cm-selectionBackground":{background:"#222"},"&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground":{background:"#d7d4f0"},"&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground":{background:"#233"},".cm-cursorLayer":{pointerEvents:"none"},"&.cm-focused > .cm-scroller > .cm-cursorLayer":{animation:"steps(1) cm-blink 1.2s infinite"},"@keyframes cm-blink":{"0%":{},"50%":{opacity:0},"100%":{}},"@keyframes cm-blink2":{"0%":{},"50%":{opacity:0},"100%":{}},".cm-cursor, .cm-dropCursor":{borderLeft:"1.2px solid black",marginLeft:"-0.6px",pointerEvents:"none"},".cm-cursor":{display:"none"},"&dark .cm-cursor":{borderLeftColor:"#444"},".cm-dropCursor":{position:"absolute"},"&.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor":{display:"block"},".cm-iso":{unicodeBidi:"isolate"},".cm-announced":{position:"fixed",top:"-10000px"},"@media print":{".cm-announced":{display:"none"}},"&light .cm-activeLine":{backgroundColor:"#cceeff44"},"&dark .cm-activeLine":{backgroundColor:"#99eeff33"},"&light .cm-specialChar":{color:"red"},"&dark .cm-specialChar":{color:"#f78"},".cm-gutters":{flexShrink:0,display:"flex",height:"100%",boxSizing:"border-box",insetInlineStart:0,zIndex:200},"&light .cm-gutters":{backgroundColor:"#f5f5f5",color:"#6c6c6c",borderRight:"1px solid #ddd"},"&dark .cm-gutters":{backgroundColor:"#333338",color:"#ccc"},".cm-gutter":{display:"flex !important",flexDirection:"column",flexShrink:0,boxSizing:"border-box",minHeight:"100%",overflow:"hidden"},".cm-gutterElement":{boxSizing:"border-box"},".cm-lineNumbers .cm-gutterElement":{padding:"0 3px 0 5px",minWidth:"20px",textAlign:"right",whiteSpace:"nowrap"},"&light .cm-activeLineGutter":{backgroundColor:"#e2f2ff"},"&dark .cm-activeLineGutter":{backgroundColor:"#222227"},".cm-panels":{boxSizing:"border-box",position:"sticky",left:0,right:0},"&light .cm-panels":{backgroundColor:"#f5f5f5",color:"black"},"&light .cm-panels-top":{borderBottom:"1px solid #ddd"},"&light .cm-panels-bottom":{borderTop:"1px solid #ddd"},"&dark .cm-panels":{backgroundColor:"#333338",color:"white"},".cm-tab":{display:"inline-block",overflow:"hidden",verticalAlign:"bottom"},".cm-widgetBuffer":{verticalAlign:"text-top",height:"1em",width:0,display:"inline"},".cm-placeholder":{color:"#888",display:"inline-block",verticalAlign:"top"},".cm-highlightSpace:before":{content:"attr(data-display)",position:"absolute",pointerEvents:"none",color:"#888"},".cm-highlightTab":{backgroundImage:'url(\'data:image/svg+xml,\')',backgroundSize:"auto 100%",backgroundPosition:"right 90%",backgroundRepeat:"no-repeat"},".cm-trailingSpace":{backgroundColor:"#ff332255"},".cm-button":{verticalAlign:"middle",color:"inherit",fontSize:"70%",padding:".2em 1em",borderRadius:"1px"},"&light .cm-button":{backgroundImage:"linear-gradient(#eff1f5, #d9d9df)",border:"1px solid #888","&:active":{backgroundImage:"linear-gradient(#b4b4b4, #d0d3d6)"}},"&dark .cm-button":{backgroundImage:"linear-gradient(#393939, #111)",border:"1px solid #888","&:active":{backgroundImage:"linear-gradient(#111, #333)"}},".cm-textfield":{verticalAlign:"middle",color:"inherit",fontSize:"70%",border:"1px solid silver",padding:".2em .5em"},"&light .cm-textfield":{backgroundColor:"white"},"&dark .cm-textfield":{border:"1px solid #555",backgroundColor:"inherit"}},Zr),Ar="￿";class _r{constructor(t,e){this.points=t,this.text="",this.lineSeparator=e.facet(vt.lineSeparator)}append(t){this.text+=t}lineBreak(){this.text+=Ar}readRange(t,e){if(!t)return this;let i=t.parentNode;for(let n=t;;){this.findPointBefore(i,n);let t=this.text.length;this.readNode(n);let r=n.nextSibling;if(r==e)break;let s=we.get(n),o=we.get(r);(s&&o?s.breakAfter:(s?s.breakAfter:Xr(n))||Xr(r)&&("BR"!=n.nodeName||n.cmIgnore)&&this.text.length>t)&&this.lineBreak(),n=r}return this.findPointBefore(i,e),this}readTextNode(t){let e=t.nodeValue;for(let i of this.points)i.node==t&&(i.pos=this.text.length+Math.min(i.offset,e.length));for(let i=0,n=this.lineSeparator?null:/\r\n?|\n/g;;){let r,s=-1,o=1;if(this.lineSeparator?(s=e.indexOf(this.lineSeparator,i),o=this.lineSeparator.length):(r=n.exec(e))&&(s=r.index,o=r[0].length),this.append(e.slice(i,s<0?e.length:s)),s<0)break;if(this.lineBreak(),o>1)for(let e of this.points)e.node==t&&e.pos>this.text.length&&(e.pos-=o-1);i=s+o}}readNode(t){if(t.cmIgnore)return;let e=we.get(t),i=e&&e.overrideDOMText;if(null!=i){this.findPointInside(t,i.length);for(let t=i.iter();!t.next().done;)t.lineBreak?this.lineBreak():this.append(t.value)}else 3==t.nodeType?this.readTextNode(t):"BR"==t.nodeName?t.nextSibling&&this.lineBreak():1==t.nodeType&&this.readRange(t.firstChild,null)}findPointBefore(t,e){for(let i of this.points)i.node==t&&t.childNodes[i.offset]==e&&(i.pos=this.text.length)}findPointInside(t,e){for(let i of this.points)(3==t.nodeType?i.node==t:t.contains(i.node))&&(i.pos=this.text.length+(Er(t,i.node,i.offset)?e:0))}}function Er(t,e,i){for(;;){if(!e||i-1)this.newSel=null;else if(e>-1&&(this.bounds=t.docView.domBoundsAround(e,i,0))){let e=r||s?[]:function(t){let e=[];if(t.root.activeElement!=t.contentDOM)return e;let{anchorNode:i,anchorOffset:n,focusNode:r,focusOffset:s}=t.observer.selectionRange;i&&(e.push(new Rr(i,n)),r==i&&s==n||e.push(new Rr(r,s)));return e}(t),i=new _r(e,t.state);i.readRange(this.bounds.startDOM,this.bounds.endDOM),this.text=i.text,this.newSel=function(t,e){if(0==t.length)return null;let i=t[0].pos,n=2==t.length?t[1].pos:i;return i>-1&&n>-1?_.single(i+e,n+e):null}(e,this.bounds.from)}else{let e=t.observer.selectionRange,i=r&&r.node==e.focusNode&&r.offset==e.focusOffset||!ee(t.contentDOM,e.focusNode)?t.state.selection.main.head:t.docView.posFromDOM(e.focusNode,e.focusOffset),n=s&&s.node==e.anchorNode&&s.offset==e.anchorOffset||!ee(t.contentDOM,e.anchorNode)?t.state.selection.main.anchor:t.docView.posFromDOM(e.anchorNode,e.anchorOffset),o=t.viewport;if((Ye.ios||Ye.chrome)&&t.state.selection.main.empty&&i!=n&&(o.from>0||o.toDate.now()-100?e.inputState.lastKeyCode:-1;if(i.bounds){let{from:r,to:a}=i.bounds,l=s.from,h=null;(8===o||Ye.android&&i.text.length0&&a>0&&t.charCodeAt(o-1)==e.charCodeAt(a-1);)o--,a--;if("end"==n){i-=o+Math.max(0,s-Math.min(o,a))-s}if(o=o?s-i:0,a=s+(a-o),o=s}else if(a=a?s-i:0,o=s+(o-a),a=s}return{from:s,toA:o,toB:a}}(e.state.doc.sliceString(r,a,Ar),i.text,l-r,h);c&&(Ye.chrome&&13==o&&c.toB==c.from+2&&i.text.slice(c.from,c.toB)==Ar+Ar&&c.toB--,n={from:r+c.from,to:r+c.toA,insert:t.of(i.text.slice(c.from,c.toB).split(Ar))})}else r&&(!e.hasFocus&&e.state.facet(Ii)||r.main.eq(s))&&(r=null);if(!n&&!r)return!1;if(!n&&i.typeOver&&!s.empty&&r&&r.main.empty?n={from:s.from,to:s.to,insert:e.state.doc.slice(s.from,s.to)}:n&&n.from>=s.from&&n.to<=s.to&&(n.from!=s.from||n.to!=s.to)&&s.to-s.from-(n.to-n.from)<=4?n={from:s.from,to:s.to,insert:e.state.doc.slice(s.from,n.from).append(n.insert).append(e.state.doc.slice(n.to,s.to))}:(Ye.mac||Ye.android)&&n&&n.from==n.to&&n.from==s.head-1&&/^\. ?$/.test(n.insert.toString())&&"off"==e.contentDOM.getAttribute("autocorrect")?(r&&2==n.insert.length&&(r=_.single(r.main.anchor-1,r.main.head-1)),n={from:s.from,to:s.to,insert:t.of([" "])}):Ye.chrome&&n&&n.from==n.to&&n.from==s.head&&"\n "==n.insert.toString()&&e.lineWrapping&&(r&&(r=_.single(r.main.anchor-1,r.main.head-1)),n={from:s.from,to:s.to,insert:t.of([" "])}),n){if(Ye.ios&&e.inputState.flushIOSKey())return!0;if(Ye.android&&(n.to==s.to&&(n.from==s.from||n.from==s.from-1&&" "==e.state.sliceDoc(n.from,s.from))&&1==n.insert.length&&2==n.insert.lines&&me(e.contentDOM,"Enter",13)||(n.from==s.from-1&&n.to==s.to&&0==n.insert.length||8==o&&n.insert.lengths.head)&&me(e.contentDOM,"Backspace",8)||n.from==s.from&&n.to==s.to+1&&0==n.insert.length&&me(e.contentDOM,"Delete",46)))return!0;let t,i=n.insert.toString();e.inputState.composing>=0&&e.inputState.composing++;let a=()=>t||(t=function(t,e,i){let n,r=t.state,s=r.selection.main;if(e.from>=s.from&&e.to<=s.to&&e.to-e.from>=(s.to-s.from)/3&&(!i||i.main.empty&&i.main.from==e.from+e.insert.length)&&t.inputState.composing<0){let i=s.frome.to?r.sliceDoc(e.to,s.to):"";n=r.replaceSelection(t.state.toText(i+e.insert.sliceString(0,void 0,t.state.lineBreak)+o))}else{let o=r.changes(e),a=i&&i.main.to<=o.newLength?i.main:void 0;if(r.selection.ranges.length>1&&t.inputState.composing>=0&&e.to<=s.to&&e.to>=s.to-10){let l,h=t.state.sliceDoc(e.from,e.to),c=i&&an(t,i.main.head);if(c){let t=e.insert.length-(e.to-e.from);l={from:c.from,to:c.to-t}}else l=t.state.doc.lineAt(s.head);let u=s.to-e.to,p=s.to-s.from;n=r.changeByRange((i=>{if(i.from==s.from&&i.to==s.to)return{changes:o,range:a||i.map(o)};let n=i.to-u,c=n-h.length;if(i.to-i.from!=p||t.state.sliceDoc(c,n)!=h||i.to>=l.from&&i.from<=l.to)return{range:i};let d=r.changes({from:c,to:n,insert:e.insert}),f=i.to-s.to;return{changes:d,range:a?_.range(Math.max(0,a.anchor+f),Math.max(0,a.head+f)):i.map(d)}}))}else n={changes:o,selection:a&&r.selection.replaceRange(a)}}let o="input.type";(t.composing||t.inputState.compositionPendingChange&&t.inputState.compositionEndedAt>Date.now()-50)&&(t.inputState.compositionPendingChange=!1,o+=".compose",t.inputState.compositionFirstChange&&(o+=".start",t.inputState.compositionFirstChange=!1));return r.update(n,{userEvent:o,scrollIntoView:!0})}(e,n,r));return e.state.facet(Ei).some((t=>t(e,n.from,n.to,i,a)))||e.dispatch(a()),!0}if(r&&!r.main.eq(s)){let t=!1,i="select";return e.inputState.lastSelectionTime>Date.now()-50&&("select"==e.inputState.lastSelectionOrigin&&(t=!0),i=e.inputState.lastSelectionOrigin),e.dispatch({selection:r,scrollIntoView:t,userEvent:i}),!0}return!1}const Wr={childList:!0,characterData:!0,subtree:!0,attributes:!0,characterDataOldValue:!0},qr=Ye.ie&&Ye.ie_version<=11;class Ir{constructor(t){this.view=t,this.active=!1,this.selectionRange=new ue,this.selectionChanged=!1,this.delayedFlush=-1,this.resizeTimeout=-1,this.queue=[],this.delayedAndroidKey=null,this.flushingAndroidKey=-1,this.lastChange=0,this.scrollTargets=[],this.intersection=null,this.resizeScroll=null,this.intersecting=!1,this.gapIntersection=null,this.gaps=[],this.parentCheck=-1,this.dom=t.contentDOM,this.observer=new MutationObserver((e=>{for(let t of e)this.queue.push(t);(Ye.ie&&Ye.ie_version<=11||Ye.ios&&t.composing)&&e.some((t=>"childList"==t.type&&t.removedNodes.length||"characterData"==t.type&&t.oldValue.length>t.target.nodeValue.length))?this.flushSoon():this.flush()})),qr&&(this.onCharData=t=>{this.queue.push({target:t.target,type:"characterData",oldValue:t.prevValue}),this.flushSoon()}),this.onSelectionChange=this.onSelectionChange.bind(this),this.onResize=this.onResize.bind(this),this.onPrint=this.onPrint.bind(this),this.onScroll=this.onScroll.bind(this),"function"==typeof ResizeObserver&&(this.resizeScroll=new ResizeObserver((()=>{var t;(null===(t=this.view.docView)||void 0===t?void 0:t.lastUpdate){this.parentCheck<0&&(this.parentCheck=setTimeout(this.listenForScroll.bind(this),1e3)),t.length>0&&t[t.length-1].intersectionRatio>0!=this.intersecting&&(this.intersecting=!this.intersecting,this.intersecting!=this.view.inView&&this.onScrollChanged(document.createEvent("Event")))}),{threshold:[0,.001]}),this.intersection.observe(this.dom),this.gapIntersection=new IntersectionObserver((t=>{t.length>0&&t[t.length-1].intersectionRatio>0&&this.onScrollChanged(document.createEvent("Event"))}),{})),this.listenForScroll(),this.readSelectionRange()}onScrollChanged(t){this.view.inputState.runHandlers("scroll",t),this.intersecting&&this.view.measure()}onScroll(t){this.intersecting&&this.flush(!1),this.onScrollChanged(t)}onResize(){this.resizeTimeout<0&&(this.resizeTimeout=setTimeout((()=>{this.resizeTimeout=-1,this.view.requestMeasure()}),50))}onPrint(){this.view.viewState.printing=!0,this.view.measure(),setTimeout((()=>{this.view.viewState.printing=!1,this.view.requestMeasure()}),500)}updateGaps(t){if(this.gapIntersection&&(t.length!=this.gaps.length||this.gaps.some(((e,i)=>e!=t[i])))){this.gapIntersection.disconnect();for(let e of t)this.gapIntersection.observe(e);this.gaps=t}}onSelectionChange(t){let e=this.selectionChanged;if(!this.readSelectionRange()||this.delayedAndroidKey)return;let{view:i}=this,n=this.selectionRange;if(i.state.facet(Ii)?i.root.activeElement!=this.dom:!ie(i.dom,n))return;let r=n.anchorNode&&i.docView.nearest(n.anchorNode);r&&r.ignoreEvent(t)?e||(this.selectionChanged=!1):(Ye.ie&&Ye.ie_version<=11||Ye.android&&Ye.chrome)&&!i.state.selection.main.empty&&n.focusNode&&re(n.focusNode,n.focusOffset,n.anchorNode,n.anchorOffset)?this.flushSoon():this.flush(!1)}readSelectionRange(){let{view:t}=this,e=Ye.safari&&11==t.root.nodeType&&function(t){let e=t.activeElement;for(;e&&e.shadowRoot;)e=e.shadowRoot.activeElement;return e}(this.dom.ownerDocument)==this.dom&&function(t){let e=null;function i(t){t.preventDefault(),t.stopImmediatePropagation(),e=t.getTargetRanges()[0]}if(t.contentDOM.addEventListener("beforeinput",i,!0),t.dom.ownerDocument.execCommand("indent"),t.contentDOM.removeEventListener("beforeinput",i,!0),!e)return null;let n=e.startContainer,r=e.startOffset,s=e.endContainer,o=e.endOffset,a=t.docView.domAtPos(t.state.selection.main.anchor);re(a.node,a.offset,s,o)&&([n,r,s,o]=[s,o,n,r]);return{anchorNode:n,anchorOffset:r,focusNode:s,focusOffset:o}}(this.view)||te(t.root);if(!e||this.selectionRange.eq(e))return!1;let i=ie(this.dom,e);return i&&!this.selectionChanged&&t.inputState.lastFocusTime>Date.now()-200&&t.inputState.lastTouchTime{let t=this.delayedAndroidKey;if(t){this.clearDelayedAndroidKey(),this.view.inputState.lastKeyCode=t.keyCode,this.view.inputState.lastKeyTime=Date.now(),!this.flush()&&t.force&&me(this.dom,t.key,t.keyCode)}};this.flushingAndroidKey=this.view.win.requestAnimationFrame(t)}this.delayedAndroidKey&&"Enter"!=t||(this.delayedAndroidKey={key:t,keyCode:e,force:this.lastChange{this.delayedFlush=-1,this.flush()})))}forceFlush(){this.delayedFlush>=0&&(this.view.win.cancelAnimationFrame(this.delayedFlush),this.delayedFlush=-1),this.flush()}pendingRecords(){for(let t of this.observer.takeRecords())this.queue.push(t);return this.queue}processRecords(){let t=this.pendingRecords();t.length&&(this.queue=[]);let e=-1,i=-1,n=!1;for(let r of t){let t=this.readMutation(r);t&&(t.typeOver&&(n=!0),-1==e?({from:e,to:i}=t):(e=Math.min(t.from,e),i=Math.max(t.to,i)))}return{from:e,to:i,typeOver:n}}readChange(){let{from:t,to:e,typeOver:i}=this.processRecords(),n=this.selectionChanged&&ie(this.dom,this.selectionRange);if(t<0&&!n)return null;t>-1&&(this.lastChange=Date.now()),this.view.inputState.lastFocusTime=0,this.selectionChanged=!1;let r=new Vr(this.view,t,e,i);return this.view.docView.domChanged={newSel:r.newSel?r.newSel.main:null},r}flush(t=!0){if(this.delayedFlush>=0||this.delayedAndroidKey)return!1;t&&this.readSelectionRange();let e=this.readChange();if(!e)return this.view.requestMeasure(),!1;let i=this.view.state,n=Yr(this.view,e);return this.view.state==i&&this.view.update([]),n}readMutation(t){let e=this.view.docView.nearest(t.target);if(!e||e.ignoreMutation(t))return null;if(e.markDirty("attributes"==t.type),"attributes"==t.type&&(e.flags|=4),"childList"==t.type){let i=jr(e,t.previousSibling||t.target.previousSibling,-1),n=jr(e,t.nextSibling||t.target.nextSibling,1);return{from:i?e.posAfter(i):e.posAtStart,to:n?e.posBefore(n):e.posAtEnd,typeOver:!1}}return"characterData"==t.type?{from:e.posAtStart,to:e.posAtEnd,typeOver:t.target.nodeValue==t.oldValue}:null}setWindow(t){t!=this.win&&(this.removeWindowListeners(this.win),this.win=t,this.addWindowListeners(this.win))}addWindowListeners(t){t.addEventListener("resize",this.onResize),t.addEventListener("beforeprint",this.onPrint),t.addEventListener("scroll",this.onScroll),t.document.addEventListener("selectionchange",this.onSelectionChange)}removeWindowListeners(t){t.removeEventListener("scroll",this.onScroll),t.removeEventListener("resize",this.onResize),t.removeEventListener("beforeprint",this.onPrint),t.document.removeEventListener("selectionchange",this.onSelectionChange)}destroy(){var t,e,i;this.stop(),null===(t=this.intersection)||void 0===t||t.disconnect(),null===(e=this.gapIntersection)||void 0===e||e.disconnect(),null===(i=this.resizeScroll)||void 0===i||i.disconnect();for(let t of this.scrollTargets)t.removeEventListener("scroll",this.onScroll);this.removeWindowListeners(this.win),clearTimeout(this.parentCheck),clearTimeout(this.resizeTimeout),this.win.cancelAnimationFrame(this.delayedFlush),this.win.cancelAnimationFrame(this.flushingAndroidKey)}}function jr(t,e,i){for(;e;){let n=we.get(e);if(n&&n.parent==t)return n;let r=e.parentNode;e=r!=t.dom?r:i>0?e.nextSibling:e.previousSibling}return null}class Dr{get state(){return this.viewState.state}get viewport(){return this.viewState.viewport}get visibleRanges(){return this.viewState.visibleRanges}get inView(){return this.viewState.inView}get composing(){return this.inputState.composing>0}get compositionStarted(){return this.inputState.composing>=0}get root(){return this._root}get win(){return this.dom.ownerDocument.defaultView||window}constructor(t={}){this.plugins=[],this.pluginMap=new Map,this.editorAttrs={},this.contentAttrs={},this.bidiCache=[],this.destroyed=!1,this.updateState=2,this.measureScheduled=-1,this.measureRequests=[],this.contentDOM=document.createElement("div"),this.scrollDOM=document.createElement("div"),this.scrollDOM.tabIndex=-1,this.scrollDOM.className="cm-scroller",this.scrollDOM.appendChild(this.contentDOM),this.announceDOM=document.createElement("div"),this.announceDOM.className="cm-announced",this.announceDOM.setAttribute("aria-live","polite"),this.dom=document.createElement("div"),this.dom.appendChild(this.announceDOM),this.dom.appendChild(this.scrollDOM),t.parent&&t.parent.appendChild(this.dom);let{dispatch:e}=t;this.dispatchTransactions=t.dispatchTransactions||e&&(t=>t.forEach((t=>e(t,this))))||(t=>this.update(t)),this.dispatch=this.dispatch.bind(this),this._root=t.root||function(t){for(;t;){if(t&&(9==t.nodeType||11==t.nodeType&&t.host))return t;t=t.assignedSlot||t.parentNode}return null}(t.parent)||document,this.viewState=new Or(t.state||vt.create(t)),t.scrollTo&&t.scrollTo.is(Wi)&&(this.viewState.scrollTarget=t.scrollTo.value.clip(this.viewState.state)),this.plugins=this.state.facet(Di).map((t=>new Ni(t)));for(let t of this.plugins)t.update(this);this.observer=new Ir(this),this.inputState=new vn(this),this.inputState.ensureHandlers(this.plugins),this.docView=new rn(this),this.mountStyles(),this.updateAttrs(),this.updateState=0,this.requestMeasure()}dispatch(...t){let e=1==t.length&&t[0]instanceof pt?t:1==t.length&&Array.isArray(t[0])?t[0]:[this.state.update(...t)];this.dispatchTransactions(e,this)}update(t){if(0!=this.updateState)throw new Error("Calls to EditorView.update are not allowed while an update is in progress");let e,i=!1,n=!1,r=this.state;for(let e of t){if(e.startState!=r)throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");r=e.state}if(this.destroyed)return void(this.viewState.state=r);let s=this.hasFocus,o=0,a=null;t.some((t=>t.annotation(Ln)))?(this.inputState.notifiedFocused=s,o=1):s!=this.inputState.notifiedFocused&&(this.inputState.notifiedFocused=s,a=Gn(r,s),a||(o=1));let l=this.observer.delayedAndroidKey,h=null;if(l?(this.observer.clearDelayedAndroidKey(),h=this.observer.readChange(),(h&&!this.state.doc.eq(r.doc)||!this.state.selection.eq(r.selection))&&(h=null)):this.observer.clear(),r.facet(vt.phrases)!=this.state.facet(vt.phrases))return this.setState(r);e=nn.create(this,r,t),e.flags|=o;let c=this.viewState.scrollTarget;try{this.updateState=2;for(let e of t){if(c&&(c=c.map(e.changes)),e.scrollIntoView){let{main:t}=e.state.selection;c=new Yi(t.empty?t:_.cursor(t.head,t.head>t.anchor?-1:1))}for(let t of e.effects)t.is(Wi)&&(c=t.value.clip(this.state))}this.viewState.update(e,c),this.bidiCache=zr.update(this.bidiCache,e.changes),e.empty||(this.updatePlugins(e),this.inputState.update(e)),i=this.docView.update(e),this.state.facet(tn)!=this.styleModules&&this.mountStyles(),n=this.updateAttrs(),this.showAnnouncements(t),this.docView.updateSelection(i,t.some((t=>t.isUserEvent("select.pointer"))))}finally{this.updateState=0}if(e.startState.facet(vr)!=e.state.facet(vr)&&(this.viewState.mustMeasureContent=!0),(i||n||c||this.viewState.mustEnforceCursorAssoc||this.viewState.mustMeasureContent)&&this.requestMeasure(),i&&this.docViewUpdate(),!e.empty)for(let t of this.state.facet(_i))try{t(e)}catch(t){qi(this.state,t,"update listener")}(a||h)&&Promise.resolve().then((()=>{a&&this.state==a.startState&&this.dispatch(a),h&&!Yr(this,h)&&l.force&&me(this.contentDOM,l.key,l.keyCode)}))}setState(t){if(0!=this.updateState)throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");if(this.destroyed)return void(this.viewState.state=t);this.updateState=2;let e=this.hasFocus;try{for(let t of this.plugins)t.destroy(this);this.viewState=new Or(t),this.plugins=t.facet(Di).map((t=>new Ni(t))),this.pluginMap.clear();for(let t of this.plugins)t.update(this);this.docView.destroy(),this.docView=new rn(this),this.inputState.ensureHandlers(this.plugins),this.mountStyles(),this.updateAttrs(),this.bidiCache=[]}finally{this.updateState=0}e&&this.focus(),this.requestMeasure()}updatePlugins(t){let e=t.startState.facet(Di),i=t.state.facet(Di);if(e!=i){let n=[];for(let r of i){let i=e.indexOf(r);if(i<0)n.push(new Ni(r));else{let e=this.plugins[i];e.mustUpdate=t,n.push(e)}}for(let e of this.plugins)e.mustUpdate!=t&&e.destroy(this);this.plugins=n,this.pluginMap.clear()}else for(let e of this.plugins)e.mustUpdate=t;for(let t=0;t-1&&this.win.cancelAnimationFrame(this.measureScheduled),this.observer.delayedAndroidKey)return this.measureScheduled=-1,void this.requestMeasure();this.measureScheduled=0,t&&this.observer.forceFlush();let e=null,i=this.scrollDOM,n=i.scrollTop*this.scaleY,{scrollAnchorPos:r,scrollAnchorHeight:s}=this.viewState;Math.abs(n-this.viewState.scrollTop)>1&&(s=-1),this.viewState.scrollAnchorHeight=-1;try{for(let t=0;;t++){if(s<0)if(ye(i))r=-1,s=this.viewState.heightMap.height;else{let t=this.viewState.scrollAnchorAt(n);r=t.from,s=t.top}this.updateState=1;let o=this.viewState.measure(this);if(!o&&!this.measureRequests.length&&null==this.viewState.scrollTarget)break;if(t>5){console.warn(this.measureRequests.length?"Measure loop restarted more than 5 times":"Viewport failed to stabilize");break}let a=[];4&o||([this.measureRequests,a]=[a,this.measureRequests]);let l=a.map((t=>{try{return t.read(this)}catch(t){return qi(this.state,t),Nr}})),h=nn.create(this,this.state,[]),c=!1;h.flags|=o,e?e.flags|=o:e=h,this.updateState=2,h.empty||(this.updatePlugins(h),this.inputState.update(h),this.updateAttrs(),c=this.docView.update(h),c&&this.docViewUpdate());for(let t=0;t1||t<-1){n+=t,i.scrollTop=n/this.scaleY,s=-1;continue}}}break}}}finally{this.updateState=0,this.measureScheduled=-1}if(e&&!e.empty)for(let t of this.state.facet(_i))t(e)}get themeClasses(){return Qr+" "+(this.state.facet(kr)?$r:Pr)+" "+this.state.facet(vr)}updateAttrs(){let t=Br(this,zi,{class:"cm-editor"+(this.hasFocus?" cm-focused ":" ")+this.themeClasses}),e={spellcheck:"false",autocorrect:"off",autocapitalize:"off",translate:"no",contenteditable:this.state.facet(Ii)?"true":"false",class:"cm-content",style:`${Ye.tabSize}: ${this.state.tabSize}`,role:"textbox","aria-multiline":"true"};this.state.readOnly&&(e["aria-readonly"]="true"),Br(this,Bi,e);let i=this.observer.ignore((()=>{let i=Ge(this.contentDOM,this.contentAttrs,e),n=Ge(this.dom,this.editorAttrs,t);return i||n}));return this.editorAttrs=t,this.contentAttrs=e,i}showAnnouncements(t){let e=!0;for(let i of t)for(let t of i.effects)if(t.is(Dr.announce)){e&&(this.announceDOM.textContent=""),e=!1,this.announceDOM.appendChild(document.createElement("div")).textContent=t.value}}mountStyles(){this.styleModules=this.state.facet(tn);let t=this.state.facet(Dr.cspNonce);zt.mount(this.root,this.styleModules.concat(Tr).reverse(),t?{nonce:t}:void 0)}readMeasured(){if(2==this.updateState)throw new Error("Reading the editor layout isn't allowed during an update");0==this.updateState&&this.measureScheduled>-1&&this.measure(!1)}requestMeasure(t){if(this.measureScheduled<0&&(this.measureScheduled=this.win.requestAnimationFrame((()=>this.measure()))),t){if(this.measureRequests.indexOf(t)>-1)return;if(null!=t.key)for(let e=0;ee.spec==t))||null),e&&e.update(this).value}get documentTop(){return this.contentDOM.getBoundingClientRect().top+this.viewState.paddingTop}get documentPadding(){return{top:this.viewState.paddingTop,bottom:this.viewState.paddingBottom}}get scaleX(){return this.viewState.scaleX}get scaleY(){return this.viewState.scaleY}elementAtHeight(t){return this.readMeasured(),this.viewState.elementAtHeight(t)}lineBlockAtHeight(t){return this.readMeasured(),this.viewState.lineBlockAtHeight(t)}get viewportLineBlocks(){return this.viewState.viewportLines}lineBlockAt(t){return this.viewState.lineBlockAt(t)}get contentHeight(){return this.viewState.contentHeight}moveByChar(t,e,i){return bn(this,t,Sn(this,t,e,i))}moveByGroup(t,e){return bn(this,t,Sn(this,t,e,(e=>function(t,e,i){let n=t.state.charCategorizer(e),r=n(i);return t=>{let e=n(t);return r==xt.Space&&(r=e),r==e}}(this,t.head,e))))}visualLineSide(t,e){let i=this.bidiSpans(t),n=this.textDirectionAt(t.from),r=i[e?i.length-1:0];return _.cursor(r.side(e,n)+t.from,r.forward(!e,n)?1:-1)}moveToLineBoundary(t,e,i=!0){return function(t,e,i,n){let r=xn(t,e.head),s=n&&r.type==Je.Text&&(t.lineWrapping||r.widgetLineBreaks)?t.coordsAtPos(e.assoc<0&&e.head>r.from?e.head-1:e.head):null;if(s){let e=t.dom.getBoundingClientRect(),n=t.textDirectionAt(r.from),o=t.posAtCoords({x:i==(n==hi.LTR)?e.right-1:e.left+1,y:(s.top+s.bottom)/2});if(null!=o)return _.cursor(o,i?-1:1)}return _.cursor(i?r.to:r.from,i?-1:1)}(this,t,e,i)}moveVertically(t,e,i){return bn(this,t,function(t,e,i,n){let r=e.head,s=i?1:-1;if(r==(i?t.state.doc.length:0))return _.cursor(r,e.assoc);let o,a=e.goalColumn,l=t.contentDOM.getBoundingClientRect(),h=t.coordsAtPos(r,e.assoc||-1),c=t.documentTop;if(h)null==a&&(a=h.left-l.left),o=s<0?h.top:h.bottom;else{let e=t.viewState.lineBlockAt(r);null==a&&(a=Math.min(l.right-l.left,t.defaultCharacterWidth*(r-e.from))),o=(s<0?e.top:e.bottom)+c}let u=l.left+a,p=null!=n?n:t.viewState.heightOracle.textHeight>>1;for(let e=0;;e+=10){let i=o+(p+e)*s,n=gn(t,{x:u,y:i},!1,s);if(il.bottom||(s<0?nr)){let e=t.docView.coordsForChar(n),r=!e||i0)}coordsForChar(t){return this.readMeasured(),this.docView.coordsForChar(t)}get defaultCharacterWidth(){return this.viewState.heightOracle.charWidth}get defaultLineHeight(){return this.viewState.heightOracle.lineHeight}get textDirection(){return this.viewState.defaultTextDirection}textDirectionAt(t){return!this.state.facet(Ri)||tthis.viewport.to?this.textDirection:(this.readMeasured(),this.docView.textDirectionAt(t))}get lineWrapping(){return this.viewState.heightOracle.lineWrapping}bidiSpans(t){if(t.length>Mr)return ki(t.length);let e,i=this.textDirectionAt(t.from);for(let n of this.bidiCache)if(n.from==t.from&&n.dir==i&&(n.fresh||Si(n.isolates,e=Hi(this,t))))return n.order;e||(e=Hi(this,t));let n=function(t,e,i){if(!t)return[new xi(0,0,e==ui?1:0)];if(e==ci&&!i.length&&!yi.test(t))return ki(t.length);if(i.length)for(;t.length>wi.length;)wi[wi.length]=256;let n=[],r=e==ci?0:1;return vi(t,r,r,i,0,t.length,n),n}(t.text,i,e);return this.bidiCache.push(new zr(t.from,t.to,i,e,!0,n)),n}get hasFocus(){var t;return(this.dom.ownerDocument.hasFocus()||Ye.safari&&(null===(t=this.inputState)||void 0===t?void 0:t.lastContextMenu)>Date.now()-3e4)&&this.root.activeElement==this.contentDOM}focus(){this.observer.ignore((()=>{fe(this.contentDOM),this.docView.updateSelection()}))}setRoot(t){this._root!=t&&(this._root=t,this.observer.setWindow((9==t.nodeType?t:t.ownerDocument).defaultView||window),this.mountStyles())}destroy(){for(let t of this.plugins)t.destroy(this);this.plugins=[],this.inputState.destroy(),this.docView.destroy(),this.dom.remove(),this.observer.destroy(),this.measureScheduled>-1&&this.win.cancelAnimationFrame(this.measureScheduled),this.destroyed=!0}static scrollIntoView(t,e={}){return Wi.of(new Yi("number"==typeof t?_.cursor(t):t,e.y,e.x,e.yMargin,e.xMargin))}scrollSnapshot(){let{scrollTop:t,scrollLeft:e}=this.scrollDOM,i=this.viewState.scrollAnchorAt(t);return Wi.of(new Yi(_.cursor(i.from),"start","start",i.top-t,e,!0))}static domEventHandlers(t){return Mi.define((()=>({})),{eventHandlers:t})}static domEventObservers(t){return Mi.define((()=>({})),{eventObservers:t})}static theme(t,e){let i=zt.newName(),n=[vr.of(i),tn.of(Cr(`.${i}`,t))];return e&&e.dark&&n.push(kr.of(!0)),n}static baseTheme(t){return G.lowest(tn.of(Cr("."+Qr,t,Zr)))}static findFromDOM(t){var e;let i=t.querySelector(".cm-content"),n=i&&we.get(i)||we.get(t);return(null===(e=null==n?void 0:n.rootView)||void 0===e?void 0:e.view)||null}}Dr.styleModule=tn,Dr.inputHandler=Ei,Dr.focusChangeEffect=Xi,Dr.perLineTextDirection=Ri,Dr.exceptionSink=Ai,Dr.updateListener=_i,Dr.editable=Ii,Dr.mouseSelectionStyle=Ti,Dr.dragMovesSelection=Ci,Dr.clickAddsSelectionRange=Zi,Dr.decorations=Li,Dr.outerDecorations=Gi,Dr.atomicRanges=Ui,Dr.bidiIsolatedRanges=Fi,Dr.scrollMargins=Ki,Dr.darkTheme=kr,Dr.cspNonce=R.define({combine:t=>t.length?t[0]:""}),Dr.contentAttributes=Bi,Dr.editorAttributes=zi,Dr.lineWrapping=Dr.contentAttributes.of({class:"cm-lineWrapping"}),Dr.announce=ut.define();const Mr=4096,Nr={};class zr{constructor(t,e,i,n,r,s){this.from=t,this.to=e,this.dir=i,this.isolates=n,this.fresh=r,this.order=s}static update(t,e){if(e.empty&&!t.some((t=>t.fresh)))return t;let i=[],n=t.length?t[t.length-1].dir:hi.LTR;for(let r=Math.max(0,t.length-10);r=0;r--){let e=n[r],s="function"==typeof e?e(t):e;s&&ze(s,i)}return i}const Lr=Ye.mac?"mac":Ye.windows?"win":Ye.linux?"linux":"key";function Gr(t,e,i){return e.altKey&&(t="Alt-"+t),e.ctrlKey&&(t="Ctrl-"+t),e.metaKey&&(t="Meta-"+t),!1!==i&&e.shiftKey&&(t="Shift-"+t),t}const Ur=G.default(Dr.domEventHandlers({keydown:(t,e)=>function(t,e,i,n){let r=function(t){var e=!(Ft&&t.metaKey&&t.shiftKey&&!t.ctrlKey&&!t.altKey||Ht&&t.shiftKey&&t.key&&1==t.key.length||"Unidentified"==t.key)&&t.key||(t.shiftKey?Ut:Gt)[t.keyCode]||t.key||"Unidentified";return"Esc"==e&&(e="Escape"),"Del"==e&&(e="Delete"),"Left"==e&&(e="ArrowLeft"),"Up"==e&&(e="ArrowUp"),"Right"==e&&(e="ArrowRight"),"Down"==e&&(e="ArrowDown"),e}(e),s=x(r,0),o=S(s)==r.length&&" "!=r,a="",l=!1,h=!1,c=!1;Kr&&Kr.view==i&&Kr.scope==n&&(a=Kr.prefix+" ",Zn.indexOf(e.keyCode)<0&&(h=!0,Kr=null));let u,p,d=new Set,f=t=>{if(t){for(let n of t.run)if(!d.has(n)&&(d.add(n),n(i,e)))return t.stopPropagation&&(c=!0),!0;t.preventDefault&&(t.stopPropagation&&(c=!0),h=!0)}return!1},O=t[n];O&&(f(O[a+Gr(r,e,!o)])?l=!0:o&&(e.altKey||e.metaKey||e.ctrlKey)&&!(Ye.windows&&e.ctrlKey&&e.altKey)&&(u=Gt[e.keyCode])&&u!=r?(f(O[a+Gr(u,e,!0)])||e.shiftKey&&(p=Ut[e.keyCode])!=r&&p!=u&&f(O[a+Gr(p,e,!1)]))&&(l=!0):o&&e.shiftKey&&f(O[a+Gr(r,e,!0)])&&(l=!0),!l&&f(O._any)&&(l=!0));h&&(l=!0);l&&c&&e.stopPropagation();return l}(function(t){let e=t.facet(Fr),i=Hr.get(e);i||Hr.set(e,i=function(t,e=Lr){let i=Object.create(null),n=Object.create(null),r=(t,e)=>{let i=n[t];if(null==i)n[t]=e;else if(i!=e)throw new Error("Key binding "+t+" is used both as a regular binding and as a multi-stroke prefix")},s=(t,n,s,o,a)=>{var l,h;let c=i[t]||(i[t]=Object.create(null)),u=n.split(/ (?!$)/).map((t=>function(t,e){const i=t.split(/-(?!$)/);let n,r,s,o,a=i[i.length-1];"Space"==a&&(a=" ");for(let t=0;t{let n=Kr={view:e,prefix:i,scope:t};return setTimeout((()=>{Kr==n&&(Kr=null)}),Jr),!0}]})}let p=u.join(" ");r(p,!1);let d=c[p]||(c[p]={preventDefault:!1,stopPropagation:!1,run:(null===(h=null===(l=c._any)||void 0===l?void 0:l.run)||void 0===h?void 0:h.slice())||[]});s&&d.run.push(s),o&&(d.preventDefault=!0),a&&(d.stopPropagation=!0)};for(let n of t){let t=n.scope?n.scope.split(" "):["editor"];if(n.any)for(let e of t){let t=i[e]||(i[e]=Object.create(null));t._any||(t._any={preventDefault:!1,stopPropagation:!1,run:[]});for(let e in t)t[e].run.push(n.any)}let r=n[e]||n.key;if(r)for(let e of t)s(e,r,n.run,n.preventDefault,n.stopPropagation),n.shift&&s(e,"Shift-"+r,n.shift,n.preventDefault,n.stopPropagation)}return i}(e.reduce(((t,e)=>t.concat(e)),[])));return i}(e.state),t,e,"editor")})),Fr=R.define({enables:Ur}),Hr=new WeakMap;let Kr=null;const Jr=4e3;class ts{constructor(t,e,i,n,r){this.className=t,this.left=e,this.top=i,this.width=n,this.height=r}draw(){let t=document.createElement("div");return t.className=this.className,this.adjust(t),t}update(t,e){return e.className==this.className&&(this.adjust(t),!0)}adjust(t){t.style.left=this.left+"px",t.style.top=this.top+"px",null!=this.width&&(t.style.width=this.width+"px"),t.style.height=this.height+"px"}eq(t){return this.left==t.left&&this.top==t.top&&this.width==t.width&&this.height==t.height&&this.className==t.className}static forRange(t,e,i){if(i.empty){let n=t.coordsAtPos(i.head,i.assoc||1);if(!n)return[];let r=es(t);return[new ts(e,n.left-r.left,n.top-r.top,null,n.bottom-n.top)]}return function(t,e,i){if(i.to<=t.viewport.from||i.from>=t.viewport.to)return[];let n=Math.max(i.from,t.viewport.from),r=Math.min(i.to,t.viewport.to),s=t.textDirection==hi.LTR,o=t.contentDOM,a=o.getBoundingClientRect(),l=es(t),h=o.querySelector(".cm-line"),c=h&&window.getComputedStyle(h),u=a.left+(c?parseInt(c.paddingLeft)+Math.min(0,parseInt(c.textIndent)):0),p=a.right-(c?parseInt(c.paddingRight):0),d=xn(t,n),f=xn(t,r),O=d.type==Je.Text?d:null,m=f.type==Je.Text?f:null;O&&(t.lineWrapping||d.widgetLineBreaks)&&(O=is(t,n,O));m&&(t.lineWrapping||f.widgetLineBreaks)&&(m=is(t,r,m));if(O&&m&&O.from==m.from)return y(x(i.from,i.to,O));{let e=O?x(i.from,null,O):S(d,!1),n=m?x(null,i.to,m):S(f,!0),r=[];return(O||d).to<(m||f).from-(O&&m?1:0)||d.widgetLineBreaks>1&&e.bottom+t.defaultLineHeight/2h&&n.from=s)break;a>r&&l(Math.max(t,r),null==e&&t<=h,Math.min(a,s),null==i&&a>=c,o.dir)}if(r=n.to+1,r>=s)break}return 0==a.length&&l(h,null==e,c,null==i,t.textDirection),{top:r,bottom:o,horizontal:a}}function S(t,e){let i=a.top+(e?t.top:t.bottom);return{top:i,bottom:i,horizontal:[]}}}(t,e,i)}}function es(t){let e=t.scrollDOM.getBoundingClientRect();return{left:(t.textDirection==hi.LTR?e.left:e.right-t.scrollDOM.clientWidth*t.scaleX)-t.scrollDOM.scrollLeft*t.scaleX,top:e.top-t.scrollDOM.scrollTop*t.scaleY}}function is(t,e,i){let n=_.cursor(e);return{from:Math.max(i.from,t.moveToLineBoundary(n,!1,!0).from),to:Math.min(i.to,t.moveToLineBoundary(n,!0,!0).from),type:Je.Text}}class ns{constructor(t,e){this.view=t,this.layer=e,this.drawn=[],this.scaleX=1,this.scaleY=1,this.measureReq={read:this.measure.bind(this),write:this.draw.bind(this)},this.dom=t.scrollDOM.appendChild(document.createElement("div")),this.dom.classList.add("cm-layer"),e.above&&this.dom.classList.add("cm-layer-above"),e.class&&this.dom.classList.add(e.class),this.scale(),this.dom.setAttribute("aria-hidden","true"),this.setOrder(t.state),t.requestMeasure(this.measureReq),e.mount&&e.mount(this.dom,t)}update(t){t.startState.facet(rs)!=t.state.facet(rs)&&this.setOrder(t.state),(this.layer.update(t,this.dom)||t.geometryChanged)&&(this.scale(),t.view.requestMeasure(this.measureReq))}docViewUpdate(t){!1!==this.layer.updateOnDocViewUpdate&&t.requestMeasure(this.measureReq)}setOrder(t){let e=0,i=t.facet(rs);for(;e{return i=t,n=this.drawn[e],!(i.constructor==n.constructor&&i.eq(n));var i,n}))){let e=this.dom.firstChild,i=0;for(let n of t)n.update&&e&&n.constructor&&this.drawn[i].constructor&&n.update(e,this.drawn[i])?(e=e.nextSibling,i++):this.dom.insertBefore(n.draw(),e);for(;e;){let t=e.nextSibling;e.remove(),e=t}this.drawn=t}}destroy(){this.layer.destroy&&this.layer.destroy(this.dom,this.view),this.dom.remove()}}const rs=R.define();function ss(t){return[Mi.define((e=>new ns(e,t))),rs.of(t)]}const os=!Ye.ios,as=R.define({combine:t=>kt(t,{cursorBlinkRate:1200,drawRangeCursor:!0},{cursorBlinkRate:(t,e)=>Math.min(t,e),drawRangeCursor:(t,e)=>t||e})});function ls(t={}){return[as.of(t),cs,ps,fs,Vi.of(!0)]}function hs(t){return t.startState.facet(as)!=t.state.facet(as)}const cs=ss({above:!0,markers(t){let{state:e}=t,i=e.facet(as),n=[];for(let r of e.selection.ranges){let s=r==e.selection.main;if(r.empty?!s||os:i.drawRangeCursor){let e=s?"cm-cursor cm-cursor-primary":"cm-cursor cm-cursor-secondary",i=r.empty?r:_.cursor(r.head,r.head>r.anchor?-1:1);for(let r of ts.forRange(t,e,i))n.push(r)}}return n},update(t,e){t.transactions.some((t=>t.selection))&&(e.style.animationName="cm-blink"==e.style.animationName?"cm-blink2":"cm-blink");let i=hs(t);return i&&us(t.state,e),t.docChanged||t.selectionSet||i},mount(t,e){us(e.state,t)},class:"cm-cursorLayer"});function us(t,e){e.style.animationDuration=t.facet(as).cursorBlinkRate+"ms"}const ps=ss({above:!1,markers:t=>t.state.selection.ranges.map((e=>e.empty?[]:ts.forRange(t,"cm-selectionBackground",e))).reduce(((t,e)=>t.concat(e))),update:(t,e)=>t.docChanged||t.selectionSet||t.viewportChanged||hs(t),class:"cm-selectionLayer"}),ds={".cm-line":{"& ::selection":{backgroundColor:"transparent !important"},"&::selection":{backgroundColor:"transparent !important"}}};os&&(ds[".cm-line"].caretColor="transparent !important",ds[".cm-content"]={caretColor:"transparent !important"});const fs=G.highest(Dr.theme(ds));function Os(t,e,i,n,r){e.lastIndex=0;for(let s,o=t.iterRange(i,n),a=i;!o.next().done;a+=o.value.length)if(!o.lineBreak)for(;s=e.exec(o.value);)r(a+s.index,s)}class ms{constructor(t){const{regexp:e,decoration:i,decorate:n,boundary:r,maxLength:s=1e3}=t;if(!e.global)throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");if(this.regexp=e,n)this.addMatch=(t,e,i,r)=>n(r,i,i+t[0].length,t,e);else if("function"==typeof i)this.addMatch=(t,e,n,r)=>{let s=i(t,e,n);s&&r(n,n+t[0].length,s)};else{if(!i)throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");this.addMatch=(t,e,n,r)=>r(n,n+t[0].length,i)}this.boundary=r,this.maxLength=s}createDeco(t){let e=new Tt,i=e.add.bind(e);for(let{from:e,to:n}of function(t,e){let i=t.visibleRanges;if(1==i.length&&i[0].from==t.viewport.from&&i[0].to==t.viewport.to)return i;let n=[];for(let{from:r,to:s}of i)r=Math.max(t.state.doc.lineAt(r).from,r-e),s=Math.min(t.state.doc.lineAt(s).to,s+e),n.length&&n[n.length-1].to>=r?n[n.length-1].to=s:n.push({from:r,to:s});return n}(t,this.maxLength))Os(t.state.doc,this.regexp,e,n,((e,n)=>this.addMatch(n,t,e,i)));return e.finish()}updateDeco(t,e){let i=1e9,n=-1;return t.docChanged&&t.changes.iterChanges(((e,r,s,o)=>{o>t.view.viewport.from&&s1e3?this.createDeco(t.view):n>-1?this.updateRange(t.view,e.map(t.changes),i,n):e}updateRange(t,e,i,n){for(let r of t.visibleRanges){let s=Math.max(r.from,i),o=Math.min(r.to,n);if(o>s){let i=t.state.doc.lineAt(s),n=i.toi.from;s--)if(this.boundary.test(i.text[s-1-i.from])){a=s;break}for(;oc.push(i.range(t,e));if(i==n)for(this.regexp.lastIndex=a-i.from;(h=this.regexp.exec(i.text))&&h.indexthis.addMatch(i,t,e,u)));e=e.update({filterFrom:a,filterTo:l,filter:(t,e)=>tl,add:c})}}return e}}const gs=null!=/x/.unicode?"gu":"g",ys=new RegExp("[\0-\b\n--Ÿ­؜​‎‏\u2028\u2029‭‮⁦⁧⁩\ufeff-]",gs),xs={0:"null",7:"bell",8:"backspace",10:"newline",11:"vertical tab",13:"carriage return",27:"escape",8203:"zero width space",8204:"zero width non-joiner",8205:"zero width joiner",8206:"left-to-right mark",8207:"right-to-left mark",8232:"line separator",8237:"left-to-right override",8238:"right-to-left override",8294:"left-to-right isolate",8295:"right-to-left isolate",8297:"pop directional isolate",8233:"paragraph separator",65279:"zero width no-break space",65532:"object replacement"};let Ss=null;const ws=R.define({combine(t){let e=kt(t,{render:null,specialChars:ys,addSpecialChars:null});return(e.replaceTabs=!function(){var t;if(null==Ss&&"undefined"!=typeof document&&document.body){let e=document.body.style;Ss=null!=(null!==(t=e.tabSize)&&void 0!==t?t:e.MozTabSize)}return Ss||!1}())&&(e.specialChars=new RegExp("\t|"+e.specialChars.source,gs)),e.addSpecialChars&&(e.specialChars=new RegExp(e.specialChars.source+"|"+e.addSpecialChars.source,gs)),e}});function bs(t={}){return[ws.of(t),vs||(vs=Mi.fromClass(class{constructor(t){this.view=t,this.decorations=ti.none,this.decorationCache=Object.create(null),this.decorator=this.makeDecorator(t.state.facet(ws)),this.decorations=this.decorator.createDeco(t)}makeDecorator(t){return new ms({regexp:t.specialChars,decoration:(e,i,n)=>{let{doc:r}=i.state,s=x(e[0],0);if(9==s){let t=r.lineAt(n),e=i.state.tabSize,s=jt(t.text,e,n-t.from);return ti.replace({widget:new Qs((e-s%e)*this.view.defaultCharacterWidth/this.view.scaleX)})}return this.decorationCache[s]||(this.decorationCache[s]=ti.replace({widget:new ks(t,s)}))},boundary:t.replaceTabs?void 0:/[^]/})}update(t){let e=t.state.facet(ws);t.startState.facet(ws)!=e?(this.decorator=this.makeDecorator(e),this.decorations=this.decorator.createDeco(t.view)):this.decorations=this.decorator.updateDeco(t,this.decorations)}},{decorations:t=>t.decorations}))]}let vs=null;class ks extends Ke{constructor(t,e){super(),this.options=t,this.code=e}eq(t){return t.code==this.code}toDOM(t){let e=function(t){return t>=32?"•":10==t?"␤":String.fromCharCode(9216+t)}(this.code),i=t.state.phrase("Control character")+" "+(xs[this.code]||"0x"+this.code.toString(16)),n=this.options.render&&this.options.render(this.code,i,e);if(n)return n;let r=document.createElement("span");return r.textContent=e,r.title=i,r.setAttribute("aria-label",i),r.className="cm-specialChar",r}ignoreEvent(){return!1}}class Qs extends Ke{constructor(t){super(),this.width=t}eq(t){return t.width==this.width}toDOM(){let t=document.createElement("span");return t.textContent="\t",t.className="cm-tab",t.style.width=this.width+"px",t}ignoreEvent(){return!1}}class Ps extends Qt{compare(t){return this==t||this.constructor==t.constructor&&this.eq(t)}eq(t){return!1}destroy(t){}}Ps.prototype.elementClass="",Ps.prototype.toDOM=void 0,Ps.prototype.mapMode=b.TrackBefore,Ps.prototype.startSide=Ps.prototype.endSide=-1,Ps.prototype.point=!0;const $s=R.define(),Zs=R.define(),Cs=R.define({combine:t=>t.some((t=>t))});function Ts(t){let e=[As];return t&&!1===t.fixed&&e.push(Cs.of(!0)),e}const As=Mi.fromClass(class{constructor(t){this.view=t,this.prevViewport=t.viewport,this.dom=document.createElement("div"),this.dom.className="cm-gutters",this.dom.setAttribute("aria-hidden","true"),this.dom.style.minHeight=this.view.contentHeight/this.view.scaleY+"px",this.gutters=t.state.facet(Zs).map((e=>new Rs(t,e)));for(let t of this.gutters)this.dom.appendChild(t.dom);this.fixed=!t.state.facet(Cs),this.fixed&&(this.dom.style.position="sticky"),this.syncGutters(!1),t.scrollDOM.insertBefore(this.dom,t.contentDOM)}update(t){if(this.updateGutters(t)){let e=this.prevViewport,i=t.view.viewport,n=Math.min(e.to,i.to)-Math.max(e.from,i.from);this.syncGutters(n<.8*(i.to-i.from))}t.geometryChanged&&(this.dom.style.minHeight=this.view.contentHeight/this.view.scaleY+"px"),this.view.state.facet(Cs)!=!this.fixed&&(this.fixed=!this.fixed,this.dom.style.position=this.fixed?"sticky":""),this.prevViewport=t.view.viewport}syncGutters(t){let e=this.dom.nextSibling;t&&this.dom.remove();let i=Ct.iter(this.view.state.facet($s),this.view.viewport.from),n=[],r=this.gutters.map((t=>new Xs(t,this.view.viewport,-this.view.documentPadding.top)));for(let t of this.view.viewportLineBlocks)if(n.length&&(n=[]),Array.isArray(t.type)){let e=!0;for(let s of t.type)if(s.type==Je.Text&&e){Es(i,n,s.from);for(let t of r)t.line(this.view,s,n);e=!1}else if(s.widget)for(let t of r)t.widget(this.view,s)}else if(t.type==Je.Text){Es(i,n,t.from);for(let e of r)e.line(this.view,t,n)}else if(t.widget)for(let e of r)e.widget(this.view,t);for(let t of r)t.finish();t&&this.view.scrollDOM.insertBefore(this.dom,e)}updateGutters(t){let e=t.startState.facet(Zs),i=t.state.facet(Zs),n=t.docChanged||t.heightChanged||t.viewportChanged||!Ct.eq(t.startState.facet($s),t.state.facet($s),t.view.viewport.from,t.view.viewport.to);if(e==i)for(let e of this.gutters)e.update(t)&&(n=!0);else{n=!0;let r=[];for(let n of i){let i=e.indexOf(n);i<0?r.push(new Rs(this.view,n)):(this.gutters[i].update(t),r.push(this.gutters[i]))}for(let t of this.gutters)t.dom.remove(),r.indexOf(t)<0&&t.destroy();for(let t of r)this.dom.appendChild(t.dom);this.gutters=r}return n}destroy(){for(let t of this.gutters)t.destroy();this.dom.remove()}},{provide:t=>Dr.scrollMargins.of((e=>{let i=e.plugin(t);return i&&0!=i.gutters.length&&i.fixed?e.textDirection==hi.LTR?{left:i.dom.offsetWidth*e.scaleX}:{right:i.dom.offsetWidth*e.scaleX}:null}))});function _s(t){return Array.isArray(t)?t:[t]}function Es(t,e,i){for(;t.value&&t.from<=i;)t.from==i&&e.push(t.value),t.next()}class Xs{constructor(t,e,i){this.gutter=t,this.height=i,this.i=0,this.cursor=Ct.iter(t.markers,e.from)}addElement(t,e,i){let{gutter:n}=this,r=(e.top-this.height)/t.scaleY,s=e.height/t.scaleY;if(this.i==n.elements.length){let e=new Vs(t,s,r,i);n.elements.push(e),n.dom.appendChild(e.dom)}else n.elements[this.i].update(t,s,r,i);this.height=e.bottom,this.i++}line(t,e,i){let n=[];Es(this.cursor,n,e.from),i.length&&(n=n.concat(i));let r=this.gutter.config.lineMarker(t,e,n);r&&n.unshift(r);let s=this.gutter;(0!=n.length||s.config.renderEmptyElements)&&this.addElement(t,e,n)}widget(t,e){let i=this.gutter.config.widgetMarker(t,e.widget,e);i&&this.addElement(t,e,[i])}finish(){let t=this.gutter;for(;t.elements.length>this.i;){let e=t.elements.pop();t.dom.removeChild(e.dom),e.destroy()}}}class Rs{constructor(t,e){this.view=t,this.config=e,this.elements=[],this.spacer=null,this.dom=document.createElement("div"),this.dom.className="cm-gutter"+(this.config.class?" "+this.config.class:"");for(let i in e.domEventHandlers)this.dom.addEventListener(i,(n=>{let r,s=n.target;if(s!=this.dom&&this.dom.contains(s)){for(;s.parentNode!=this.dom;)s=s.parentNode;let t=s.getBoundingClientRect();r=(t.top+t.bottom)/2}else r=n.clientY;let o=t.lineBlockAtHeight(r-t.documentTop);e.domEventHandlers[i](t,o,n)&&n.preventDefault()}));this.markers=_s(e.markers(t)),e.initialSpacer&&(this.spacer=new Vs(t,0,0,[e.initialSpacer(t)]),this.dom.appendChild(this.spacer.dom),this.spacer.dom.style.cssText+="visibility: hidden; pointer-events: none")}update(t){let e=this.markers;if(this.markers=_s(this.config.markers(t.view)),this.spacer&&this.config.updateSpacer){let e=this.config.updateSpacer(this.spacer.markers[0],t);e!=this.spacer.markers[0]&&this.spacer.update(t.view,0,0,[e])}let i=t.view.viewport;return!Ct.eq(this.markers,e,i.from,i.to)||!!this.config.lineMarkerChange&&this.config.lineMarkerChange(t)}destroy(){for(let t of this.elements)t.destroy()}}class Vs{constructor(t,e,i,n){this.height=-1,this.above=0,this.markers=[],this.dom=document.createElement("div"),this.dom.className="cm-gutterElement",this.update(t,e,i,n)}update(t,e,i,n){this.height!=e&&(this.height=e,this.dom.style.height=e+"px"),this.above!=i&&(this.dom.style.marginTop=(this.above=i)?i+"px":""),function(t,e){if(t.length!=e.length)return!1;for(let i=0;ikt(t,{formatNumber:String,domEventHandlers:{}},{domEventHandlers(t,e){let i=Object.assign({},t);for(let t in e){let n=i[t],r=e[t];i[t]=n?(t,e,i)=>n(t,e,i)||r(t,e,i):r}return i}})});class qs extends Ps{constructor(t){super(),this.number=t}eq(t){return this.number==t.number}toDOM(){return document.createTextNode(this.number)}}function Is(t,e){return t.state.facet(Ws).formatNumber(e,t.state)}const js=Zs.compute([Ws],(t=>({class:"cm-lineNumbers",renderEmptyElements:!1,markers:t=>t.state.facet(Ys),lineMarker:(t,e,i)=>i.some((t=>t.toDOM))?null:new qs(Is(t,t.state.doc.lineAt(e.from).number)),widgetMarker:()=>null,lineMarkerChange:t=>t.startState.facet(Ws)!=t.state.facet(Ws),initialSpacer:t=>new qs(Is(t,Ms(t.state.doc.lines))),updateSpacer(t,e){let i=Is(e.view,Ms(e.view.state.doc.lines));return i==t.number?t:new qs(i)},domEventHandlers:t.facet(Ws).domEventHandlers})));function Ds(t={}){return[Ws.of(t),Ts(),js]}function Ms(t){let e=9;for(;e{throw new Error("This node type doesn't define a deserialize function")})}add(t){if(this.perNode)throw new RangeError("Can't add per-node props to node types");return"function"!=typeof t&&(t=Fs.match(t)),e=>{let i=t(e);return void 0===i?null:[this,i]}}}Ls.closedBy=new Ls({deserialize:t=>t.split(" ")}),Ls.openedBy=new Ls({deserialize:t=>t.split(" ")}),Ls.group=new Ls({deserialize:t=>t.split(" ")}),Ls.isolate=new Ls({deserialize:t=>{if(t&&"rtl"!=t&&"ltr"!=t&&"auto"!=t)throw new RangeError("Invalid value for isolate: "+t);return t||"auto"}}),Ls.contextHash=new Ls({perNode:!0}),Ls.lookAhead=new Ls({perNode:!0}),Ls.mounted=new Ls({perNode:!0});class Gs{constructor(t,e,i){this.tree=t,this.overlay=e,this.parser=i}static get(t){return t&&t.props&&t.props[Ls.mounted.id]}}const Us=Object.create(null);class Fs{constructor(t,e,i,n=0){this.name=t,this.props=e,this.id=i,this.flags=n}static define(t){let e=t.props&&t.props.length?Object.create(null):Us,i=(t.top?1:0)|(t.skipped?2:0)|(t.error?4:0)|(null==t.name?8:0),n=new Fs(t.name||"",e,t.id,i);if(t.props)for(let i of t.props)if(Array.isArray(i)||(i=i(n)),i){if(i[0].perNode)throw new RangeError("Can't store a per-node prop on a node type");e[i[0].id]=i[1]}return n}prop(t){return this.props[t.id]}get isTop(){return(1&this.flags)>0}get isSkipped(){return(2&this.flags)>0}get isError(){return(4&this.flags)>0}get isAnonymous(){return(8&this.flags)>0}is(t){if("string"==typeof t){if(this.name==t)return!0;let e=this.prop(Ls.group);return!!e&&e.indexOf(t)>-1}return this.id==t}static match(t){let e=Object.create(null);for(let i in t)for(let n of i.split(" "))e[n]=t[i];return t=>{for(let i=t.prop(Ls.group),n=-1;n<(i?i.length:0);n++){let r=e[n<0?t.name:i[n]];if(r)return r}}}}Fs.none=new Fs("",Object.create(null),0,8);class Hs{constructor(t){this.types=t;for(let e=0;e=e){let o=new ao(s.tree,s.overlay[0].from+t.from,-1,t);(r||(r=[n])).push(so(o,e,i,!1))}}return r?po(r):n}(this,t,e)}iterate(t){let{enter:e,leave:i,from:n=0,to:r=this.length}=t,s=t.mode||0,o=(s&to.IncludeAnonymous)>0;for(let t=this.cursor(s|to.IncludeAnonymous);;){let s=!1;if(t.from<=r&&t.to>=n&&(!o&&t.type.isAnonymous||!1!==e(t))){if(t.firstChild())continue;s=!0}for(;s&&i&&(o||!t.type.isAnonymous)&&i(t),!t.nextSibling();){if(!t.parent())return;s=!0}}}prop(t){return t.perNode?this.props?this.props[t.id]:void 0:this.type.prop(t)}get propValues(){let t=[];if(this.props)for(let e in this.props)t.push([+e,this.props[e]]);return t}balance(t={}){return this.children.length<=8?this:xo(Fs.none,this.children,this.positions,0,this.children.length,0,this.length,((t,e,i)=>new eo(this.type,t,e,i,this.propValues)),t.makeTree||((t,e,i)=>new eo(Fs.none,t,e,i)))}static build(t){return function(t){var e;let{buffer:i,nodeSet:n,maxBufferLength:r=Ns,reused:s=[],minRepeatType:o=n.types.length}=t,a=Array.isArray(i)?new io(i,i.length):i,l=n.types,h=0,c=0;function u(t,e,i,y,x,S){let{id:w,start:b,end:v,size:k}=a,Q=c;for(;k<0;){if(a.next(),-1==k){let e=s[w];return i.push(e),void y.push(b-t)}if(-3==k)return void(h=w);if(-4==k)return void(c=w);throw new RangeError(`Unrecognized record size: ${k}`)}let P,$,Z=l[w],C=b-t;if(v-b<=r&&($=m(a.pos-e,x))){let e=new Uint16Array($.size-$.skip),i=a.pos-$.size,r=e.length;for(;a.pos>i;)r=g($.start,e,r);P=new no(e,v-$.start,n),C=$.start-t}else{let t=a.pos-k;a.next();let e=[],i=[],n=w>=o?w:-1,s=0,l=v;for(;a.pos>t;)n>=0&&a.id==n&&a.size>=0?(a.end<=l-r&&(f(e,i,b,s,a.end,l,n,Q),s=e.length,l=a.end),a.next()):S>2500?p(b,t,e,i):u(b,t,e,i,n,S+1);if(n>=0&&s>0&&s-1&&s>0){let t=d(Z);P=xo(Z,e,i,0,e.length,0,v-b,t,t)}else P=O(Z,e,i,v-b,Q-v)}i.push(P),y.push(C)}function p(t,e,i,s){let o=[],l=0,h=-1;for(;a.pos>e;){let{id:t,start:e,end:i,size:n}=a;if(n>4)a.next();else{if(h>-1&&e=0;t-=3)e[i++]=o[t],e[i++]=o[t+1]-r,e[i++]=o[t+2]-r,e[i++]=i;i.push(new no(e,o[2]-r,n)),s.push(r-t)}}function d(t){return(e,i,n)=>{let r,s,o=0,a=e.length-1;if(a>=0&&(r=e[a])instanceof eo){if(!a&&r.type==t&&r.length==n)return r;(s=r.prop(Ls.lookAhead))&&(o=i[a]+r.length+s)}return O(t,e,i,n,o)}}function f(t,e,i,r,s,o,a,l){let h=[],c=[];for(;t.length>r;)h.push(t.pop()),c.push(e.pop()+i-s);t.push(O(n.types[a],h,c,o-s,l-o)),e.push(s-i)}function O(t,e,i,n,r=0,s){if(h){let t=[Ls.contextHash,h];s=s?[t].concat(s):[t]}if(r>25){let t=[Ls.lookAhead,r];s=s?[t].concat(s):[t]}return new eo(t,e,i,n,s)}function m(t,e){let i=a.fork(),n=0,s=0,l=0,h=i.end-r,c={size:0,start:0,skip:0};t:for(let r=i.pos-t;i.pos>r;){let t=i.size;if(i.id==e&&t>=0){c.size=n,c.start=s,c.skip=l,l+=4,n+=4,i.next();continue}let a=i.pos-t;if(t<0||a=o?4:0,p=i.start;for(i.next();i.pos>a;){if(i.size<0){if(-3!=i.size)break t;u+=4}else i.id>=o&&(u+=4);i.next()}s=p,n+=t,l+=u}return(e<0||n==t)&&(c.size=n,c.start=s,c.skip=l),c.size>4?c:void 0}function g(t,e,i){let{id:n,start:r,end:s,size:l}=a;if(a.next(),l>=0&&n4){let n=a.pos-(l-4);for(;a.pos>n;)i=g(t,e,i)}e[--i]=o,e[--i]=s-t,e[--i]=r-t,e[--i]=n}else-3==l?h=n:-4==l&&(c=n);return i}let y=[],x=[];for(;a.pos>0;)u(t.start||0,t.bufferStart||0,y,x,-1,0);let S=null!==(e=t.length)&&void 0!==e?e:y.length?x[0]+y[0].length:0;return new eo(l[t.topID],y.reverse(),x.reverse(),S)}(t)}}eo.empty=new eo(Fs.none,[],[],0);class io{constructor(t,e){this.buffer=t,this.index=e}get id(){return this.buffer[this.index-4]}get start(){return this.buffer[this.index-3]}get end(){return this.buffer[this.index-2]}get size(){return this.buffer[this.index-1]}get pos(){return this.index}next(){this.index-=4}fork(){return new io(this.buffer,this.index)}}class no{constructor(t,e,i){this.buffer=t,this.length=e,this.set=i}get type(){return Fs.none}toString(){let t=[];for(let e=0;e0));a=s[a+3]);return o}slice(t,e,i){let n=this.buffer,r=new Uint16Array(e-t),s=0;for(let o=t,a=0;o=e&&ie;case 1:return i<=e&&n>e;case 2:return n>e;case 4:return!0}}function so(t,e,i,n){for(var r;t.from==t.to||(i<1?t.from>=e:t.from>e)||(i>-1?t.to<=e:t.to0?o.length:-1;t!=l;t+=e){let l=o[t],h=a[t]+s.from;if(ro(n,i,h,h+l.length))if(l instanceof no){if(r&to.ExcludeBuffers)continue;let o=l.findChild(0,l.buffer.length,e,i-h,n);if(o>-1)return new uo(new co(s,l,t,h),null,o)}else if(r&to.IncludeAnonymous||!l.type.isAnonymous||mo(l)){let o;if(!(r&to.IgnoreMounts)&&(o=Gs.get(l))&&!o.overlay)return new ao(o.tree,h,t,s);let a=new ao(l,h,t,s);return r&to.IncludeAnonymous||!a.type.isAnonymous?a:a.nextChild(e<0?l.children.length-1:0,e,i,n)}}if(r&to.IncludeAnonymous||!s.type.isAnonymous)return null;if(t=s.index>=0?s.index+e:e<0?-1:s._parent._tree.children.length,s=s._parent,!s)return null}}get firstChild(){return this.nextChild(0,1,0,4)}get lastChild(){return this.nextChild(this._tree.children.length-1,-1,0,4)}childAfter(t){return this.nextChild(0,1,t,2)}childBefore(t){return this.nextChild(this._tree.children.length-1,-1,t,-2)}enter(t,e,i=0){let n;if(!(i&to.IgnoreOverlays)&&(n=Gs.get(this._tree))&&n.overlay){let i=t-this.from;for(let{from:t,to:r}of n.overlay)if((e>0?t<=i:t=i:r>i))return new ao(n.tree,n.overlay[0].from+this.from,-1,this)}return this.nextChild(0,1,t,e,i)}nextSignificantParent(){let t=this;for(;t.type.isAnonymous&&t._parent;)t=t._parent;return t}get parent(){return this._parent?this._parent.nextSignificantParent():null}get nextSibling(){return this._parent&&this.index>=0?this._parent.nextChild(this.index+1,1,0,4):null}get prevSibling(){return this._parent&&this.index>=0?this._parent.nextChild(this.index-1,-1,0,4):null}get tree(){return this._tree}toTree(){return this._tree}toString(){return this._tree.toString()}}function lo(t,e,i,n){let r=t.cursor(),s=[];if(!r.firstChild())return s;if(null!=i)for(let t=!1;!t;)if(t=r.type.is(i),!r.nextSibling())return s;for(;;){if(null!=n&&r.type.is(n))return s;if(r.type.is(e)&&s.push(r.node),!r.nextSibling())return null==n?s:[]}}function ho(t,e,i=e.length-1){for(let n=t.parent;i>=0;n=n.parent){if(!n)return!1;if(!n.type.isAnonymous){if(e[i]&&e[i]!=n.name)return!1;i--}}return!0}class co{constructor(t,e,i,n){this.parent=t,this.buffer=e,this.index=i,this.start=n}}class uo extends oo{get name(){return this.type.name}get from(){return this.context.start+this.context.buffer.buffer[this.index+1]}get to(){return this.context.start+this.context.buffer.buffer[this.index+2]}constructor(t,e,i){super(),this.context=t,this._parent=e,this.index=i,this.type=t.buffer.set.types[t.buffer.buffer[i]]}child(t,e,i){let{buffer:n}=this.context,r=n.findChild(this.index+4,n.buffer[this.index+3],t,e-this.context.start,i);return r<0?null:new uo(this.context,this,r)}get firstChild(){return this.child(1,0,4)}get lastChild(){return this.child(-1,0,4)}childAfter(t){return this.child(1,t,2)}childBefore(t){return this.child(-1,t,-2)}enter(t,e,i=0){if(i&to.ExcludeBuffers)return null;let{buffer:n}=this.context,r=n.findChild(this.index+4,n.buffer[this.index+3],e>0?1:-1,t-this.context.start,e);return r<0?null:new uo(this.context,this,r)}get parent(){return this._parent||this.context.parent.nextSignificantParent()}externalSibling(t){return this._parent?null:this.context.parent.nextChild(this.context.index+t,t,0,4)}get nextSibling(){let{buffer:t}=this.context,e=t.buffer[this.index+3];return e<(this._parent?t.buffer[this._parent.index+3]:t.buffer.length)?new uo(this.context,this._parent,e):this.externalSibling(1)}get prevSibling(){let{buffer:t}=this.context,e=this._parent?this._parent.index+4:0;return this.index==e?this.externalSibling(-1):new uo(this.context,this._parent,t.findChild(e,this.index,-1,0,4))}get tree(){return null}toTree(){let t=[],e=[],{buffer:i}=this.context,n=this.index+4,r=i.buffer[this.index+3];if(r>n){let s=i.buffer[this.index+1];t.push(i.slice(n,r,s)),e.push(0)}return new eo(this.type,t,e,this.to-this.from)}toString(){return this.context.buffer.childString(this.index)}}function po(t){if(!t.length)return null;let e=0,i=t[0];for(let n=1;ni.from||r.to0){if(this.index-1)for(let n=e+t,r=t<0?-1:i._tree.children.length;n!=r;n+=t){let t=i._tree.children[n];if(this.mode&to.IncludeAnonymous||t instanceof no||!t.type.isAnonymous||mo(t))return!1}return!0}move(t,e){if(e&&this.enterChild(t,0,4))return!0;for(;;){if(this.sibling(t))return!0;if(this.atLastNode(t)||!this.parent())return!1}}next(t=!0){return this.move(1,t)}prev(t=!0){return this.move(-1,t)}moveTo(t,e=0){for(;(this.from==this.to||(e<1?this.from>=t:this.from>t)||(e>-1?this.to<=t:this.to=0;){for(let s=t;s;s=s._parent)if(s.index==n){if(n==this.index)return s;e=s,i=r+1;break t}n=this.stack[--r]}for(let t=i;t=0;r--){if(r<0)return ho(this.node,t,n);let s=i[e.buffer[this.stack[r]]];if(!s.isAnonymous){if(t[n]&&t[n]!=s.name)return!1;n--}}return!0}}function mo(t){return t.children.some((t=>t instanceof no||!t.type.isAnonymous||mo(t)))}const go=new WeakMap;function yo(t,e){if(!t.isAnonymous||e instanceof no||e.type!=t)return 1;let i=go.get(e);if(null==i){i=1;for(let n of e.children){if(n.type!=t||!(n instanceof eo)){i=1;break}i+=yo(t,n)}go.set(e,i)}return i}function xo(t,e,i,n,r,s,o,a,l){let h=0;for(let i=n;i=c)break;f+=e}if(h==r+1){if(f>c){let t=i[r];e(t.children,t.positions,0,t.children.length,n[r]+a);continue}u.push(i[r])}else{let e=n[h-1]+i[h-1].length-d;u.push(xo(t,i,n,r,h,d,e,null,l))}p.push(d+a-s)}}(e,i,n,r,0),(a||l)(u,p,o)}class So{constructor(){this.map=new WeakMap}setBuffer(t,e,i){let n=this.map.get(t);n||this.map.set(t,n=new Map),n.set(e,i)}getBuffer(t,e){let i=this.map.get(t);return i&&i.get(e)}set(t,e){t instanceof uo?this.setBuffer(t.context.buffer,t.index,e):t instanceof ao&&this.map.set(t.tree,e)}get(t){return t instanceof uo?this.getBuffer(t.context.buffer,t.index):t instanceof ao?this.map.get(t.tree):void 0}cursorSet(t,e){t.buffer?this.setBuffer(t.buffer.buffer,t.index,e):this.map.set(t.tree,e)}cursorGet(t){return t.buffer?this.getBuffer(t.buffer.buffer,t.index):this.map.get(t.tree)}}class wo{constructor(t,e,i,n,r=!1,s=!1){this.from=t,this.to=e,this.tree=i,this.offset=n,this.open=(r?1:0)|(s?2:0)}get openStart(){return(1&this.open)>0}get openEnd(){return(2&this.open)>0}static addTree(t,e=[],i=!1){let n=[new wo(0,t.length,t,0,!1,i)];for(let i of e)i.to>t.length&&n.push(i);return n}static applyChanges(t,e,i=128){if(!e.length)return t;let n=[],r=1,s=t.length?t[0]:null;for(let o=0,a=0,l=0;;o++){let h=o=i)for(;s&&s.from=e.from||c<=e.to||l){let t=Math.max(e.from,a)-l,i=Math.min(e.to,c)-l;e=t>=i?null:new wo(t,i,e.tree,e.offset+l,o>0,!!h)}if(e&&n.push(e),s.to>c)break;s=rnew Bs(t.from,t.to))):[new Bs(0,0)]:[new Bs(0,t.length)],this.createParse(t,e||[],i)}parse(t,e,i){let n=this.startParse(t,e,i);for(;;){let t=n.advance();if(t)return t}}};class vo{constructor(t){this.string=t}get length(){return this.string.length}chunk(t){return this.string.slice(t)}get lineChunks(){return!1}read(t,e){return this.string.slice(t,e)}}class ko{constructor(t,e,i,n,r){this.parser=t,this.parse=e,this.overlay=i,this.target=n,this.from=r}}function Qo(t){if(!t.length||t.some((t=>t.from>=t.to)))throw new RangeError("Invalid inner parse ranges given: "+JSON.stringify(t))}class Po{constructor(t,e,i,n,r,s,o){this.parser=t,this.predicate=e,this.mounts=i,this.index=n,this.start=r,this.target=s,this.prev=o,this.depth=0,this.ranges=[]}}const $o=new Ls({perNode:!0});class Zo{constructor(t,e,i,n,r){this.nest=e,this.input=i,this.fragments=n,this.ranges=r,this.inner=[],this.innerDone=0,this.baseTree=null,this.stoppedAt=null,this.baseParse=t}advance(){if(this.baseParse){let t=this.baseParse.advance();if(!t)return null;if(this.baseParse=null,this.baseTree=t,this.startInner(),null!=this.stoppedAt)for(let t of this.inner)t.parse.stopAt(this.stoppedAt)}if(this.innerDone==this.inner.length){let t=this.baseTree;return null!=this.stoppedAt&&(t=new eo(t.type,t.children,t.positions,t.length,t.propValues.concat([[$o,this.stoppedAt]]))),t}let t=this.inner[this.innerDone],e=t.parse.advance();if(e){this.innerDone++;let i=Object.assign(Object.create(null),t.target.props);i[Ls.mounted.id]=new Gs(e,t.overlay,t.parser),t.target.props=i}return null}get parsedPos(){if(this.baseParse)return 0;let t=this.input.length;for(let e=this.innerDone;e=this.stoppedAt)a=!1;else if(t.hasNode(n)){if(e){let t=e.mounts.find((t=>t.frag.from<=n.from&&t.frag.to>=n.to&&t.mount.overlay));if(t)for(let i of t.mount.overlay){let r=i.from+t.pos,s=i.to+t.pos;r>=n.from&&s<=n.to&&!e.ranges.some((t=>t.fromr))&&e.ranges.push({from:r,to:s})}}a=!1}else if(i&&(s=Co(i.ranges,n.from,n.to)))a=2!=s;else if(!n.type.isAnonymous&&(r=this.nest(n,this.input))&&(n.fromnew Bs(t.from-n.from,t.to-n.from))):null,n.tree,t.length?t[0].from:n.from)),r.overlay?t.length&&(i={ranges:t,depth:0,prev:i}):a=!1}}else e&&(o=e.predicate(n))&&(!0===o&&(o=new Bs(n.from,n.to)),o.fromnew Bs(t.from-e.start,t.to-e.start))),e.target,t[0].from))),e=e.prev}i&&! --i.depth&&(i=i.prev)}}}}function Co(t,e,i){for(let n of t){if(n.from>=i)break;if(n.to>e)return n.from<=e&&n.to>=i?2:1}return 0}function To(t,e,i,n,r,s){if(e=t&&e.enter(i,1,to.IgnoreOverlays|to.ExcludeBuffers)||e.next(!1)||(this.done=!0)}hasNode(t){if(this.moveTo(t.from),!this.done&&this.cursor.from+this.offset==t.from&&this.cursor.tree)for(let e=this.cursor.tree;;){if(e==t.tree)return!0;if(!(e.children.length&&0==e.positions[0]&&e.children[0]instanceof eo))break;e=e.children[0]}return!1}}let Eo=class{constructor(t){var e;if(this.fragments=t,this.curTo=0,this.fragI=0,t.length){let i=this.curFrag=t[0];this.curTo=null!==(e=i.tree.prop($o))&&void 0!==e?e:i.to,this.inner=new _o(i.tree,-i.offset)}else this.curFrag=this.inner=null}hasNode(t){for(;this.curFrag&&t.from>=this.curTo;)this.nextFrag();return this.curFrag&&this.curFrag.from<=t.from&&this.curTo>=t.to&&this.inner.hasNode(t)}nextFrag(){var t;if(this.fragI++,this.fragI==this.fragments.length)this.curFrag=this.inner=null;else{let e=this.curFrag=this.fragments[this.fragI];this.curTo=null!==(t=e.tree.prop($o))&&void 0!==t?t:e.to,this.inner=new _o(e.tree,-e.offset)}}findMounts(t,e){var i;let n=[];if(this.inner){this.inner.cursor.moveTo(t,1);for(let t=this.inner.cursor.node;t;t=t.parent){let r=null===(i=t.tree)||void 0===i?void 0:i.prop(Ls.mounted);if(r&&r.parser==e)for(let e=this.fragI;e=t.to)break;i.tree==this.curFrag.tree&&n.push({frag:i,pos:t.from-i.offset,mount:r})}}}return n}};function Xo(t,e){let i=null,n=e;for(let r=1,s=0;r=a)break;t.to<=o||(i||(n=i=e.slice()),t.froma&&i.splice(s+1,0,new Bs(a,t.to))):t.to>a?i[s--]=new Bs(a,t.to):i.splice(s--,1))}}return n}function Ro(t,e,i,n){let r=0,s=0,o=!1,a=!1,l=-1e9,h=[];for(;;){let c=r==t.length?1e9:o?t[r].to:t[r].from,u=s==e.length?1e9:a?e[s].to:e[s].from;if(o!=a){let t=Math.max(l,i),e=Math.min(c,u,n);tnew Bs(t.from+n,t.to+n))),a,l);for(let e=0,n=a;;e++){let a=e==o.length,h=a?l:o[e].from;if(h>n&&i.push(new wo(n,h,r.tree,-t,s.from>=n||s.openStart,s.to<=h||s.openEnd)),a)break;n=o[e].to}}else i.push(new wo(a,l,r.tree,-t,s.from>=t||s.openStart,s.to<=o||s.openEnd))}return i}let Yo=0;class Wo{constructor(t,e,i){this.set=t,this.base=e,this.modified=i,this.id=Yo++}static define(t){if(null==t?void 0:t.base)throw new Error("Can not derive from a modified tag");let e=new Wo([],null,[]);if(e.set.push(e),t)for(let i of t.set)e.set.push(i);return e}static defineModifier(){let t=new Io;return e=>e.modified.indexOf(t)>-1?e:Io.get(e.base||e,e.modified.concat(t).sort(((t,e)=>t.id-e.id)))}}let qo=0;class Io{constructor(){this.instances=[],this.id=qo++}static get(t,e){if(!e.length)return t;let i=e[0].instances.find((i=>{return i.base==t&&(n=e,r=i.modified,n.length==r.length&&n.every(((t,e)=>t==r[e])));var n,r}));if(i)return i;let n=[],r=new Wo(n,t,e);for(let t of e)t.instances.push(r);let s=function(t){let e=[[]];for(let i=0;ie.length-t.length))}(e);for(let e of t.set)if(!e.modified.length)for(let t of s)n.push(Io.get(e,t));return r}}function jo(t){let e=Object.create(null);for(let i in t){let n=t[i];Array.isArray(n)||(n=[n]);for(let t of i.split(" "))if(t){let i=[],r=2,s=t;for(let e=0;;){if("..."==s&&e>0&&e+3==t.length){r=1;break}let n=/^"(?:[^"\\]|\\.)*?"|[^\/!]+/.exec(s);if(!n)throw new RangeError("Invalid path: "+t);if(i.push("*"==n[0]?"":'"'==n[0][0]?JSON.parse(n[0]):n[0]),e+=n[0].length,e==t.length)break;let o=t[e++];if(e==t.length&&"!"==o){r=0;break}if("/"!=o)throw new RangeError("Invalid path: "+t);s=t.slice(e)}let o=i.length-1,a=i[o];if(!a)throw new RangeError("Invalid path: "+t);let l=new Mo(n,r,o>0?i.slice(0,o):null);e[a]=l.sort(e[a])}}return Do.add(e)}const Do=new Ls;class Mo{constructor(t,e,i,n){this.tags=t,this.mode=e,this.context=i,this.next=n}get opaque(){return 0==this.mode}get inherit(){return 1==this.mode}sort(t){return!t||t.depth{let e=r;for(let n of t)for(let t of n.set){let n=i[t.id];if(n){e=e?e+" "+n:n;break}}return e},scope:n}}function zo(t,e,i,n=0,r=t.length){let s=new Bo(n,Array.isArray(e)?e:[e],i);s.highlightRange(t.cursor(),n,r,"",s.highlighters),s.flush(r)}Mo.empty=new Mo([],2,null);class Bo{constructor(t,e,i){this.at=t,this.highlighters=e,this.span=i,this.class=""}startSpan(t,e){e!=this.class&&(this.flush(t),t>this.at&&(this.at=t),this.class=e)}flush(t){t>this.at&&this.class&&this.span(this.at,t,this.class)}highlightRange(t,e,i,n,r){let{type:s,from:o,to:a}=t;if(o>=i||a<=e)return;s.isTop&&(r=this.highlighters.filter((t=>!t.scope||t.scope(s))));let l=n,h=function(t){let e=t.type.prop(Do);for(;e&&e.context&&!t.matchContext(e.context);)e=e.next;return e||null}(t)||Mo.empty,c=function(t,e){let i=null;for(let n of t){let t=n.style(e);t&&(i=i?i+" "+t:t)}return i}(r,h.tags);if(c&&(l&&(l+=" "),l+=c,1==h.mode&&(n+=(n?" ":"")+c)),this.startSpan(Math.max(e,o),l),h.opaque)return;let u=t.tree&&t.tree.prop(Ls.mounted);if(u&&u.overlay){let s=t.node.enter(u.overlay[0].from+o,1),h=this.highlighters.filter((t=>!t.scope||t.scope(u.tree.type))),c=t.firstChild();for(let p=0,d=o;;p++){let f=p=O)&&t.nextSibling()););if(!f||O>i)break;d=f.to+o,d>e&&(this.highlightRange(s.cursor(),Math.max(e,f.from+o),Math.min(i,d),"",h),this.startSpan(Math.min(i,d),l))}c&&t.parent()}else if(t.firstChild()){u&&(n="");do{if(!(t.to<=e)){if(t.from>=i)break;this.highlightRange(t,e,i,n,r),this.startSpan(Math.min(i,t.to),l)}}while(t.nextSibling());t.parent()}}}const Lo=Wo.define,Go=Lo(),Uo=Lo(),Fo=Lo(Uo),Ho=Lo(Uo),Ko=Lo(),Jo=Lo(Ko),ta=Lo(Ko),ea=Lo(),ia=Lo(ea),na=Lo(),ra=Lo(),sa=Lo(),oa=Lo(sa),aa=Lo(),la={comment:Go,lineComment:Lo(Go),blockComment:Lo(Go),docComment:Lo(Go),name:Uo,variableName:Lo(Uo),typeName:Fo,tagName:Lo(Fo),propertyName:Ho,attributeName:Lo(Ho),className:Lo(Uo),labelName:Lo(Uo),namespace:Lo(Uo),macroName:Lo(Uo),literal:Ko,string:Jo,docString:Lo(Jo),character:Lo(Jo),attributeValue:Lo(Jo),number:ta,integer:Lo(ta),float:Lo(ta),bool:Lo(Ko),regexp:Lo(Ko),escape:Lo(Ko),color:Lo(Ko),url:Lo(Ko),keyword:na,self:Lo(na),null:Lo(na),atom:Lo(na),unit:Lo(na),modifier:Lo(na),operatorKeyword:Lo(na),controlKeyword:Lo(na),definitionKeyword:Lo(na),moduleKeyword:Lo(na),operator:ra,derefOperator:Lo(ra),arithmeticOperator:Lo(ra),logicOperator:Lo(ra),bitwiseOperator:Lo(ra),compareOperator:Lo(ra),updateOperator:Lo(ra),definitionOperator:Lo(ra),typeOperator:Lo(ra),controlOperator:Lo(ra),punctuation:sa,separator:Lo(sa),bracket:oa,angleBracket:Lo(oa),squareBracket:Lo(oa),paren:Lo(oa),brace:Lo(oa),content:ea,heading:ia,heading1:Lo(ia),heading2:Lo(ia),heading3:Lo(ia),heading4:Lo(ia),heading5:Lo(ia),heading6:Lo(ia),contentSeparator:Lo(ea),list:Lo(ea),quote:Lo(ea),emphasis:Lo(ea),strong:Lo(ea),link:Lo(ea),monospace:Lo(ea),strikethrough:Lo(ea),inserted:Lo(),deleted:Lo(),changed:Lo(),invalid:Lo(),meta:aa,documentMeta:Lo(aa),annotation:Lo(aa),processingInstruction:Lo(aa),definition:Wo.defineModifier(),constant:Wo.defineModifier(),function:Wo.defineModifier(),standard:Wo.defineModifier(),local:Wo.defineModifier(),special:Wo.defineModifier()},ha=No([{tag:la.link,class:"tok-link"},{tag:la.heading,class:"tok-heading"},{tag:la.emphasis,class:"tok-emphasis"},{tag:la.strong,class:"tok-strong"},{tag:la.keyword,class:"tok-keyword"},{tag:la.atom,class:"tok-atom"},{tag:la.bool,class:"tok-bool"},{tag:la.url,class:"tok-url"},{tag:la.labelName,class:"tok-labelName"},{tag:la.inserted,class:"tok-inserted"},{tag:la.deleted,class:"tok-deleted"},{tag:la.literal,class:"tok-literal"},{tag:la.string,class:"tok-string"},{tag:la.number,class:"tok-number"},{tag:[la.regexp,la.escape,la.special(la.string)],class:"tok-string2"},{tag:la.variableName,class:"tok-variableName"},{tag:la.local(la.variableName),class:"tok-variableName tok-local"},{tag:la.definition(la.variableName),class:"tok-variableName tok-definition"},{tag:la.special(la.variableName),class:"tok-variableName2"},{tag:la.definition(la.propertyName),class:"tok-propertyName tok-definition"},{tag:la.typeName,class:"tok-typeName"},{tag:la.namespace,class:"tok-namespace"},{tag:la.className,class:"tok-className"},{tag:la.macroName,class:"tok-macroName"},{tag:la.propertyName,class:"tok-propertyName"},{tag:la.operator,class:"tok-operator"},{tag:la.comment,class:"tok-comment"},{tag:la.meta,class:"tok-meta"},{tag:la.invalid,class:"tok-invalid"},{tag:la.punctuation,class:"tok-punctuation"}]);var ca;const ua=new Ls;function pa(t){return R.define({combine:t?e=>e.concat(t):void 0})}const da=new Ls;class fa{constructor(t,e,i=[],n=""){this.data=t,this.name=n,vt.prototype.hasOwnProperty("tree")||Object.defineProperty(vt.prototype,"tree",{get(){return ga(this)}}),this.parser=e,this.extension=[Pa.of(this),vt.languageData.of(((t,e,i)=>{let n=Oa(t,e,i),r=n.type.prop(ua);if(!r)return[];let s=t.facet(r),o=n.type.prop(da);if(o){let r=n.resolve(e-n.from,i);for(let e of o)if(e.test(r,t)){let i=t.facet(e.facet);return"replace"==e.type?i:i.concat(s)}}return s}))].concat(i)}isActiveAt(t,e,i=-1){return Oa(t,e,i).type.prop(ua)==this.data}findRegions(t){let e=t.facet(Pa);if((null==e?void 0:e.data)==this.data)return[{from:0,to:t.doc.length}];if(!e||!e.allowsNesting)return[];let i=[],n=(t,e)=>{if(t.prop(ua)==this.data)return void i.push({from:e,to:e+t.length});let r=t.prop(Ls.mounted);if(r){if(r.tree.prop(ua)==this.data){if(r.overlay)for(let t of r.overlay)i.push({from:t.from+e,to:t.to+e});else i.push({from:e,to:e+t.length});return}if(r.overlay){let t=i.length;if(n(r.tree,r.overlay[0].from+e),i.length>t)return}}for(let i=0;it.isTop?e:void 0))]}),t.name)}configure(t,e){return new ma(this.data,this.parser.configure(t),e||this.name)}get allowsNesting(){return this.parser.hasWrappers()}}function ga(t){let e=t.field(fa.state,!1);return e?e.tree:eo.empty}class ya{constructor(t){this.doc=t,this.cursorPos=0,this.string="",this.cursor=t.iter()}get length(){return this.doc.length}syncTo(t){return this.string=this.cursor.next(t-this.cursorPos).value,this.cursorPos=t+this.string.length,this.cursorPos-this.string.length}chunk(t){return this.syncTo(t),this.string}get lineChunks(){return!0}read(t,e){let i=this.cursorPos-this.string.length;return t=this.cursorPos?this.doc.sliceString(t,e):this.string.slice(t-i,e-i)}}let xa=null;class Sa{constructor(t,e,i=[],n,r,s,o,a){this.parser=t,this.state=e,this.fragments=i,this.tree=n,this.treeLen=r,this.viewport=s,this.skipped=o,this.scheduleOn=a,this.parse=null,this.tempSkipped=[]}static create(t,e,i){return new Sa(t,e,[],eo.empty,0,i,[],null)}startParse(){return this.parser.startParse(new ya(this.state.doc),this.fragments)}work(t,e){return null!=e&&e>=this.state.doc.length&&(e=void 0),this.tree!=eo.empty&&this.isDone(null!=e?e:this.state.doc.length)?(this.takeTree(),!0):this.withContext((()=>{var i;if("number"==typeof t){let e=Date.now()+t;t=()=>Date.now()>e}for(this.parse||(this.parse=this.startParse()),null!=e&&(null==this.parse.stoppedAt||this.parse.stoppedAt>e)&&e=this.treeLen&&((null==this.parse.stoppedAt||this.parse.stoppedAt>t)&&this.parse.stopAt(t),this.withContext((()=>{for(;!(e=this.parse.advance()););})),this.treeLen=t,this.tree=e,this.fragments=this.withoutTempSkipped(wo.addTree(this.tree,this.fragments,!0)),this.parse=null)}withContext(t){let e=xa;xa=this;try{return t()}finally{xa=e}}withoutTempSkipped(t){for(let e;e=this.tempSkipped.pop();)t=wa(t,e.from,e.to);return t}changes(t,e){let{fragments:i,tree:n,treeLen:r,viewport:s,skipped:o}=this;if(this.takeTree(),!t.empty){let e=[];if(t.iterChangedRanges(((t,i,n,r)=>e.push({fromA:t,toA:i,fromB:n,toB:r}))),i=wo.applyChanges(i,e),n=eo.empty,r=0,s={from:t.mapPos(s.from,-1),to:t.mapPos(s.to,1)},this.skipped.length){o=[];for(let e of this.skipped){let i=t.mapPos(e.from,1),n=t.mapPos(e.to,-1);it.from&&(this.fragments=wa(this.fragments,i,n),this.skipped.splice(e--,1))}return!(this.skipped.length>=e)&&(this.reset(),!0)}reset(){this.parse&&(this.takeTree(),this.parse=null)}skipUntilInView(t,e){this.skipped.push({from:t,to:e})}static getSkippingParser(t){return new class extends bo{createParse(e,i,n){let r=n[0].from,s=n[n.length-1].to;return{parsedPos:r,advance(){let e=xa;if(e){for(let t of n)e.tempSkipped.push(t);t&&(e.scheduleOn=e.scheduleOn?Promise.all([e.scheduleOn,t]):t)}return this.parsedPos=s,new eo(Fs.none,[],[],s-r)},stoppedAt:null,stopAt(){}}}}}isDone(t){t=Math.min(t,this.state.doc.length);let e=this.fragments;return this.treeLen>=t&&e.length&&0==e[0].from&&e[0].to>=t}static get(){return xa}}function wa(t,e,i){return wo.applyChanges(t,[{fromA:e,toA:i,fromB:e,toB:i}])}class ba{constructor(t){this.context=t,this.tree=t.tree}apply(t){if(!t.docChanged&&this.tree==this.context.tree)return this;let e=this.context.changes(t.changes,t.state),i=this.context.treeLen==t.startState.doc.length?void 0:Math.max(t.changes.mapPos(this.context.treeLen),e.viewport.to);return e.work(20,i)||e.takeTree(),new ba(e)}static init(t){let e=Math.min(3e3,t.doc.length),i=Sa.create(t.facet(Pa).parser,t,{from:0,to:e});return i.work(20,e)||i.takeTree(),new ba(i)}}fa.state=D.define({create:ba.init,update(t,e){for(let t of e.effects)if(t.is(fa.setState))return t.value;return e.startState.facet(Pa)!=e.state.facet(Pa)?ba.init(e.state):t.apply(e)}});let va=t=>{let e=setTimeout((()=>t()),500);return()=>clearTimeout(e)};"undefined"!=typeof requestIdleCallback&&(va=t=>{let e=-1,i=setTimeout((()=>{e=requestIdleCallback(t,{timeout:400})}),100);return()=>e<0?clearTimeout(i):cancelIdleCallback(e)});const ka="undefined"!=typeof navigator&&(null===(ca=navigator.scheduling)||void 0===ca?void 0:ca.isInputPending)?()=>navigator.scheduling.isInputPending():null,Qa=Mi.fromClass(class{constructor(t){this.view=t,this.working=null,this.workScheduled=0,this.chunkEnd=-1,this.chunkBudget=-1,this.work=this.work.bind(this),this.scheduleWork()}update(t){let e=this.view.state.field(fa.state).context;(e.updateViewport(t.view.viewport)||this.view.viewport.to>e.treeLen)&&this.scheduleWork(),(t.docChanged||t.selectionSet)&&(this.view.hasFocus&&(this.chunkBudget+=50),this.scheduleWork()),this.checkAsyncSchedule(e)}scheduleWork(){if(this.working)return;let{state:t}=this.view,e=t.field(fa.state);e.tree==e.context.tree&&e.context.isDone(t.doc.length)||(this.working=va(this.work))}work(t){this.working=null;let e=Date.now();if(this.chunkEndn+1e3,a=r.context.work((()=>ka&&ka()||Date.now()>s),n+(o?0:1e5));this.chunkBudget-=Date.now()-e,(a||this.chunkBudget<=0)&&(r.context.takeTree(),this.view.dispatch({effects:fa.setState.of(new ba(r.context))})),this.chunkBudget>0&&(!a||o)&&this.scheduleWork(),this.checkAsyncSchedule(r.context)}checkAsyncSchedule(t){t.scheduleOn&&(this.workScheduled++,t.scheduleOn.then((()=>this.scheduleWork())).catch((t=>qi(this.view.state,t))).then((()=>this.workScheduled--)),t.scheduleOn=null)}destroy(){this.working&&this.working()}isWorking(){return!!(this.working||this.workScheduled>0)}},{eventHandlers:{focus(){this.scheduleWork()}}}),Pa=R.define({combine:t=>t.length?t[0]:null,enables:t=>[fa.state,Qa,Dr.contentAttributes.compute([t],(e=>{let i=e.facet(t);return i&&i.name?{"data-language":i.name}:{}}))]});class $a{constructor(t,e=[]){this.language=t,this.support=e,this.extension=[t,e]}}const Za=R.define(),Ca=R.define({combine:t=>{if(!t.length)return" ";let e=t[0];if(!e||/\S/.test(e)||Array.from(e).some((t=>t!=e[0])))throw new Error("Invalid indent unit: "+JSON.stringify(t[0]));return e}});function Ta(t){let e=t.facet(Ca);return 9==e.charCodeAt(0)?t.tabSize*e.length:e.length}function Aa(t,e){let i="",n=t.tabSize,r=t.facet(Ca)[0];if("\t"==r){for(;e>=n;)i+="\t",e-=n;r=" "}for(let t=0;t=e?function(t,e,i){let n=e.resolveStack(i),r=n.node.enterUnfinishedNodesBefore(i);if(r!=n.node){let t=[];for(let e=r;e!=n.node;e=e.parent)t.push(e);for(let e=t.length-1;e>=0;e--)n={node:t[e],next:n}}return Ra(n,t,i)}(t,i,e):null}class Ea{constructor(t,e={}){this.state=t,this.options=e,this.unit=Ta(t)}lineAt(t,e=1){let i=this.state.doc.lineAt(t),{simulateBreak:n,simulateDoubleBreak:r}=this.options;return null!=n&&n>=i.from&&n<=i.to?r&&n==t?{text:"",from:t}:(e<0?n-1&&(r+=s-this.countColumn(i,i.search(/\S|$/))),r}countColumn(t,e=t.length){return jt(t,this.state.tabSize,e)}lineIndent(t,e=1){let{text:i,from:n}=this.lineAt(t,e),r=this.options.overrideIndentation;if(r){let t=r(n);if(t>-1)return t}return this.countColumn(i,i.search(/\S|$/))}get simulatedBreak(){return this.options.simulateBreak||null}}const Xa=new Ls;function Ra(t,e,i){for(let n=t;n;n=n.next){let t=Va(n.node);if(t)return t(Wa.create(e,i,n))}return 0}function Va(t){let e=t.type.prop(Xa);if(e)return e;let i,n=t.firstChild;if(n&&(i=n.type.prop(Ls.closedBy))){let e=t.lastChild,n=e&&i.indexOf(e.name)>-1;return t=>ja(t,!0,1,void 0,n&&!function(t){return t.pos==t.options.simulateBreak&&t.options.simulateDoubleBreak}(t)?e.from:void 0)}return null==t.parent?Ya:null}function Ya(){return 0}class Wa extends Ea{constructor(t,e,i){super(t.state,t.options),this.base=t,this.pos=e,this.context=i}get node(){return this.context.node}static create(t,e,i){return new Wa(t,e,i)}get textAfter(){return this.textAfterPos(this.pos)}get baseIndent(){return this.baseIndentFor(this.node)}baseIndentFor(t){let e=this.state.doc.lineAt(t.from);for(;;){let i=t.resolve(e.from);for(;i.parent&&i.parent.from==i.from;)i=i.parent;if(qa(i,t))break;e=this.state.doc.lineAt(i.from)}return this.lineIndent(e.from)}continue(){return Ra(this.context.next,this.base,this.pos)}}function qa(t,e){for(let i=e;i;i=i.parent)if(t==i)return!0;return!1}function Ia({closing:t,align:e=!0,units:i=1}){return n=>ja(n,e,i,t)}function ja(t,e,i,n,r){let s=t.textAfter,o=s.match(/^\s*/)[0].length,a=n&&s.slice(o,o+n.length)==n||r==t.pos+o,l=e?function(t){let e=t.node,i=e.childAfter(e.from),n=e.lastChild;if(!i)return null;let r=t.options.simulateBreak,s=t.state.doc.lineAt(i.from),o=null==r||r<=s.from?s.to:Math.min(s.to,r);for(let t=i.to;;){let r=e.childAfter(t);if(!r||r==n)return null;if(!r.type.isSkipped)return r.from{let n=t&&t.test(i.textAfter);return i.baseIndent+(n?0:e*i.unit)}}const Ma=new Ls;function Na(t){let e=t.firstChild,i=t.lastChild;return e&&e.tot.prop(ua)==s.data:s?t=>t==s:void 0,this.style=No(t.map((t=>({tag:t.tag,class:t.class||n(Object.assign({},t,{tag:null}))}))),{all:r}).style,this.module=i?new zt(i):null,this.themeType=e.themeType}static define(t,e){return new za(t,e||{})}}const Ba=R.define(),La=R.define({combine:t=>t.length?[t[0]]:null});function Ga(t){let e=t.facet(Ba);return e.length?e:t.facet(La)}function Ua(t,e){let i,n=[Ha];return t instanceof za&&(t.module&&n.push(Dr.styleModule.of(t.module)),i=t.themeType),(null==e?void 0:e.fallback)?n.push(La.of(t)):i?n.push(Ba.computeN([Dr.darkTheme],(e=>e.facet(Dr.darkTheme)==("dark"==i)?[t]:[]))):n.push(Ba.of(t)),n}class Fa{constructor(t){this.markCache=Object.create(null),this.tree=ga(t.state),this.decorations=this.buildDeco(t,Ga(t.state)),this.decoratedTo=t.viewport.to}update(t){let e=ga(t.state),i=Ga(t.state),n=i!=Ga(t.startState),{viewport:r}=t.view,s=t.changes.mapPos(this.decoratedTo,1);e.length=r.to?(this.decorations=this.decorations.map(t.changes),this.decoratedTo=s):(e!=this.tree||t.viewportChanged||n)&&(this.tree=e,this.decorations=this.buildDeco(t.view,i),this.decoratedTo=r.to)}buildDeco(t,e){if(!e||!this.tree.length)return ti.none;let i=new Tt;for(let{from:n,to:r}of t.visibleRanges)zo(this.tree,e,((t,e,n)=>{i.add(t,e,this.markCache[n]||(this.markCache[n]=ti.mark({class:n})))}),n,r);return i.finish()}}const Ha=G.high(Mi.fromClass(Fa,{decorations:t=>t.decorations})),Ka=za.define([{tag:la.meta,color:"#404740"},{tag:la.link,textDecoration:"underline"},{tag:la.heading,textDecoration:"underline",fontWeight:"bold"},{tag:la.emphasis,fontStyle:"italic"},{tag:la.strong,fontWeight:"bold"},{tag:la.strikethrough,textDecoration:"line-through"},{tag:la.keyword,color:"#708"},{tag:[la.atom,la.bool,la.url,la.contentSeparator,la.labelName],color:"#219"},{tag:[la.literal,la.inserted],color:"#164"},{tag:[la.string,la.deleted],color:"#a11"},{tag:[la.regexp,la.escape,la.special(la.string)],color:"#e40"},{tag:la.definition(la.variableName),color:"#00f"},{tag:la.local(la.variableName),color:"#30a"},{tag:[la.typeName,la.namespace],color:"#085"},{tag:la.className,color:"#167"},{tag:[la.special(la.variableName),la.macroName],color:"#256"},{tag:la.definition(la.propertyName),color:"#00c"},{tag:la.comment,color:"#940"},{tag:la.invalid,color:"#f00"}]),Ja=Dr.baseTheme({"&.cm-focused .cm-matchingBracket":{backgroundColor:"#328c8252"},"&.cm-focused .cm-nonmatchingBracket":{backgroundColor:"#bb555544"}}),tl=1e4,el="()[]{}",il=R.define({combine:t=>kt(t,{afterCursor:!0,brackets:el,maxScanDistance:tl,renderMatch:sl})}),nl=ti.mark({class:"cm-matchingBracket"}),rl=ti.mark({class:"cm-nonmatchingBracket"});function sl(t){let e=[],i=t.matched?nl:rl;return e.push(i.range(t.start.from,t.start.to)),t.end&&e.push(i.range(t.end.from,t.end.to)),e}const ol=D.define({create:()=>ti.none,update(t,e){if(!e.docChanged&&!e.selection)return t;let i=[],n=e.state.facet(il);for(let t of e.state.selection.ranges){if(!t.empty)continue;let r=pl(e.state,t.head,-1,n)||t.head>0&&pl(e.state,t.head-1,1,n)||n.afterCursor&&(pl(e.state,t.head,1,n)||t.headDr.decorations.from(t)}),al=[ol,Ja];function ll(t={}){return[il.of(t),al]}const hl=new Ls;function cl(t,e,i){let n=t.prop(e<0?Ls.openedBy:Ls.closedBy);if(n)return n;if(1==t.name.length){let n=i.indexOf(t.name);if(n>-1&&n%2==(e<0?1:0))return[i[n+e]]}return null}function ul(t){let e=t.type.prop(hl);return e?e(t.node):t}function pl(t,e,i,n={}){let r=n.maxScanDistance||tl,s=n.brackets||el,o=ga(t),a=o.resolveInner(e,i);for(let n=a;n;n=n.parent){let r=cl(n.type,i,s);if(r&&n.from0?e>=o.from&&eo.from&&e<=o.to))return dl(t,e,i,n,o,r,s)}}return function(t,e,i,n,r,s,o){let a=i<0?t.sliceDoc(e-1,e):t.sliceDoc(e,e+1),l=o.indexOf(a);if(l<0||l%2==0!=i>0)return null;let h={from:i<0?e-1:e,to:i>0?e+1:e},c=t.doc.iterRange(e,i>0?t.doc.length:0),u=0;for(let t=0;!c.next().done&&t<=s;){let s=c.value;i<0&&(t+=s.length);let a=e+t*i;for(let t=i>0?0:s.length-1,e=i>0?s.length:-1;t!=e;t+=i){let e=o.indexOf(s[t]);if(!(e<0||n.resolveInner(a+t,1).type!=r))if(e%2==0==i>0)u++;else{if(1==u)return{start:h,end:{from:a+t,to:a+t+1},matched:e>>1==l>>1};u--}}i>0&&(t+=s.length)}return c.done?{start:h,matched:!1}:null}(t,e,i,o,a.type,r,s)}function dl(t,e,i,n,r,s,o){let a=n.parent,l={from:r.from,to:r.to},h=0,c=null==a?void 0:a.cursor();if(c&&(i<0?c.childBefore(n.from):c.childAfter(n.to)))do{if(i<0?c.to<=n.from:c.from>=n.to){if(0==h&&s.indexOf(c.type.name)>-1&&c.from-1||(ml.push(t),console.warn(e))}function Sl(t,e){let i=[];for(let n of e.split(" ")){let e=[];for(let i of n.split(".")){let n=t[i]||la[i];n?"function"==typeof n?e.length?e=e.map(n):xl(i,`Modifier ${i} used at start of tag`):e.length?xl(i,`Tag ${i} used as modifier`):e=Array.isArray(n)?n:[n]:xl(i,`Unknown highlighting tag ${i}`)}for(let t of e)i.push(t)}if(!i.length)return 0;let n=e.replace(/ /g,"_"),r=n+" "+i.map((t=>t.id)),s=gl[r];if(s)return s.id;let o=gl[r]=Fs.define({id:Ol.length,name:n,props:[jo({[n]:i})]});return Ol.push(o),o.id}hi.RTL,hi.LTR;function wl(t,e){return({state:i,dispatch:n})=>{if(i.readOnly)return!1;let r=t(e,i);return!!r&&(n(i.update(r)),!0)}}const bl=wl(Zl,0),vl=wl($l,0),kl=wl(((t,e)=>$l(t,e,function(t){let e=[];for(let i of t.selection.ranges){let n=t.doc.lineAt(i.from),r=i.to<=n.to?n:t.doc.lineAt(i.to),s=e.length-1;s>=0&&e[s].to>n.from?e[s].to=r.to:e.push({from:n.from+/^\s*/.exec(n.text)[0].length,to:r.to})}return e}(e))),0);function Ql(t,e){let i=t.languageDataAt("commentTokens",e);return i.length?i[0]:{}}const Pl=50;function $l(t,e,i=e.selection.ranges){let n=i.map((t=>Ql(e,t.from).block));if(!n.every((t=>t)))return null;let r=i.map(((t,i)=>function(t,{open:e,close:i},n,r){let s,o,a=t.sliceDoc(n-Pl,n),l=t.sliceDoc(r,r+Pl),h=/\s*$/.exec(a)[0].length,c=/^\s*/.exec(l)[0].length,u=a.length-h;if(a.slice(u-e.length,u)==e&&l.slice(c,c+i.length)==i)return{open:{pos:n-h,margin:h&&1},close:{pos:r+c,margin:c&&1}};r-n<=2*Pl?s=o=t.sliceDoc(n,r):(s=t.sliceDoc(n,n+Pl),o=t.sliceDoc(r-Pl,r));let p=/^\s*/.exec(s)[0].length,d=/\s*$/.exec(o)[0].length,f=o.length-d-i.length;return s.slice(p,p+e.length)==e&&o.slice(f,f+i.length)==i?{open:{pos:n+p+e.length,margin:/\s/.test(s.charAt(p+e.length))?1:0},close:{pos:r-d-i.length,margin:/\s/.test(o.charAt(f-1))?1:0}}:null}(e,n[i],t.from,t.to)));if(2!=t&&!r.every((t=>t)))return{changes:e.changes(i.map(((t,e)=>r[e]?[]:[{from:t.from,insert:n[e].open+" "},{from:t.to,insert:" "+n[e].close}])))};if(1!=t&&r.some((t=>t))){let t=[];for(let e,i=0;ir&&(t==s||s>l.from)){r=l.from;let t=/^\s*/.exec(l.text)[0].length,e=t==l.length,i=l.text.slice(t,t+a.length)==a?t:-1;tt.comment<0&&(!t.empty||t.single)))){let t=[];for(let{line:e,token:i,indent:r,empty:s,single:o}of n)!o&&s||t.push({from:e.from+r,insert:i+" "});let i=e.changes(t);return{changes:i,selection:e.selection.map(i,1)}}if(1!=t&&n.some((t=>t.comment>=0))){let t=[];for(let{line:e,comment:i,token:r}of n)if(i>=0){let n=e.from+i,s=n+r.length;" "==e.text[s-e.from]&&s++,t.push({from:n,to:s})}return{changes:t}}return null}const Cl=lt.define(),Tl=lt.define(),Al=R.define(),_l=R.define({combine:t=>kt(t,{minDepth:100,newGroupDelay:500,joinToEvent:(t,e)=>e},{minDepth:Math.max,newGroupDelay:Math.min,joinToEvent:(t,e)=>(i,n)=>t(i,n)||e(i,n)})}),El=D.define({create:()=>Fl.empty,update(t,e){let i=e.state.facet(_l),n=e.annotation(Cl);if(n){let r=Il.fromTransaction(e,n.selection),s=n.side,o=0==s?t.undone:t.done;return o=r?jl(o,o.length,i.minDepth,r):zl(o,e.startState.selection),new Fl(0==s?n.rest:o,0==s?o:n.rest)}let r=e.annotation(Tl);if("full"!=r&&"before"!=r||(t=t.isolate()),!1===e.annotation(pt.addToHistory))return e.changes.empty?t:t.addMapping(e.changes.desc);let s=Il.fromTransaction(e),o=e.annotation(pt.time),a=e.annotation(pt.userEvent);return s?t=t.addChanges(s,o,a,i,e):e.selection&&(t=t.addSelection(e.startState.selection,o,a,i.newGroupDelay)),"full"!=r&&"after"!=r||(t=t.isolate()),t},toJSON:t=>({done:t.done.map((t=>t.toJSON())),undone:t.undone.map((t=>t.toJSON()))}),fromJSON:t=>new Fl(t.done.map(Il.fromJSON),t.undone.map(Il.fromJSON))});function Xl(t={}){return[El,_l.of(t),Dr.domEventHandlers({beforeinput(t,e){let i="historyUndo"==t.inputType?Vl:"historyRedo"==t.inputType?Yl:null;return!!i&&(t.preventDefault(),i(e))}})]}function Rl(t,e){return function({state:i,dispatch:n}){if(!e&&i.readOnly)return!1;let r=i.field(El,!1);if(!r)return!1;let s=r.pop(t,i,e);return!!s&&(n(s),!0)}}const Vl=Rl(0,!1),Yl=Rl(1,!1),Wl=Rl(0,!0),ql=Rl(1,!0);class Il{constructor(t,e,i,n,r){this.changes=t,this.effects=e,this.mapped=i,this.startSelection=n,this.selectionsAfter=r}setSelAfter(t){return new Il(this.changes,this.effects,this.mapped,this.startSelection,t)}toJSON(){var t,e,i;return{changes:null===(t=this.changes)||void 0===t?void 0:t.toJSON(),mapped:null===(e=this.mapped)||void 0===e?void 0:e.toJSON(),startSelection:null===(i=this.startSelection)||void 0===i?void 0:i.toJSON(),selectionsAfter:this.selectionsAfter.map((t=>t.toJSON()))}}static fromJSON(t){return new Il(t.changes&&k.fromJSON(t.changes),[],t.mapped&&v.fromJSON(t.mapped),t.startSelection&&_.fromJSON(t.startSelection),t.selectionsAfter.map(_.fromJSON))}static fromTransaction(t,e){let i=Ml;for(let e of t.startState.facet(Al)){let n=e(t);n.length&&(i=i.concat(n))}return!i.length&&t.changes.empty?null:new Il(t.changes.invert(t.startState.doc),i,void 0,e||t.startState.selection,Ml)}static selection(t){return new Il(void 0,Ml,void 0,void 0,t)}}function jl(t,e,i,n){let r=e+1>i+20?e-i-1:0,s=t.slice(r,e);return s.push(n),s}function Dl(t,e){return t.length?e.length?t.concat(e):t:e}const Ml=[],Nl=200;function zl(t,e){if(t.length){let i=t[t.length-1],n=i.selectionsAfter.slice(Math.max(0,i.selectionsAfter.length-Nl));return n.length&&n[n.length-1].eq(e)?t:(n.push(e),jl(t,t.length-1,1e9,i.setSelAfter(n)))}return[Il.selection([e])]}function Bl(t){let e=t[t.length-1],i=t.slice();return i[t.length-1]=e.setSelAfter(e.selectionsAfter.slice(0,e.selectionsAfter.length-1)),i}function Ll(t,e){if(!t.length)return t;let i=t.length,n=Ml;for(;i;){let r=Gl(t[i-1],e,n);if(r.changes&&!r.changes.empty||r.effects.length){let e=t.slice(0,i);return e[i-1]=r,e}e=r.mapped,i--,n=r.selectionsAfter}return n.length?[Il.selection(n)]:Ml}function Gl(t,e,i){let n=Dl(t.selectionsAfter.length?t.selectionsAfter.map((t=>t.map(e))):Ml,i);if(!t.changes)return Il.selection(n);let r=t.changes.map(e),s=e.mapDesc(t.changes,!0),o=t.mapped?t.mapped.composeDesc(s):s;return new Il(r,ut.mapEffects(t.effects,e),o,t.startSelection.map(s),n)}const Ul=/^(input\.type|delete)($|\.)/;class Fl{constructor(t,e,i=0,n=void 0){this.done=t,this.undone=e,this.prevTime=i,this.prevUserEvent=n}isolate(){return this.prevTime?new Fl(this.done,this.undone):this}addChanges(t,e,i,n,r){let s=this.done,o=s[s.length-1];return s=o&&o.changes&&!o.changes.empty&&t.changes&&(!i||Ul.test(i))&&(!o.selectionsAfter.length&&e-this.prevTimei.push(t,e))),e.iterChangedRanges(((t,e,r,s)=>{for(let t=0;t=e&&r<=o&&(n=!0)}})),n}(o.changes,t.changes))||"input.type.compose"==i)?jl(s,s.length-1,n.minDepth,new Il(t.changes.compose(o.changes),Dl(t.effects,o.effects),o.mapped,o.startSelection,Ml)):jl(s,s.length,n.minDepth,t),new Fl(s,Ml,e,i)}addSelection(t,e,i,n){let r=this.done.length?this.done[this.done.length-1].selectionsAfter:Ml;return r.length>0&&e-this.prevTimet.empty!=o.ranges[e].empty)).length)?this:new Fl(zl(this.done,t),this.undone,e,i);var s,o}addMapping(t){return new Fl(Ll(this.done,t),Ll(this.undone,t),this.prevTime,this.prevUserEvent)}pop(t,e,i){let n=0==t?this.done:this.undone;if(0==n.length)return null;let r=n[n.length-1],s=r.selectionsAfter[0]||e.selection;if(i&&r.selectionsAfter.length)return e.update({selection:r.selectionsAfter[r.selectionsAfter.length-1],annotations:Cl.of({side:t,rest:Bl(n),selection:s}),userEvent:0==t?"select.undo":"select.redo",scrollIntoView:!0});if(r.changes){let i=1==n.length?Ml:n.slice(0,n.length-1);return r.mapped&&(i=Ll(i,r.mapped)),e.update({changes:r.changes,selection:r.startSelection,effects:r.effects,annotations:Cl.of({side:t,rest:i,selection:s}),filter:!1,userEvent:0==t?"undo":"redo",scrollIntoView:!0})}return null}}Fl.empty=new Fl(Ml,Ml);const Hl=[{key:"Mod-z",run:Vl,preventDefault:!0},{key:"Mod-y",mac:"Mod-Shift-z",run:Yl,preventDefault:!0},{linux:"Ctrl-Shift-z",run:Yl,preventDefault:!0},{key:"Mod-u",run:Wl,preventDefault:!0},{key:"Alt-u",mac:"Mod-Shift-u",run:ql,preventDefault:!0}];function Kl(t,e){return _.create(t.ranges.map(e),t.mainIndex)}function Jl(t,e){return t.update({selection:e,scrollIntoView:!0,userEvent:"select"})}function th({state:t,dispatch:e},i){let n=Kl(t.selection,i);return!n.eq(t.selection,!0)&&(e(Jl(t,n)),!0)}function eh(t,e){return _.cursor(e?t.to:t.from)}function ih(t,e){return th(t,(i=>i.empty?t.moveByChar(i,e):eh(i,e)))}function nh(t){return t.textDirectionAt(t.state.selection.main.head)==hi.LTR}const rh=t=>ih(t,!nh(t)),sh=t=>ih(t,nh(t));function oh(t,e){return th(t,(i=>i.empty?t.moveByGroup(i,e):eh(i,e)))}function ah(t,e,i){if(e.type.prop(i))return!0;let n=e.to-e.from;return n&&(n>2||/[^\s,.;:]/.test(t.sliceDoc(e.from,e.to)))||e.firstChild}function lh(t,e,i){let n,r,s=ga(t).resolveInner(e.head),o=i?Ls.closedBy:Ls.openedBy;for(let n=e.head;;){let e=i?s.childAfter(n):s.childBefore(n);if(!e)break;ah(t,e,o)?s=e:n=i?e.to:e.from}return r=s.type.prop(o)&&(n=i?pl(t,s.from,1):pl(t,s.to,-1))&&n.matched?i?n.end.to:n.end.from:i?s.to:s.from,_.cursor(r,i?-1:1)}function hh(t,e){return th(t,(i=>{if(!i.empty)return eh(i,e);let n=t.moveVertically(i,e);return n.head!=i.head?n:t.moveToLineBoundary(i,e)}))}const ch=t=>hh(t,!1),uh=t=>hh(t,!0);function ph(t){let e,i=t.scrollDOM.clientHeighti.empty?t.moveVertically(i,e,n.height):eh(i,e)));if(s.eq(r.selection))return!1;if(n.selfScroll){let e=t.coordsAtPos(r.selection.main.head),o=t.scrollDOM.getBoundingClientRect(),a=o.top+n.marginTop,l=o.bottom-n.marginBottom;e&&e.top>a&&e.bottomdh(t,!1),Oh=t=>dh(t,!0);function mh(t,e,i){let n=t.lineBlockAt(e.head),r=t.moveToLineBoundary(e,i);if(r.head==e.head&&r.head!=(i?n.to:n.from)&&(r=t.moveToLineBoundary(e,i,!1)),!i&&r.head==n.from&&n.length){let i=/^\s*/.exec(t.state.sliceDoc(n.from,Math.min(n.from+100,n.to)))[0].length;i&&e.head!=n.from+i&&(r=_.cursor(n.from+i))}return r}function gh(t,e){let i=Kl(t.state.selection,(t=>{let i=e(t);return _.range(t.anchor,i.head,i.goalColumn,i.bidiLevel||void 0)}));return!i.eq(t.state.selection)&&(t.dispatch(Jl(t.state,i)),!0)}function yh(t,e){return gh(t,(i=>t.moveByChar(i,e)))}const xh=t=>yh(t,!nh(t)),Sh=t=>yh(t,nh(t));function wh(t,e){return gh(t,(i=>t.moveByGroup(i,e)))}function bh(t,e){return gh(t,(i=>t.moveVertically(i,e)))}const vh=t=>bh(t,!1),kh=t=>bh(t,!0);function Qh(t,e){return gh(t,(i=>t.moveVertically(i,e,ph(t).height)))}const Ph=t=>Qh(t,!1),$h=t=>Qh(t,!0),Zh=({state:t,dispatch:e})=>(e(Jl(t,{anchor:0})),!0),Ch=({state:t,dispatch:e})=>(e(Jl(t,{anchor:t.doc.length})),!0),Th=({state:t,dispatch:e})=>(e(Jl(t,{anchor:t.selection.main.anchor,head:0})),!0),Ah=({state:t,dispatch:e})=>(e(Jl(t,{anchor:t.selection.main.anchor,head:t.doc.length})),!0);function _h(t,e){if(t.state.readOnly)return!1;let i="delete.selection",{state:n}=t,r=n.changeByRange((n=>{let{from:r,to:s}=n;if(r==s){let o=e(n);or&&(i="delete.forward",o=Eh(t,o,!0)),r=Math.min(r,o),s=Math.max(s,o)}else r=Eh(t,r,!1),s=Eh(t,s,!0);return r==s?{range:n}:{changes:{from:r,to:s},range:_.cursor(r,re(t))))n.between(e,e,((t,n)=>{te&&(e=i?n:t)}));return e}const Xh=(t,e)=>_h(t,(i=>{let n,r,s=i.from,{state:o}=t,a=o.doc.lineAt(s);if(!e&&s>a.from&&sXh(t,!1),Vh=t=>Xh(t,!0),Yh=(t,e)=>_h(t,(i=>{let n=i.head,{state:r}=t,s=r.doc.lineAt(n),o=r.charCategorizer(n);for(let t=null;;){if(n==(e?s.to:s.from)){n==i.head&&s.number!=(e?r.doc.lines:1)&&(n+=e?1:-1);break}let a=f(s.text,n-s.from,e)+s.from,l=s.text.slice(Math.min(n,a)-s.from,Math.max(n,a)-s.from),h=o(l);if(null!=t&&h!=t)break;" "==l&&n==i.head||(t=h),n=a}return n})),Wh=t=>Yh(t,!1);function qh(t){let e=[],i=-1;for(let n of t.selection.ranges){let r=t.doc.lineAt(n.from),s=t.doc.lineAt(n.to);if(n.empty||n.to!=s.from||(s=t.doc.lineAt(n.to-1)),i>=r.number){let t=e[e.length-1];t.to=s.to,t.ranges.push(n)}else e.push({from:r.from,to:s.to,ranges:[n]});i=s.number+1}return e}function Ih(t,e,i){if(t.readOnly)return!1;let n=[],r=[];for(let e of qh(t)){if(i?e.to==t.doc.length:0==e.from)continue;let s=t.doc.lineAt(i?e.to+1:e.from-1),o=s.length+1;if(i){n.push({from:e.to,to:s.to},{from:e.from,insert:s.text+t.lineBreak});for(let i of e.ranges)r.push(_.range(Math.min(t.doc.length,i.anchor+o),Math.min(t.doc.length,i.head+o)))}else{n.push({from:s.from,to:e.from},{from:e.to,insert:t.lineBreak+s.text});for(let t of e.ranges)r.push(_.range(t.anchor-o,t.head-o))}}return!!n.length&&(e(t.update({changes:n,scrollIntoView:!0,selection:_.create(r,t.selection.mainIndex),userEvent:"move.line"})),!0)}function jh(t,e,i){if(t.readOnly)return!1;let n=[];for(let e of qh(t))i?n.push({from:e.from,insert:t.doc.slice(e.from,e.to)+t.lineBreak}):n.push({from:e.to,insert:t.lineBreak+t.doc.slice(e.from,e.to)});return e(t.update({changes:n,scrollIntoView:!0,userEvent:"input.copyline"})),!0}const Dh=Mh(!1);function Mh(e){return({state:i,dispatch:n})=>{if(i.readOnly)return!1;let r=i.changeByRange((n=>{let{from:r,to:s}=n,o=i.doc.lineAt(r),a=!e&&r==s&&function(t,e){if(/\(\)|\[\]|\{\}/.test(t.sliceDoc(e-1,e+1)))return{from:e,to:e};let i,n=ga(t).resolveInner(e),r=n.childBefore(e),s=n.childAfter(e);return r&&s&&r.to<=e&&s.from>=e&&(i=r.type.prop(Ls.closedBy))&&i.indexOf(s.name)>-1&&t.doc.lineAt(r.to).from==t.doc.lineAt(s.from).from&&!/\S/.test(t.sliceDoc(r.to,s.from))?{from:r.to,to:s.from}:null}(i,r);e&&(r=s=(s<=o.to?o:i.doc.lineAt(s)).to);let l=new Ea(i,{simulateBreak:r,simulateDoubleBreak:!!a}),h=_a(l,r);for(null==h&&(h=jt(/^\s*/.exec(i.doc.lineAt(r).text)[0],i.tabSize));so.from&&r{let r=[];for(let s=n.from;s<=n.to;){let o=t.doc.lineAt(s);o.number>i&&(n.empty||n.to>o.from)&&(e(o,r,n),i=o.number),s=o.to+1}let s=t.changes(r);return{changes:r,range:_.range(s.mapPos(n.anchor,1),s.mapPos(n.head,1))}}))}const zh=[{key:"Alt-ArrowLeft",mac:"Ctrl-ArrowLeft",run:t=>th(t,(e=>lh(t.state,e,!nh(t)))),shift:t=>gh(t,(e=>lh(t.state,e,!nh(t))))},{key:"Alt-ArrowRight",mac:"Ctrl-ArrowRight",run:t=>th(t,(e=>lh(t.state,e,nh(t)))),shift:t=>gh(t,(e=>lh(t.state,e,nh(t))))},{key:"Alt-ArrowUp",run:({state:t,dispatch:e})=>Ih(t,e,!1)},{key:"Shift-Alt-ArrowUp",run:({state:t,dispatch:e})=>jh(t,e,!1)},{key:"Alt-ArrowDown",run:({state:t,dispatch:e})=>Ih(t,e,!0)},{key:"Shift-Alt-ArrowDown",run:({state:t,dispatch:e})=>jh(t,e,!0)},{key:"Escape",run:({state:t,dispatch:e})=>{let i=t.selection,n=null;return i.ranges.length>1?n=_.create([i.main]):i.main.empty||(n=_.create([_.cursor(i.main.head)])),!!n&&(e(Jl(t,n)),!0)}},{key:"Mod-Enter",run:Mh(!0)},{key:"Alt-l",mac:"Ctrl-l",run:({state:t,dispatch:e})=>{let i=qh(t).map((({from:e,to:i})=>_.range(e,Math.min(i+1,t.doc.length))));return e(t.update({selection:_.create(i),userEvent:"select"})),!0}},{key:"Mod-i",run:({state:t,dispatch:e})=>{let i=Kl(t.selection,(e=>{var i;for(let n=ga(t).resolveStack(e.from,1);n;n=n.next){let{node:t}=n;if((t.from=e.to||t.to>e.to&&t.from<=e.from)&&(null===(i=t.parent)||void 0===i?void 0:i.parent))return _.range(t.to,t.from)}return e}));return e(Jl(t,i)),!0},preventDefault:!0},{key:"Mod-[",run:({state:t,dispatch:e})=>!t.readOnly&&(e(t.update(Nh(t,((e,i)=>{let n=/^\s*/.exec(e.text)[0];if(!n)return;let r=jt(n,t.tabSize),s=0,o=Aa(t,Math.max(0,r-Ta(t)));for(;s!t.readOnly&&(e(t.update(Nh(t,((e,i)=>{i.push({from:e.from,insert:t.facet(Ca)})})),{userEvent:"input.indent"})),!0)},{key:"Mod-Alt-\\",run:({state:t,dispatch:e})=>{if(t.readOnly)return!1;let i=Object.create(null),n=new Ea(t,{overrideIndentation:t=>{let e=i[t];return null==e?-1:e}}),r=Nh(t,((e,r,s)=>{let o=_a(n,e.from);if(null==o)return;/\S/.test(e.text)||(o=0);let a=/^\s*/.exec(e.text)[0],l=Aa(t,o);(a!=l||s.from{if(t.state.readOnly)return!1;let{state:e}=t,i=e.changes(qh(e).map((({from:t,to:i})=>(t>0?t--:it.moveVertically(e,!0))).map(i);return t.dispatch({changes:i,selection:n,scrollIntoView:!0,userEvent:"delete.line"}),!0}},{key:"Shift-Mod-\\",run:({state:t,dispatch:e})=>function(t,e,i){let n=!1,r=Kl(t.selection,(e=>{let r=pl(t,e.head,-1)||pl(t,e.head,1)||e.head>0&&pl(t,e.head-1,1)||e.head{let{state:e}=t,i=e.doc.lineAt(e.selection.main.from),n=Ql(t.state,i.from);return n.line?bl(t):!!n.block&&kl(t)}},{key:"Alt-A",run:vl}].concat([{key:"ArrowLeft",run:rh,shift:xh,preventDefault:!0},{key:"Mod-ArrowLeft",mac:"Alt-ArrowLeft",run:t=>oh(t,!nh(t)),shift:t=>wh(t,!nh(t)),preventDefault:!0},{mac:"Cmd-ArrowLeft",run:t=>th(t,(e=>mh(t,e,!nh(t)))),shift:t=>gh(t,(e=>mh(t,e,!nh(t)))),preventDefault:!0},{key:"ArrowRight",run:sh,shift:Sh,preventDefault:!0},{key:"Mod-ArrowRight",mac:"Alt-ArrowRight",run:t=>oh(t,nh(t)),shift:t=>wh(t,nh(t)),preventDefault:!0},{mac:"Cmd-ArrowRight",run:t=>th(t,(e=>mh(t,e,nh(t)))),shift:t=>gh(t,(e=>mh(t,e,nh(t)))),preventDefault:!0},{key:"ArrowUp",run:ch,shift:vh,preventDefault:!0},{mac:"Cmd-ArrowUp",run:Zh,shift:Th},{mac:"Ctrl-ArrowUp",run:fh,shift:Ph},{key:"ArrowDown",run:uh,shift:kh,preventDefault:!0},{mac:"Cmd-ArrowDown",run:Ch,shift:Ah},{mac:"Ctrl-ArrowDown",run:Oh,shift:$h},{key:"PageUp",run:fh,shift:Ph},{key:"PageDown",run:Oh,shift:$h},{key:"Home",run:t=>th(t,(e=>mh(t,e,!1))),shift:t=>gh(t,(e=>mh(t,e,!1))),preventDefault:!0},{key:"Mod-Home",run:Zh,shift:Th},{key:"End",run:t=>th(t,(e=>mh(t,e,!0))),shift:t=>gh(t,(e=>mh(t,e,!0))),preventDefault:!0},{key:"Mod-End",run:Ch,shift:Ah},{key:"Enter",run:Dh},{key:"Mod-a",run:({state:t,dispatch:e})=>(e(t.update({selection:{anchor:0,head:t.doc.length},userEvent:"select"})),!0)},{key:"Backspace",run:Rh,shift:Rh},{key:"Delete",run:Vh},{key:"Mod-Backspace",mac:"Alt-Backspace",run:Wh},{key:"Mod-Delete",mac:"Alt-Delete",run:t=>Yh(t,!0)},{mac:"Mod-Backspace",run:t=>_h(t,(e=>{let i=t.moveToLineBoundary(e,!1).head;return e.head>i?i:Math.max(0,e.head-1)}))},{mac:"Mod-Delete",run:t=>_h(t,(e=>{let i=t.moveToLineBoundary(e,!0).head;return e.headth(t,(e=>_.cursor(t.lineBlockAt(e.head).from,1))),shift:t=>gh(t,(e=>_.cursor(t.lineBlockAt(e.head).from)))},{key:"Ctrl-e",run:t=>th(t,(e=>_.cursor(t.lineBlockAt(e.head).to,-1))),shift:t=>gh(t,(e=>_.cursor(t.lineBlockAt(e.head).to)))},{key:"Ctrl-d",run:Vh},{key:"Ctrl-h",run:Rh},{key:"Ctrl-k",run:t=>_h(t,(e=>{let i=t.lineBlockAt(e.head).to;return e.head{if(e.readOnly)return!1;let n=e.changeByRange((e=>({changes:{from:e.from,to:e.to,insert:t.of(["",""])},range:_.cursor(e.from)})));return i(e.update(n,{scrollIntoView:!0,userEvent:"input"})),!0}},{key:"Ctrl-t",run:({state:t,dispatch:e})=>{if(t.readOnly)return!1;let i=t.changeByRange((e=>{if(!e.empty||0==e.from||e.from==t.doc.length)return{range:e};let i=e.from,n=t.doc.lineAt(i),r=i==n.from?i-1:f(n.text,i-n.from,!1)+n.from,s=i==n.to?i+1:f(n.text,i-n.from,!0)+n.from;return{changes:{from:r,to:s,insert:t.doc.slice(i,s).append(t.doc.slice(r,i))},range:_.cursor(s)}}));return!i.changes.empty&&(e(t.update(i,{scrollIntoView:!0,userEvent:"move.character"})),!0)}},{key:"Ctrl-v",run:Oh}].map((t=>({mac:t.key,run:t.run,shift:t.shift})))));function Bh(t){let e=Object.keys(t).join(""),i=/\w/.test(e);return i&&(e=e.replace(/\w/g,"")),`[${i?"\\w":""}${e.replace(/[^\w\s]/g,"\\$&")}]`}function Lh(t){let e=t.map((t=>"string"==typeof t?{label:t}:t)),[i,n]=e.every((t=>/^\w+$/.test(t.label)))?[/\w*$/,/\w+$/]:function(t){let e=Object.create(null),i=Object.create(null);for(let{label:n}of t){e[n[0]]=!0;for(let t=1;t{let r=t.matchBefore(n);return r||t.explicit?{from:r?r.from:t.pos,options:e,validFor:i}:null}}const Gh=lt.define(),Uh=Dr.baseTheme({".cm-tooltip.cm-tooltip-autocomplete":{"& > ul":{fontFamily:"monospace",whiteSpace:"nowrap",overflow:"hidden auto",maxWidth_fallback:"700px",maxWidth:"min(700px, 95vw)",minWidth:"250px",maxHeight:"10em",height:"100%",listStyle:"none",margin:0,padding:0,"& > li, & > completion-section":{padding:"1px 3px",lineHeight:1.2},"& > li":{overflowX:"hidden",textOverflow:"ellipsis",cursor:"pointer"},"& > completion-section":{display:"list-item",borderBottom:"1px solid silver",paddingLeft:"0.5em",opacity:.7}}},"&light .cm-tooltip-autocomplete ul li[aria-selected]":{background:"#17c",color:"white"},"&light .cm-tooltip-autocomplete-disabled ul li[aria-selected]":{background:"#777"},"&dark .cm-tooltip-autocomplete ul li[aria-selected]":{background:"#347",color:"white"},"&dark .cm-tooltip-autocomplete-disabled ul li[aria-selected]":{background:"#444"},".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after":{content:'"···"',opacity:.5,display:"block",textAlign:"center"},".cm-tooltip.cm-completionInfo":{position:"absolute",padding:"3px 9px",width:"max-content",maxWidth:"400px",boxSizing:"border-box"},".cm-completionInfo.cm-completionInfo-left":{right:"100%"},".cm-completionInfo.cm-completionInfo-right":{left:"100%"},".cm-completionInfo.cm-completionInfo-left-narrow":{right:"30px"},".cm-completionInfo.cm-completionInfo-right-narrow":{left:"30px"},"&light .cm-snippetField":{backgroundColor:"#00000022"},"&dark .cm-snippetField":{backgroundColor:"#ffffff22"},".cm-snippetFieldPosition":{verticalAlign:"text-top",width:0,height:"1.15em",display:"inline-block",margin:"0 -0.7px -.7em",borderLeft:"1.4px dotted #888"},".cm-completionMatchedText":{textDecoration:"underline"},".cm-completionDetail":{marginLeft:"0.5em",fontStyle:"italic"},".cm-completionIcon":{fontSize:"90%",width:".8em",display:"inline-block",textAlign:"center",paddingRight:".6em",opacity:"0.6",boxSizing:"content-box"},".cm-completionIcon-function, .cm-completionIcon-method":{"&:after":{content:"'ƒ'"}},".cm-completionIcon-class":{"&:after":{content:"'○'"}},".cm-completionIcon-interface":{"&:after":{content:"'◌'"}},".cm-completionIcon-variable":{"&:after":{content:"'𝑥'"}},".cm-completionIcon-constant":{"&:after":{content:"'𝐶'"}},".cm-completionIcon-type":{"&:after":{content:"'𝑡'"}},".cm-completionIcon-enum":{"&:after":{content:"'∪'"}},".cm-completionIcon-property":{"&:after":{content:"'□'"}},".cm-completionIcon-keyword":{"&:after":{content:"'🔑︎'"}},".cm-completionIcon-namespace":{"&:after":{content:"'▢'"}},".cm-completionIcon-text":{"&:after":{content:"'abc'",fontSize:"50%",verticalAlign:"middle"}}});class Fh{constructor(t,e,i,n){this.field=t,this.line=e,this.from=i,this.to=n}}class Hh{constructor(t,e,i){this.field=t,this.from=e,this.to=i}map(t){let e=t.mapPos(this.from,-1,b.TrackDel),i=t.mapPos(this.to,1,b.TrackDel);return null==e||null==i?null:new Hh(this.field,e,i)}}class Kh{constructor(t,e){this.lines=t,this.fieldPositions=e}instantiate(t,e){let i=[],n=[e],r=t.doc.lineAt(e),s=/^\s*/.exec(r.text)[0];for(let r of this.lines){if(i.length){let i=s,o=/^\t*/.exec(r)[0].length;for(let e=0;enew Hh(t.field,n[t.line]+t.from,n[t.line]+t.to)));return{text:i,ranges:o}}static parse(t){let e,i=[],n=[],r=[];for(let s of t.split(/\r\n?|\n/)){for(;e=/[#$]\{(?:(\d+)(?::([^}]*))?|([^}]*))\}/.exec(s);){let t=e[1]?+e[1]:null,o=e[2]||e[3]||"",a=-1;for(let e=0;e=a&&t.field++}r.push(new Fh(a,n.length,e.index,e.index+o.length)),s=s.slice(0,e.index)+o+s.slice(e.index+e[0].length)}for(let t;t=/\\([{}])/.exec(s);){s=s.slice(0,t.index)+t[1]+s.slice(t.index+t[0].length);for(let e of r)e.line==n.length&&e.from>t.index&&(e.from--,e.to--)}n.push(s)}return new Kh(n,r)}}let Jh=ti.widget({widget:new class extends Ke{toDOM(){let t=document.createElement("span");return t.className="cm-snippetFieldPosition",t}ignoreEvent(){return!1}}}),tc=ti.mark({class:"cm-snippetField"});class ec{constructor(t,e){this.ranges=t,this.active=e,this.deco=ti.set(t.map((t=>(t.from==t.to?Jh:tc).range(t.from,t.to))))}map(t){let e=[];for(let i of this.ranges){let n=i.map(t);if(!n)return null;e.push(n)}return new ec(e,this.active)}selectionInsideField(t){return t.ranges.every((t=>this.ranges.some((e=>e.field==this.active&&e.from<=t.from&&e.to>=t.to))))}}const ic=ut.define({map:(t,e)=>t&&t.map(e)}),nc=ut.define(),rc=D.define({create:()=>null,update(t,e){for(let i of e.effects){if(i.is(ic))return i.value;if(i.is(nc)&&t)return new ec(t.ranges,i.value)}return t&&e.docChanged&&(t=t.map(e.changes)),t&&e.selection&&!t.selectionInsideField(e.selection)&&(t=null),t},provide:t=>Dr.decorations.from(t,(t=>t?t.deco:ti.none))});function sc(t,e){return _.create(t.filter((t=>t.field==e)).map((t=>_.range(t.from,t.to))))}function oc(e){let i=Kh.parse(e);return(e,n,r,s)=>{let{text:o,ranges:a}=i.instantiate(e.state,r),l={changes:{from:r,to:s,insert:t.of(o)},scrollIntoView:!0,annotations:n?[Gh.of(n),pt.userEvent.of("input.complete")]:void 0};if(a.length&&(l.selection=sc(a,0)),a.some((t=>t.field>0))){let t=new ec(a,0),i=l.effects=[ic.of(t)];void 0===e.state.field(rc,!1)&&i.push(ut.appendConfig.of([rc,cc,pc,Uh]))}e.dispatch(e.state.update(l))}}function ac(t){return({state:e,dispatch:i})=>{let n=e.field(rc,!1);if(!n||t<0&&0==n.active)return!1;let r=n.active+t,s=t>0&&!n.ranges.some((e=>e.field==r+t));return i(e.update({selection:sc(n.ranges,r),effects:ic.of(s?null:new ec(n.ranges,r)),scrollIntoView:!0})),!0}}const lc=[{key:"Tab",run:ac(1),shift:ac(-1)},{key:"Escape",run:({state:t,dispatch:e})=>!!t.field(rc,!1)&&(e(t.update({effects:ic.of(null)})),!0)}],hc=R.define({combine:t=>t.length?t[0]:lc}),cc=G.highest(Fr.compute([hc],(t=>t.facet(hc))));function uc(t,e){return Object.assign(Object.assign({},e),{apply:oc(t)})}const pc=Dr.domEventHandlers({mousedown(t,e){let i,n=e.state.field(rc,!1);if(!n||null==(i=e.posAtCoords({x:t.clientX,y:t.clientY})))return!1;let r=n.ranges.find((t=>t.from<=i&&t.to>=i));return!(!r||r.field==n.active)&&(e.dispatch({selection:sc(n.ranges,r.field),effects:ic.of(n.ranges.some((t=>t.field>r.field))?new ec(n.ranges,r.field):null),scrollIntoView:!0}),!0)}}),dc=new class extends Qt{};dc.startSide=1,dc.endSide=-1;const fc=(()=>[bs(),Xl(),ls(),Ua(Ka,{fallback:!0}),Fr.of([...zh,...Hl])])();class Oc{constructor(t,e,i,n,r,s,o,a,l,h=0,c){this.p=t,this.stack=e,this.state=i,this.reducePos=n,this.pos=r,this.score=s,this.buffer=o,this.bufferBase=a,this.curContext=l,this.lookAhead=h,this.parent=c}toString(){return`[${this.stack.filter(((t,e)=>e%3==0)).concat(this.state)}]@${this.pos}${this.score?"!"+this.score:""}`}static start(t,e,i=0){let n=t.parser.context;return new Oc(t,[],e,i,i,0,[],0,n?new mc(n,n.start):null,0,null)}get context(){return this.curContext?this.curContext.context:null}pushState(t,e){this.stack.push(this.state,e,this.bufferBase+this.buffer.length),this.state=t}reduce(t){var e;let i=t>>19,n=65535&t,{parser:r}=this.p,s=r.dynamicPrecedence(n);if(s&&(this.score+=s),0==i)return this.pushState(r.getGoto(this.state,n,!0),this.reducePos),n=2e3&&!(null===(e=this.p.parser.nodeSet.types[n])||void 0===e?void 0:e.isAnonymous)&&(a==this.p.lastBigReductionStart?(this.p.bigReductionCount++,this.p.lastBigReductionSize=l):this.p.lastBigReductionSizeo;)this.stack.pop();this.reduceContext(n,a)}storeNode(t,e,i,n=4,r=!1){if(0==t&&(!this.stack.length||this.stack[this.stack.length-1]0&&0==t.buffer[n-4]&&t.buffer[n-1]>-1){if(e==i)return;if(t.buffer[n-2]>=e)return void(t.buffer[n-2]=i)}}if(r&&this.pos!=i){let r=this.buffer.length;if(r>0&&0!=this.buffer[r-4])for(;r>0&&this.buffer[r-2]>i;)this.buffer[r]=this.buffer[r-4],this.buffer[r+1]=this.buffer[r-3],this.buffer[r+2]=this.buffer[r-2],this.buffer[r+3]=this.buffer[r-1],r-=4,n>4&&(n-=4);this.buffer[r]=t,this.buffer[r+1]=e,this.buffer[r+2]=i,this.buffer[r+3]=n}else this.buffer.push(t,e,i,n)}shift(t,e,i,n){if(131072&t)this.pushState(65535&t,this.pos);else if(262144&t)this.pos=n,this.shiftContext(e,i),e<=this.p.parser.maxNode&&this.buffer.push(e,i,n,4);else{let r=t,{parser:s}=this.p;(n>this.pos||e<=s.maxNode)&&(this.pos=n,s.stateFlag(r,1)||(this.reducePos=n)),this.pushState(r,i),this.shiftContext(e,i),e<=s.maxNode&&this.buffer.push(e,i,n,4)}}apply(t,e,i,n){65536&t?this.reduce(t):this.shift(t,e,i,n)}useNode(t,e){let i=this.p.reused.length-1;(i<0||this.p.reused[i]!=t)&&(this.p.reused.push(t),i++);let n=this.pos;this.reducePos=this.pos=n+t.length,this.pushState(e,n),this.buffer.push(i,n,this.reducePos,-1),this.curContext&&this.updateContext(this.curContext.tracker.reuse(this.curContext.context,t,this,this.p.stream.reset(this.pos-t.length)))}split(){let t=this,e=t.buffer.length;for(;e>0&&t.buffer[e-2]>t.reducePos;)e-=4;let i=t.buffer.slice(e),n=t.bufferBase+e;for(;t&&n==t.bufferBase;)t=t.parent;return new Oc(this.p,this.stack.slice(),this.state,this.reducePos,this.pos,this.score,i,n,this.curContext,this.lookAhead,t)}recoverByDelete(t,e){let i=t<=this.p.parser.maxNode;i&&this.storeNode(t,this.pos,e,4),this.storeNode(0,this.pos,e,i?8:4),this.pos=this.reducePos=e,this.score-=190}canShift(t){for(let e=new gc(this);;){let i=this.p.parser.stateSlot(e.state,4)||this.p.parser.hasAction(e.state,t);if(0==i)return!1;if(!(65536&i))return!0;e.reduce(i)}}recoverByInsert(t){if(this.stack.length>=300)return[];let e=this.p.parser.nextStates(this.state);if(e.length>8||this.stack.length>=120){let i=[];for(let n,r=0;r1&e&&t==n))||i.push(e[t],n)}e=i}let i=[];for(let t=0;t>19,n=65535&e,r=this.stack.length-3*i;if(r<0||t.getGoto(this.stack[r],n,!1)<0){let t=this.findForcedReduction();if(null==t)return!1;e=t}this.storeNode(0,this.pos,this.pos,4,!0),this.score-=100}return this.reducePos=this.pos,this.reduce(e),!0}findForcedReduction(){let{parser:t}=this.p,e=[],i=(n,r)=>{if(!e.includes(n))return e.push(n),t.allActions(n,(e=>{if(393216&e);else if(65536&e){let i=(e>>19)-r;if(i>1){let n=65535&e,r=this.stack.length-3*i;if(r>=0&&t.getGoto(this.stack[r],n,!1)>=0)return i<<19|65536|n}}else{let t=i(e,r+1);if(null!=t)return t}}))};return i(this.state,0)}forceAll(){for(;!this.p.parser.stateFlag(this.state,2);)if(!this.forceReduce()){this.storeNode(0,this.pos,this.pos,4,!0);break}return this}get deadEnd(){if(3!=this.stack.length)return!1;let{parser:t}=this.p;return 65535==t.data[t.stateSlot(this.state,1)]&&!t.stateSlot(this.state,4)}restart(){this.storeNode(0,this.pos,this.pos,4,!0),this.state=this.stack[0],this.stack.length=0}sameState(t){if(this.state!=t.state||this.stack.length!=t.stack.length)return!1;for(let e=0;ethis.lookAhead&&(this.emitLookAhead(),this.lookAhead=t)}close(){this.curContext&&this.curContext.tracker.strict&&this.emitContext(),this.lookAhead>0&&this.emitLookAhead()}}class mc{constructor(t,e){this.tracker=t,this.context=e,this.hash=t.strict?t.hash(e):0}}class gc{constructor(t){this.start=t,this.state=t.state,this.stack=t.stack,this.base=this.stack.length}reduce(t){let e=65535&t,i=t>>19;0==i?(this.stack==this.start.stack&&(this.stack=this.stack.slice()),this.stack.push(this.state,0,0),this.base+=3):this.base-=3*(i-1);let n=this.start.p.parser.getGoto(this.stack[this.base-3],e,!0);this.state=n}}class yc{constructor(t,e,i){this.stack=t,this.pos=e,this.index=i,this.buffer=t.buffer,0==this.index&&this.maybeNext()}static create(t,e=t.bufferBase+t.buffer.length){return new yc(t,e,e-t.bufferBase)}maybeNext(){let t=this.stack.parent;null!=t&&(this.index=this.stack.bufferBase-t.bufferBase,this.stack=t,this.buffer=t.buffer)}get id(){return this.buffer[this.index-4]}get start(){return this.buffer[this.index-3]}get end(){return this.buffer[this.index-2]}get size(){return this.buffer[this.index-1]}next(){this.index-=4,this.pos-=4,0==this.index&&this.maybeNext()}fork(){return new yc(this.stack,this.pos,this.index)}}function xc(t,e=Uint16Array){if("string"!=typeof t)return t;let i=null;for(let n=0,r=0;n=92&&e--,e>=34&&e--;let r=e-32;if(r>=46&&(r-=46,i=!0),s+=r,i)break;s*=46}i?i[r++]=s:i=new e(s)}return i}class Sc{constructor(){this.start=-1,this.value=-1,this.end=-1,this.extended=-1,this.lookAhead=0,this.mask=0,this.context=0}}const wc=new Sc;class bc{constructor(t,e){this.input=t,this.ranges=e,this.chunk="",this.chunkOff=0,this.chunk2="",this.chunk2Pos=0,this.next=-1,this.token=wc,this.rangeIndex=0,this.pos=this.chunkPos=e[0].from,this.range=e[0],this.end=e[e.length-1].to,this.readNext()}resolveOffset(t,e){let i=this.range,n=this.rangeIndex,r=this.pos+t;for(;ri.to:r>=i.to;){if(n==this.ranges.length-1)return null;let t=this.ranges[++n];r+=t.from-i.to,i=t}return r}clipPos(t){if(t>=this.range.from&&tt)return Math.max(t,e.from);return this.end}peek(t){let e,i,n=this.chunkOff+t;if(n>=0&&n=this.chunk2Pos&&en.to&&(this.chunk2=this.chunk2.slice(0,n.to-e)),i=this.chunk2.charCodeAt(0)}}return e>=this.token.lookAhead&&(this.token.lookAhead=e+1),i}acceptToken(t,e=0){let i=e?this.resolveOffset(e,-1):this.pos;if(null==i||i=this.chunk2Pos&&this.posthis.range.to?t.slice(0,this.range.to-this.pos):t,this.chunkPos=this.pos,this.chunkOff=0}}readNext(){return this.chunkOff>=this.chunk.length&&(this.getChunk(),this.chunkOff==this.chunk.length)?this.next=-1:this.next=this.chunk.charCodeAt(this.chunkOff)}advance(t=1){for(this.chunkOff+=t;this.pos+t>=this.range.to;){if(this.rangeIndex==this.ranges.length-1)return this.setDone();t-=this.range.to-this.pos,this.range=this.ranges[++this.rangeIndex],this.pos=this.range.from}return this.pos+=t,this.pos>=this.token.lookAhead&&(this.token.lookAhead=this.pos+1),this.readNext()}setDone(){return this.pos=this.chunkPos=this.end,this.range=this.ranges[this.rangeIndex=this.ranges.length-1],this.chunk="",this.next=-1}reset(t,e){if(e?(this.token=e,e.start=t,e.lookAhead=t+1,e.value=e.extended=-1):this.token=wc,this.pos!=t){if(this.pos=t,t==this.end)return this.setDone(),this;for(;t=this.range.to;)this.range=this.ranges[++this.rangeIndex];t>=this.chunkPos&&t=this.chunkPos&&e<=this.chunkPos+this.chunk.length)return this.chunk.slice(t-this.chunkPos,e-this.chunkPos);if(t>=this.chunk2Pos&&e<=this.chunk2Pos+this.chunk2.length)return this.chunk2.slice(t-this.chunk2Pos,e-this.chunk2Pos);if(t>=this.range.from&&e<=this.range.to)return this.input.read(t,e);let i="";for(let n of this.ranges){if(n.from>=e)break;n.to>t&&(i+=this.input.read(Math.max(n.from,t),Math.min(n.to,e)))}return i}}class vc{constructor(t,e){this.data=t,this.id=e}token(t,e){let{parser:i}=e.p;Pc(this.data,t,e,this.id,i.data,i.tokenPrecTable)}}vc.prototype.contextual=vc.prototype.fallback=vc.prototype.extend=!1;class kc{constructor(t,e,i){this.precTable=e,this.elseToken=i,this.data="string"==typeof t?xc(t):t}token(t,e){let i=t.pos,n=0;for(;;){let i=t.next<0,r=t.resolveOffset(1,1);if(Pc(this.data,t,e,0,this.data,this.precTable),t.token.value>-1)break;if(null==this.elseToken)return;if(i||n++,null==r)break;t.reset(r,t.token)}n&&(t.reset(i,t.token),t.acceptToken(this.elseToken,n))}}kc.prototype.contextual=vc.prototype.fallback=vc.prototype.extend=!1;class Qc{constructor(t,e={}){this.token=t,this.contextual=!!e.contextual,this.fallback=!!e.fallback,this.extend=!!e.extend}}function Pc(t,e,i,n,r,s){let o=0,a=1<0){let i=t[n];if(l.allows(i)&&(-1==e.token.value||e.token.value==i||Zc(i,e.token.value,r,s))){e.acceptToken(i);break}}let n=e.next,h=0,c=t[o+2];if(!(e.next<0&&c>h&&65535==t[i+3*c-3])){for(;h>1,s=i+r+(r<<1),a=t[s],l=t[s+1]||65536;if(n=l)){o=t[s+2],e.advance();continue t}h=r+1}}break}o=t[i+3*c-1]}}function $c(t,e,i){for(let n,r=e;65535!=(n=t[r]);r++)if(n==i)return r-e;return-1}function Zc(t,e,i,n){let r=$c(i,n,e);return r<0||$c(i,n,t)e)&&!n.type.isError)return i<0?Math.max(0,Math.min(n.to-1,e-25)):Math.min(t.length,Math.max(n.from+1,e+25));if(i<0?n.prevSibling():n.nextSibling())break;if(!n.parent())return i<0?0:t.length}}class _c{constructor(t,e){this.fragments=t,this.nodeSet=e,this.i=0,this.fragment=null,this.safeFrom=-1,this.safeTo=-1,this.trees=[],this.start=[],this.index=[],this.nextFragment()}nextFragment(){let t=this.fragment=this.i==this.fragments.length?null:this.fragments[this.i++];if(t){for(this.safeFrom=t.openStart?Ac(t.tree,t.from+t.offset,1)-t.offset:t.from,this.safeTo=t.openEnd?Ac(t.tree,t.to+t.offset,-1)-t.offset:t.to;this.trees.length;)this.trees.pop(),this.start.pop(),this.index.pop();this.trees.push(t.tree),this.start.push(-t.offset),this.index.push(0),this.nextStart=this.safeFrom}else this.nextStart=1e9}nodeAt(t){if(tt)return this.nextStart=s,null;if(r instanceof eo){if(s==t){if(s=Math.max(this.safeFrom,t)&&(this.trees.push(r),this.start.push(s),this.index.push(0))}else this.index[e]++,this.nextStart=s+r.length}}}class Ec{constructor(t,e){this.stream=e,this.tokens=[],this.mainToken=null,this.actions=[],this.tokens=t.tokenizers.map((t=>new Sc))}getActions(t){let e=0,i=null,{parser:n}=t.p,{tokenizers:r}=n,s=n.stateSlot(t.state,3),o=t.curContext?t.curContext.hash:0,a=0;for(let n=0;nh.end+25&&(a=Math.max(h.lookAhead,a)),0!=h.value)){let n=e;if(h.extended>-1&&(e=this.addActions(t,h.extended,h.end,e)),e=this.addActions(t,h.value,h.end,e),!l.extend&&(i=h,e>n))break}}for(;this.actions.length>e;)this.actions.pop();return a&&t.setLookAhead(a),i||t.pos!=this.stream.end||(i=new Sc,i.value=t.p.parser.eofTerm,i.start=i.end=t.pos,e=this.addActions(t,i.value,i.end,e)),this.mainToken=i,this.actions}getMainToken(t){if(this.mainToken)return this.mainToken;let e=new Sc,{pos:i,p:n}=t;return e.start=i,e.end=Math.min(i+1,n.stream.end),e.value=i==n.stream.end?n.parser.eofTerm:0,e}updateCachedToken(t,e,i){let n=this.stream.clipPos(i.pos);if(e.token(this.stream.reset(n,t),i),t.value>-1){let{parser:e}=i.p;for(let n=0;n=0&&i.p.parser.dialect.allows(r>>1)){1&r?t.extended=r>>1:t.value=r>>1;break}}}else t.value=0,t.end=this.stream.clipPos(n+1)}putAction(t,e,i,n){for(let e=0;e4*t.bufferLength?new _c(i,t.nodeSet):null}get parsedPos(){return this.minStackPos}advance(){let t,e,i=this.stacks,n=this.minStackPos,r=this.stacks=[];if(this.bigReductionCount>300&&1==i.length){let[t]=i;for(;t.forceReduce()&&t.stack.length&&t.stack[t.stack.length-2]>=this.lastBigReductionStart;);this.bigReductionCount=this.lastBigReductionSize=0}for(let s=0;sn)r.push(o);else{if(this.advanceStack(o,r,i))continue;{t||(t=[],e=[]),t.push(o);let i=this.tokens.getMainToken(o);e.push(i.value,i.end)}}break}}if(!r.length){let e=t&&function(t){let e=null;for(let i of t){let t=i.p.stoppedAt;(i.pos==i.p.stream.end||null!=t&&i.pos>t)&&i.p.parser.stateFlag(i.state,2)&&(!e||e.scorethis.stoppedAt?t[0]:this.runRecovery(t,e,r);if(i)return Cc&&console.log("Force-finish "+this.stackID(i)),this.stackToTree(i.forceAll())}if(this.recovering){let t=1==this.recovering?1:3*this.recovering;if(r.length>t)for(r.sort(((t,e)=>e.score-t.score));r.length>t;)r.pop();r.some((t=>t.reducePos>n))&&this.recovering--}else if(r.length>1){t:for(let t=0;t500&&n.buffer.length>500){if(!((e.score-n.score||e.buffer.length-n.buffer.length)>0)){r.splice(t--,1);continue t}r.splice(i--,1)}}}r.length>12&&r.splice(12,r.length-12)}this.minStackPos=r[0].pos;for(let t=1;t ":"";if(null!=this.stoppedAt&&n>this.stoppedAt)return t.forceReduce()?t:null;if(this.fragments){let e=t.curContext&&t.curContext.tracker.strict,i=e?t.curContext.hash:0;for(let o=this.fragments.nodeAt(n);o;){let n=this.parser.nodeSet.types[o.type.id]==o.type?r.getGoto(t.state,o.type.id):-1;if(n>-1&&o.length&&(!e||(o.prop(Ls.contextHash)||0)==i))return t.useNode(o,n),Cc&&console.log(s+this.stackID(t)+` (via reuse of ${r.getName(o.type.id)})`),!0;if(!(o instanceof eo)||0==o.children.length||o.positions[0]>0)break;let a=o.children[0];if(!(a instanceof eo&&0==o.positions[0]))break;o=a}}let o=r.stateSlot(t.state,4);if(o>0)return t.reduce(o),Cc&&console.log(s+this.stackID(t)+` (via always-reduce ${r.getName(65535&o)})`),!0;if(t.stack.length>=8400)for(;t.stack.length>6e3&&t.forceReduce(););let a=this.tokens.getActions(t);for(let o=0;on?e.push(p):i.push(p)}return!1}advanceFully(t,e){let i=t.pos;for(;;){if(!this.advanceStack(t,null,null))return!1;if(t.pos>i)return Rc(t,e),!0}}runRecovery(t,e,i){let n=null,r=!1;for(let s=0;s ":"";if(o.deadEnd){if(r)continue;if(r=!0,o.restart(),Cc&&console.log(h+this.stackID(o)+" (restarted)"),this.advanceFully(o,i))continue}let c=o.split(),u=h;for(let t=0;c.forceReduce()&&t<10;t++){if(Cc&&console.log(u+this.stackID(c)+" (via force-reduce)"),this.advanceFully(c,i))break;Cc&&(u=this.stackID(c)+" -> ")}for(let t of o.recoverByInsert(a))Cc&&console.log(h+this.stackID(t)+" (via recover-insert)"),this.advanceFully(t,i);this.stream.end>o.pos?(l==o.pos&&(l++,a=0),o.recoverByDelete(a,l),Cc&&console.log(h+this.stackID(o)+` (via recover-delete ${this.parser.getName(a)})`),Rc(o,i)):(!n||n.scoret;class Wc{constructor(t){this.start=t.start,this.shift=t.shift||Yc,this.reduce=t.reduce||Yc,this.reuse=t.reuse||Yc,this.hash=t.hash||(()=>0),this.strict=!1!==t.strict}}class qc extends bo{constructor(t){if(super(),this.wrappers=[],14!=t.version)throw new RangeError(`Parser version (${t.version}) doesn't match runtime version (14)`);let e=t.nodeNames.split(" ");this.minRepeatTerm=e.length;for(let i=0;it.topRules[e][1])),n=[];for(let t=0;t=0)r(n,t,e[i++]);else{let s=e[i+-n];for(let o=-n;o>0;o--)r(e[i++],t,s);i++}}}this.nodeSet=new Hs(e.map(((e,r)=>Fs.define({name:r>=this.minRepeatTerm?void 0:e,id:r,props:n[r],top:i.indexOf(r)>-1,error:0==r,skipped:t.skippedNodes&&t.skippedNodes.indexOf(r)>-1})))),t.propSources&&(this.nodeSet=this.nodeSet.extend(...t.propSources)),this.strict=!1,this.bufferLength=Ns;let s=xc(t.tokenData);this.context=t.context,this.specializerSpecs=t.specialized||[],this.specialized=new Uint16Array(this.specializerSpecs.length);for(let t=0;t"number"==typeof t?new vc(s,t):t)),this.topRules=t.topRules,this.dialects=t.dialects||{},this.dynamicPrecedences=t.dynamicPrecedences||null,this.tokenPrecTable=t.tokenPrec,this.termNames=t.termNames||null,this.maxNode=this.nodeSet.types.length-1,this.dialect=this.parseDialect(),this.top=this.topRules[Object.keys(this.topRules)[0]]}createParse(t,e,i){let n=new Xc(this,t,e,i);for(let r of this.wrappers)n=r(n,t,e,i);return n}getGoto(t,e,i=!1){let n=this.goto;if(e>=n[0])return-1;for(let r=n[e+1];;){let e=n[r++],s=1&e,o=n[r++];if(s&&i)return o;for(let i=r+(e>>1);r0}validAction(t,e){return!!this.allActions(t,(t=>t==e||null))}allActions(t,e){let i=this.stateSlot(t,4),n=i?e(i):void 0;for(let i=this.stateSlot(t,1);null==n;i+=3){if(65535==this.data[i]){if(1!=this.data[i+1])break;i=Ic(this.data,i+2)}n=e(Ic(this.data,i+1))}return n}nextStates(t){let e=[];for(let i=this.stateSlot(t,1);;i+=3){if(65535==this.data[i]){if(1!=this.data[i+1])break;i=Ic(this.data,i+2)}if(!(1&this.data[i+2])){let t=this.data[i+1];e.some(((e,i)=>1&i&&e==t))||e.push(this.data[i],t)}}return e}configure(t){let e=Object.assign(Object.create(qc.prototype),this);if(t.props&&(e.nodeSet=this.nodeSet.extend(...t.props)),t.top){let i=this.topRules[t.top];if(!i)throw new RangeError(`Invalid top rule name ${t.top}`);e.top=i}return t.tokenizers&&(e.tokenizers=this.tokenizers.map((e=>{let i=t.tokenizers.find((t=>t.from==e));return i?i.to:e}))),t.specializers&&(e.specializers=this.specializers.slice(),e.specializerSpecs=this.specializerSpecs.map(((i,n)=>{let r=t.specializers.find((t=>t.from==i.external));if(!r)return i;let s=Object.assign(Object.assign({},i),{external:r.to});return e.specializers[n]=jc(s),s}))),t.contextTracker&&(e.context=t.contextTracker),t.dialect&&(e.dialect=this.parseDialect(t.dialect)),null!=t.strict&&(e.strict=t.strict),t.wrap&&(e.wrappers=e.wrappers.concat(t.wrap)),null!=t.bufferLength&&(e.bufferLength=t.bufferLength),e}hasWrappers(){return this.wrappers.length>0}getName(t){return this.termNames?this.termNames[t]:String(t<=this.maxNode&&this.nodeSet.types[t].name||t)}get eofTerm(){return this.maxNode+1}get topNode(){return this.nodeSet.types[this.top[1]]}dynamicPrecedence(t){let e=this.dynamicPrecedences;return null==e?0:e[t]||0}parseDialect(t){let e=Object.keys(this.dialects),i=e.map((()=>!1));if(t)for(let n of t.split(" ")){let t=e.indexOf(n);t>=0&&(i[t]=!0)}let n=null;for(let t=0;tt.external(i,n)<<1|e}return t.get}const Dc=20,Mc=22,Nc=23,zc=24,Bc=26,Lc=27,Gc=28,Uc=31,Fc=34,Hc=37,Kc={area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,frame:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0,menuitem:!0},Jc={dd:!0,li:!0,optgroup:!0,option:!0,p:!0,rp:!0,rt:!0,tbody:!0,td:!0,tfoot:!0,th:!0,tr:!0},tu={dd:{dd:!0,dt:!0},dt:{dd:!0,dt:!0},li:{li:!0},option:{option:!0,optgroup:!0},optgroup:{optgroup:!0},p:{address:!0,article:!0,aside:!0,blockquote:!0,dir:!0,div:!0,dl:!0,fieldset:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,menu:!0,nav:!0,ol:!0,p:!0,pre:!0,section:!0,table:!0,ul:!0},rp:{rp:!0,rt:!0},rt:{rp:!0,rt:!0},tbody:{tbody:!0,tfoot:!0},td:{td:!0,th:!0},tfoot:{tbody:!0},th:{td:!0,th:!0},thead:{tbody:!0,tfoot:!0},tr:{tr:!0}};function eu(t){return 9==t||10==t||13==t||32==t}let iu=null,nu=null,ru=0;function su(t,e){let i=t.pos+e;if(ru==i&&nu==t)return iu;let n=t.peek(e);for(;eu(n);)n=t.peek(++e);let r="";for(;45==(s=n)||46==s||58==s||s>=65&&s<=90||95==s||s>=97&&s<=122||s>=161;)r+=String.fromCharCode(n),n=t.peek(++e);var s;return nu=t,ru=i,iu=r?r.toLowerCase():n==ou||n==au?void 0:null}const ou=63,au=33;function lu(t,e){this.name=t,this.parent=e,this.hash=e?e.hash:0;for(let e=0;ehu.indexOf(e)>-1?new lu(su(n,1)||"",t):t,reduce:(t,e)=>e==Dc&&t?t.parent:t,reuse(t,e,i,n){let r=e.type.id;return 6==r||36==r?new lu(su(n,1)||"",t):t},hash:t=>t?t.hash:0,strict:!1}),uu=new Qc(((t,e)=>{if(60!=t.next)return void(t.next<0&&e.context&&t.acceptToken(57));t.advance();let i=47==t.next;i&&t.advance();let n=su(t,0);if(void 0===n)return;if(!n)return t.acceptToken(i?14:6);let r=e.context?e.context.name:null;if(i){if(n==r)return t.acceptToken(11);if(r&&Jc[r])return t.acceptToken(57,-2);if(e.dialectEnabled(0))return t.acceptToken(12);for(let t=e.context;t;t=t.parent)if(t.name==n)return;t.acceptToken(13)}else{if("script"==n)return t.acceptToken(7);if("style"==n)return t.acceptToken(8);if("textarea"==n)return t.acceptToken(9);if(Kc.hasOwnProperty(n))return t.acceptToken(10);r&&tu[r]&&tu[r][n]?t.acceptToken(57,-1):t.acceptToken(6)}}),{contextual:!0}),pu=new Qc((t=>{for(let e=0,i=0;;i++){if(t.next<0){i&&t.acceptToken(58);break}if(45==t.next)e++;else{if(62==t.next&&e>=2){i>=3&&t.acceptToken(58,-2);break}e=0}t.advance()}}));const du=new Qc(((t,e)=>{if(47==t.next&&62==t.peek(1)){let i=e.dialectEnabled(1)||function(t){for(;t;t=t.parent)if("svg"==t.name||"math"==t.name)return!0;return!1}(e.context);t.acceptToken(i?5:4,2)}else 62==t.next&&t.acceptToken(4,1)}));function fu(t,e,i){let n=2+t.length;return new Qc((r=>{for(let s=0,o=0,a=0;;a++){if(r.next<0){a&&r.acceptToken(e);break}if(0==s&&60==r.next||1==s&&47==r.next||s>=2&&so?r.acceptToken(e,-o):r.acceptToken(i,-(o-2));break}if((10==r.next||13==r.next)&&a){r.acceptToken(e,1);break}s=o=0}else o++;r.advance()}}))}const Ou=fu("script",54,1),mu=fu("style",55,2),gu=fu("textarea",56,3),yu=jo({"Text RawText":la.content,"StartTag StartCloseTag SelfClosingEndTag EndTag":la.angleBracket,TagName:la.tagName,"MismatchedCloseTag/TagName":[la.tagName,la.invalid],AttributeName:la.attributeName,"AttributeValue UnquotedAttributeValue":la.attributeValue,Is:la.definitionOperator,"EntityReference CharacterReference":la.character,Comment:la.blockComment,ProcessingInst:la.processingInstruction,DoctypeDecl:la.documentMeta}),xu=qc.deserialize({version:14,states:",xOVO!rOOO!WQ#tO'#CqO!]Q#tO'#CzO!bQ#tO'#C}O!gQ#tO'#DQO!lQ#tO'#DSO!qOaO'#CpO!|ObO'#CpO#XOdO'#CpO$eO!rO'#CpOOO`'#Cp'#CpO$lO$fO'#DTO$tQ#tO'#DVO$yQ#tO'#DWOOO`'#Dk'#DkOOO`'#DY'#DYQVO!rOOO%OQ&rO,59]O%ZQ&rO,59fO%fQ&rO,59iO%qQ&rO,59lO%|Q&rO,59nOOOa'#D^'#D^O&XOaO'#CxO&dOaO,59[OOOb'#D_'#D_O&lObO'#C{O&wObO,59[OOOd'#D`'#D`O'POdO'#DOO'[OdO,59[OOO`'#Da'#DaO'dO!rO,59[O'kQ#tO'#DROOO`,59[,59[OOOp'#Db'#DbO'pO$fO,59oOOO`,59o,59oO'xQ#|O,59qO'}Q#|O,59rOOO`-E7W-E7WO(SQ&rO'#CsOOQW'#DZ'#DZO(bQ&rO1G.wOOOa1G.w1G.wOOO`1G/Y1G/YO(mQ&rO1G/QOOOb1G/Q1G/QO(xQ&rO1G/TOOOd1G/T1G/TO)TQ&rO1G/WOOO`1G/W1G/WO)`Q&rO1G/YOOOa-E7[-E7[O)kQ#tO'#CyOOO`1G.v1G.vOOOb-E7]-E7]O)pQ#tO'#C|OOOd-E7^-E7^O)uQ#tO'#DPOOO`-E7_-E7_O)zQ#|O,59mOOOp-E7`-E7`OOO`1G/Z1G/ZOOO`1G/]1G/]OOO`1G/^1G/^O*PQ,UO,59_OOQW-E7X-E7XOOOa7+$c7+$cOOO`7+$t7+$tOOOb7+$l7+$lOOOd7+$o7+$oOOO`7+$r7+$rO*[Q#|O,59eO*aQ#|O,59hO*fQ#|O,59kOOO`1G/X1G/XO*kO7[O'#CvO*|OMhO'#CvOOQW1G.y1G.yOOO`1G/P1G/POOO`1G/S1G/SOOO`1G/V1G/VOOOO'#D['#D[O+_O7[O,59bOOQW,59b,59bOOOO'#D]'#D]O+pOMhO,59bOOOO-E7Y-E7YOOQW1G.|1G.|OOOO-E7Z-E7Z",stateData:",]~O!^OS~OUSOVPOWQOXROYTO[]O][O^^O`^Oa^Ob^Oc^Ox^O{_O!dZO~OfaO~OfbO~OfcO~OfdO~OfeO~O!WfOPlP!ZlP~O!XiOQoP!ZoP~O!YlORrP!ZrP~OUSOVPOWQOXROYTOZqO[]O][O^^O`^Oa^Ob^Oc^Ox^O!dZO~O!ZrO~P#dO![sO!euO~OfvO~OfwO~OS|OT}OhyO~OS!POT}OhyO~OS!ROT}OhyO~OS!TOT}OhyO~OS}OT}OhyO~O!WfOPlX!ZlX~OP!WO!Z!XO~O!XiOQoX!ZoX~OQ!ZO!Z!XO~O!YlORrX!ZrX~OR!]O!Z!XO~O!Z!XO~P#dOf!_O~O![sO!e!aO~OS!bO~OS!cO~Oi!dOSgXTgXhgX~OS!fOT!gOhyO~OS!hOT!gOhyO~OS!iOT!gOhyO~OS!jOT!gOhyO~OS!gOT!gOhyO~Of!kO~Of!lO~Of!mO~OS!nO~Ok!qO!`!oO!b!pO~OS!rO~OS!sO~OS!tO~Oa!uOb!uOc!uO!`!wO!a!uO~Oa!xOb!xOc!xO!b!wO!c!xO~Oa!uOb!uOc!uO!`!{O!a!uO~Oa!xOb!xOc!xO!b!{O!c!xO~OT~bac!dx{!d~",goto:"%p!`PPPPPPPPPPPPPPPPPPPP!a!gP!mPP!yP!|#P#S#Y#]#`#f#i#l#r#x!aP!a!aP$O$U$l$r$x%O%U%[%bPPPPPPPP%hX^OX`pXUOX`pezabcde{!O!Q!S!UR!q!dRhUR!XhXVOX`pRkVR!XkXWOX`pRnWR!XnXXOX`pQrXR!XpXYOX`pQ`ORx`Q{aQ!ObQ!QcQ!SdQ!UeZ!e{!O!Q!S!UQ!v!oR!z!vQ!y!pR!|!yQgUR!VgQjVR!YjQmWR![mQpXR!^pQtZR!`tS_O`ToXp",nodeNames:"⚠ StartCloseTag StartCloseTag StartCloseTag EndTag SelfClosingEndTag StartTag StartTag StartTag StartTag StartTag StartCloseTag StartCloseTag StartCloseTag IncompleteCloseTag Document Text EntityReference CharacterReference InvalidEntity Element OpenTag TagName Attribute AttributeName Is AttributeValue UnquotedAttributeValue ScriptText CloseTag OpenTag StyleText CloseTag OpenTag TextareaText CloseTag OpenTag CloseTag SelfClosingTag Comment ProcessingInst MismatchedCloseTag CloseTag DoctypeDecl",maxTerm:67,context:cu,nodeProps:[["closedBy",-10,1,2,3,7,8,9,10,11,12,13,"EndTag",6,"EndTag SelfClosingEndTag",-4,21,30,33,36,"CloseTag"],["openedBy",4,"StartTag StartCloseTag",5,"StartTag",-4,29,32,35,37,"OpenTag"],["group",-9,14,17,18,19,20,39,40,41,42,"Entity",16,"Entity TextContent",-3,28,31,34,"TextContent Entity"],["isolate",-11,21,29,30,32,33,35,36,37,38,41,42,"ltr",-3,26,27,39,""]],propSources:[yu],skippedNodes:[0],repeatNodeCount:9,tokenData:"!]tw8twx7Sx!P8t!P!Q5u!Q!]8t!]!^/^!^!a7S!a#S8t#S#T;{#T#s8t#s$f5u$f;'S8t;'S;=`>V<%l?Ah8t?Ah?BY5u?BY?Mn8t?MnO5u!Z5zbkWOX5uXZ7SZ[5u[^7S^p5uqr5urs7Sst+Ptw5uwx7Sx!]5u!]!^7w!^!a7S!a#S5u#S#T7S#T;'S5u;'S;=`8n<%lO5u!R7VVOp7Sqs7St!]7S!]!^7l!^;'S7S;'S;=`7q<%lO7S!R7qOa!R!R7tP;=`<%l7S!Z8OYkWa!ROX+PZ[+P^p+Pqr+Psw+Px!^+P!a#S+P#T;'S+P;'S;=`+t<%lO+P!Z8qP;=`<%l5u!_8{ihSkWOX5uXZ7SZ[5u[^7S^p5uqr8trs7Sst/^tw8twx7Sx!P8t!P!Q5u!Q!]8t!]!^:j!^!a7S!a#S8t#S#T;{#T#s8t#s$f5u$f;'S8t;'S;=`>V<%l?Ah8t?Ah?BY5u?BY?Mn8t?MnO5u!_:sbhSkWa!ROX+PZ[+P^p+Pqr/^sw/^x!P/^!P!Q+P!Q!^/^!a#S/^#S#T0m#T#s/^#s$f+P$f;'S/^;'S;=`1e<%l?Ah/^?Ah?BY+P?BY?Mn/^?MnO+P!VP<%l?Ah;{?Ah?BY7S?BY?Mn;{?MnO7S!V=dXhSa!Rqr0msw0mx!P0m!Q!^0m!a#s0m$f;'S0m;'S;=`1_<%l?Ah0m?BY?Mn0m!V>SP;=`<%l;{!_>YP;=`<%l8t!_>dhhSkWOX@OXZAYZ[@O[^AY^p@OqrBwrsAYswBwwxAYx!PBw!P!Q@O!Q!]Bw!]!^/^!^!aAY!a#SBw#S#TE{#T#sBw#s$f@O$f;'SBw;'S;=`HS<%l?AhBw?Ah?BY@O?BY?MnBw?MnO@O!Z@TakWOX@OXZAYZ[@O[^AY^p@Oqr@OrsAYsw@OwxAYx!]@O!]!^Az!^!aAY!a#S@O#S#TAY#T;'S@O;'S;=`Bq<%lO@O!RA]UOpAYq!]AY!]!^Ao!^;'SAY;'S;=`At<%lOAY!RAtOb!R!RAwP;=`<%lAY!ZBRYkWb!ROX+PZ[+P^p+Pqr+Psw+Px!^+P!a#S+P#T;'S+P;'S;=`+t<%lO+P!ZBtP;=`<%l@O!_COhhSkWOX@OXZAYZ[@O[^AY^p@OqrBwrsAYswBwwxAYx!PBw!P!Q@O!Q!]Bw!]!^Dj!^!aAY!a#SBw#S#TE{#T#sBw#s$f@O$f;'SBw;'S;=`HS<%l?AhBw?Ah?BY@O?BY?MnBw?MnO@O!_DsbhSkWb!ROX+PZ[+P^p+Pqr/^sw/^x!P/^!P!Q+P!Q!^/^!a#S/^#S#T0m#T#s/^#s$f+P$f;'S/^;'S;=`1e<%l?Ah/^?Ah?BY+P?BY?Mn/^?MnO+P!VFQbhSOpAYqrE{rsAYswE{wxAYx!PE{!P!QAY!Q!]E{!]!^GY!^!aAY!a#sE{#s$fAY$f;'SE{;'S;=`G|<%l?AhE{?Ah?BYAY?BY?MnE{?MnOAY!VGaXhSb!Rqr0msw0mx!P0m!Q!^0m!a#s0m$f;'S0m;'S;=`1_<%l?Ah0m?BY?Mn0m!VHPP;=`<%lE{!_HVP;=`<%lBw!ZHcW!bx`P!a`Or(trs'ksv(tw!^(t!^!_)e!_;'S(t;'S;=`*P<%lO(t!aIYlhS`PkW!a`!cpOX$qXZ&XZ[$q[^&X^p$qpq&Xqr-_rs&}sv-_vw/^wx(tx}-_}!OKQ!O!P-_!P!Q$q!Q!^-_!^!_*V!_!a&X!a#S-_#S#T1k#T#s-_#s$f$q$f;'S-_;'S;=`3X<%l?Ah-_?Ah?BY$q?BY?Mn-_?MnO$q!aK_khS`PkW!a`!cpOX$qXZ&XZ[$q[^&X^p$qpq&Xqr-_rs&}sv-_vw/^wx(tx!P-_!P!Q$q!Q!^-_!^!_*V!_!`&X!`!aMS!a#S-_#S#T1k#T#s-_#s$f$q$f;'S-_;'S;=`3X<%l?Ah-_?Ah?BY$q?BY?Mn-_?MnO$q!TM_X`P!a`!cp!eQOr&Xrs&}sv&Xwx(tx!^&X!^!_*V!_;'S&X;'S;=`*y<%lO&X!aNZ!ZhSfQ`PkW!a`!cpOX$qXZ&XZ[$q[^&X^p$qpq&Xqr-_rs&}sv-_vw/^wx(tx}-_}!OMz!O!PMz!P!Q$q!Q![Mz![!]Mz!]!^-_!^!_*V!_!a&X!a!c-_!c!}Mz!}#R-_#R#SMz#S#T1k#T#oMz#o#s-_#s$f$q$f$}-_$}%OMz%O%W-_%W%oMz%o%p-_%p&aMz&a&b-_&b1pMz1p4UMz4U4dMz4d4e-_4e$ISMz$IS$I`-_$I`$IbMz$Ib$Je-_$Je$JgMz$Jg$Kh-_$Kh%#tMz%#t&/x-_&/x&EtMz&Et&FV-_&FV;'SMz;'S;:j!#|;:j;=`3X<%l?&r-_?&r?AhMz?Ah?BY$q?BY?MnMz?MnO$q!a!$PP;=`<%lMz!R!$ZY!a`!cpOq*Vqr!$yrs(Vsv*Vwx)ex!a*V!a!b!4t!b;'S*V;'S;=`*s<%lO*V!R!%Q]!a`!cpOr*Vrs(Vsv*Vwx)ex}*V}!O!%y!O!f*V!f!g!']!g#W*V#W#X!0`#X;'S*V;'S;=`*s<%lO*V!R!&QX!a`!cpOr*Vrs(Vsv*Vwx)ex}*V}!O!&m!O;'S*V;'S;=`*s<%lO*V!R!&vV!a`!cp!dPOr*Vrs(Vsv*Vwx)ex;'S*V;'S;=`*s<%lO*V!R!'dX!a`!cpOr*Vrs(Vsv*Vwx)ex!q*V!q!r!(P!r;'S*V;'S;=`*s<%lO*V!R!(WX!a`!cpOr*Vrs(Vsv*Vwx)ex!e*V!e!f!(s!f;'S*V;'S;=`*s<%lO*V!R!(zX!a`!cpOr*Vrs(Vsv*Vwx)ex!v*V!v!w!)g!w;'S*V;'S;=`*s<%lO*V!R!)nX!a`!cpOr*Vrs(Vsv*Vwx)ex!{*V!{!|!*Z!|;'S*V;'S;=`*s<%lO*V!R!*bX!a`!cpOr*Vrs(Vsv*Vwx)ex!r*V!r!s!*}!s;'S*V;'S;=`*s<%lO*V!R!+UX!a`!cpOr*Vrs(Vsv*Vwx)ex!g*V!g!h!+q!h;'S*V;'S;=`*s<%lO*V!R!+xY!a`!cpOr!+qrs!,hsv!+qvw!-Swx!.[x!`!+q!`!a!/j!a;'S!+q;'S;=`!0Y<%lO!+qq!,mV!cpOv!,hvx!-Sx!`!,h!`!a!-q!a;'S!,h;'S;=`!.U<%lO!,hP!-VTO!`!-S!`!a!-f!a;'S!-S;'S;=`!-k<%lO!-SP!-kO{PP!-nP;=`<%l!-Sq!-xS!cp{POv(Vx;'S(V;'S;=`(h<%lO(Vq!.XP;=`<%l!,ha!.aX!a`Or!.[rs!-Ssv!.[vw!-Sw!`!.[!`!a!.|!a;'S!.[;'S;=`!/d<%lO!.[a!/TT!a`{POr)esv)ew;'S)e;'S;=`)y<%lO)ea!/gP;=`<%l!.[!R!/sV!a`!cp{POr*Vrs(Vsv*Vwx)ex;'S*V;'S;=`*s<%lO*V!R!0]P;=`<%l!+q!R!0gX!a`!cpOr*Vrs(Vsv*Vwx)ex#c*V#c#d!1S#d;'S*V;'S;=`*s<%lO*V!R!1ZX!a`!cpOr*Vrs(Vsv*Vwx)ex#V*V#V#W!1v#W;'S*V;'S;=`*s<%lO*V!R!1}X!a`!cpOr*Vrs(Vsv*Vwx)ex#h*V#h#i!2j#i;'S*V;'S;=`*s<%lO*V!R!2qX!a`!cpOr*Vrs(Vsv*Vwx)ex#m*V#m#n!3^#n;'S*V;'S;=`*s<%lO*V!R!3eX!a`!cpOr*Vrs(Vsv*Vwx)ex#d*V#d#e!4Q#e;'S*V;'S;=`*s<%lO*V!R!4XX!a`!cpOr*Vrs(Vsv*Vwx)ex#X*V#X#Y!+q#Y;'S*V;'S;=`*s<%lO*V!R!4{Y!a`!cpOr!4trs!5ksv!4tvw!6Vwx!8]x!a!4t!a!b!:]!b;'S!4t;'S;=`!;r<%lO!4tq!5pV!cpOv!5kvx!6Vx!a!5k!a!b!7W!b;'S!5k;'S;=`!8V<%lO!5kP!6YTO!a!6V!a!b!6i!b;'S!6V;'S;=`!7Q<%lO!6VP!6lTO!`!6V!`!a!6{!a;'S!6V;'S;=`!7Q<%lO!6VP!7QOxPP!7TP;=`<%l!6Vq!7]V!cpOv!5kvx!6Vx!`!5k!`!a!7r!a;'S!5k;'S;=`!8V<%lO!5kq!7yS!cpxPOv(Vx;'S(V;'S;=`(h<%lO(Vq!8YP;=`<%l!5ka!8bX!a`Or!8]rs!6Vsv!8]vw!6Vw!a!8]!a!b!8}!b;'S!8];'S;=`!:V<%lO!8]a!9SX!a`Or!8]rs!6Vsv!8]vw!6Vw!`!8]!`!a!9o!a;'S!8];'S;=`!:V<%lO!8]a!9vT!a`xPOr)esv)ew;'S)e;'S;=`)y<%lO)ea!:YP;=`<%l!8]!R!:dY!a`!cpOr!4trs!5ksv!4tvw!6Vwx!8]x!`!4t!`!a!;S!a;'S!4t;'S;=`!;r<%lO!4t!R!;]V!a`!cpxPOr*Vrs(Vsv*Vwx)ex;'S*V;'S;=`*s<%lO*V!R!;uP;=`<%l!4t!V!{let a=t.type.id;if(a==Gc)return bu(t,e,i);if(a==Uc)return bu(t,e,n);if(a==Fc)return bu(t,e,r);if(a==Dc&&s.length){let i,n=t.node,r=n.firstChild,o=r&&wu(r,e);if(o)for(let t of s)if(t.tag==o&&(!t.attrs||t.attrs(i||(i=Su(n,e))))){let e=n.lastChild,i=e.type.id==Hc?e.from:n.to;if(i>r.to)return{parser:t.parser,overlay:[{from:r.to,to:i}]}}}if(o&&a==Nc){let i,n=t.node;if(i=n.firstChild){let t=o[e.read(i.from,i.to)];if(t)for(let i of t){if(i.tagName&&i.tagName!=wu(n.parent,e))continue;let t=n.lastChild;if(t.type.id==Bc){let e=t.from+1,n=t.lastChild,r=t.to-(n&&n.isError?0:1);if(r>e)return{parser:i.parser,overlay:[{from:e,to:r}]}}else if(t.type.id==Lc)return{parser:i.parser,overlay:[{from:t.from,to:t.to}]}}}}return null},(t,e,i,n)=>new Zo(t,a,e,i,n);var a}const ku=[9,10,11,12,13,32,133,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288];function Qu(t){return t>=65&&t<=90||t>=97&&t<=122||t>=161}function Pu(t){return t>=48&&t<=57}const $u=new Qc(((t,e)=>{for(let i=!1,n=0,r=0;;r++){let{next:s}=t;if(Qu(s)||45==s||95==s||i&&Pu(s))!i&&(45!=s||r>0)&&(i=!0),n===r&&45==s&&n++,t.advance();else{if(92!=s||10==t.peek(1)){i&&t.acceptToken(40==s?100:2==n&&e.canShift(2)?2:101);break}t.advance(),t.next>-1&&t.advance(),i=!0}}})),Zu=new Qc((t=>{if(ku.includes(t.peek(-1))){let{next:e}=t;(Qu(e)||95==e||35==e||46==e||91==e||58==e&&Qu(t.peek(1))||45==e||38==e)&&t.acceptToken(99)}})),Cu=new Qc((t=>{if(!ku.includes(t.peek(-1))){let{next:e}=t;if(37==e&&(t.advance(),t.acceptToken(1)),Qu(e)){do{t.advance()}while(Qu(t.next)||Pu(t.next));t.acceptToken(1)}}})),Tu=jo({"AtKeyword import charset namespace keyframes media supports":la.definitionKeyword,"from to selector":la.keyword,NamespaceName:la.namespace,KeyframeName:la.labelName,KeyframeRangeName:la.operatorKeyword,TagName:la.tagName,ClassName:la.className,PseudoClassName:la.constant(la.className),IdName:la.labelName,"FeatureName PropertyName":la.propertyName,AttributeName:la.attributeName,NumberLiteral:la.number,KeywordQuery:la.keyword,UnaryQueryOp:la.operatorKeyword,"CallTag ValueName":la.atom,VariableName:la.variableName,Callee:la.operatorKeyword,Unit:la.unit,"UniversalSelector NestingSelector":la.definitionOperator,MatchOp:la.compareOperator,"ChildOp SiblingOp, LogicOp":la.logicOperator,BinOp:la.arithmeticOperator,Important:la.modifier,Comment:la.blockComment,ColorLiteral:la.color,"ParenthesizedContent StringLiteral":la.string,":":la.punctuation,"PseudoOp #":la.derefOperator,"; ,":la.separator,"( )":la.paren,"[ ]":la.squareBracket,"{ }":la.brace}),Au={__proto__:null,lang:32,"nth-child":32,"nth-last-child":32,"nth-of-type":32,"nth-last-of-type":32,dir:32,"host-context":32,url:60,"url-prefix":60,domain:60,regexp:60,selector:138},_u={__proto__:null,"@import":118,"@media":142,"@charset":146,"@namespace":150,"@keyframes":156,"@supports":168},Eu={__proto__:null,not:132,only:132},Xu=qc.deserialize({version:14,states:":^QYQ[OOO#_Q[OOP#fOWOOOOQP'#Cd'#CdOOQP'#Cc'#CcO#kQ[O'#CfO$_QXO'#CaO$fQ[O'#ChO$qQ[O'#DTO$vQ[O'#DWOOQP'#Em'#EmO${QdO'#DgO%jQ[O'#DtO${QdO'#DvO%{Q[O'#DxO&WQ[O'#D{O&`Q[O'#ERO&nQ[O'#ETOOQS'#El'#ElOOQS'#EW'#EWQYQ[OOO&uQXO'#CdO'jQWO'#DcO'oQWO'#EsO'zQ[O'#EsQOQWOOP(UO#tO'#C_POOO)C@[)C@[OOQP'#Cg'#CgOOQP,59Q,59QO#kQ[O,59QO(aQ[O'#E[O({QWO,58{O)TQ[O,59SO$qQ[O,59oO$vQ[O,59rO(aQ[O,59uO(aQ[O,59wO(aQ[O,59xO)`Q[O'#DbOOQS,58{,58{OOQP'#Ck'#CkOOQO'#DR'#DROOQP,59S,59SO)gQWO,59SO)lQWO,59SOOQP'#DV'#DVOOQP,59o,59oOOQO'#DX'#DXO)qQ`O,59rOOQS'#Cp'#CpO${QdO'#CqO)yQvO'#CsO+ZQtO,5:ROOQO'#Cx'#CxO)lQWO'#CwO+oQWO'#CyO+tQ[O'#DOOOQS'#Ep'#EpOOQO'#Dj'#DjO+|Q[O'#DqO,[QWO'#EtO&`Q[O'#DoO,jQWO'#DrOOQO'#Eu'#EuO)OQWO,5:`O,oQpO,5:bOOQS'#Dz'#DzO,wQWO,5:dO,|Q[O,5:dOOQO'#D}'#D}O-UQWO,5:gO-ZQWO,5:mO-cQWO,5:oOOQS-E8U-E8UO${QdO,59}O-kQ[O'#E^O-xQWO,5;_O-xQWO,5;_POOO'#EV'#EVP.TO#tO,58yPOOO,58y,58yOOQP1G.l1G.lO.zQXO,5:vOOQO-E8Y-E8YOOQS1G.g1G.gOOQP1G.n1G.nO)gQWO1G.nO)lQWO1G.nOOQP1G/Z1G/ZO/XQ`O1G/^O/rQXO1G/aO0YQXO1G/cO0pQXO1G/dO1WQWO,59|O1]Q[O'#DSO1dQdO'#CoOOQP1G/^1G/^O${QdO1G/^O1kQpO,59]OOQS,59_,59_O${QdO,59aO1sQWO1G/mOOQS,59c,59cO1xQ!bO,59eOOQS'#DP'#DPOOQS'#EY'#EYO2QQ[O,59jOOQS,59j,59jO2YQWO'#DjO2eQWO,5:VO2jQWO,5:]O&`Q[O,5:XO&`Q[O'#E_O2rQWO,5;`O2}QWO,5:ZO(aQ[O,5:^OOQS1G/z1G/zOOQS1G/|1G/|OOQS1G0O1G0OO3`QWO1G0OO3eQdO'#EOOOQS1G0R1G0ROOQS1G0X1G0XOOQS1G0Z1G0ZO3pQtO1G/iOOQO,5:x,5:xO4WQ[O,5:xOOQO-E8[-E8[O4eQWO1G0yPOOO-E8T-E8TPOOO1G.e1G.eOOQP7+$Y7+$YOOQP7+$x7+$xO${QdO7+$xOOQS1G/h1G/hO4pQXO'#ErO4wQWO,59nO4|QtO'#EXO5tQdO'#EoO6OQWO,59ZO6TQpO7+$xOOQS1G.w1G.wOOQS1G.{1G.{OOQS7+%X7+%XO6]QWO1G/POOQS-E8W-E8WOOQS1G/U1G/UO${QdO1G/qOOQO1G/w1G/wOOQO1G/s1G/sO6bQWO,5:yOOQO-E8]-E8]O6pQXO1G/xOOQS7+%j7+%jO6wQYO'#CsOOQO'#EQ'#EQO7SQ`O'#EPOOQO'#EP'#EPO7_QWO'#E`O7gQdO,5:jOOQS,5:j,5:jO7rQtO'#E]O${QdO'#E]O8sQdO7+%TOOQO7+%T7+%TOOQO1G0d1G0dO9WQpO<OAN>OO:xQdO,5:uOOQO-E8X-E8XOOQO<T![;'S%^;'S;=`%o<%lO%^l;TUo`Oy%^z!Q%^!Q![;g![;'S%^;'S;=`%o<%lO%^l;nYo`#e[Oy%^z!Q%^!Q![;g![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^l[[o`#e[Oy%^z!O%^!O!P;g!P!Q%^!Q![>T![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^n?VSt^Oy%^z;'S%^;'S;=`%o<%lO%^l?hWjWOy%^z!O%^!O!P;O!P!Q%^!Q![>T![;'S%^;'S;=`%o<%lO%^n@VU#bQOy%^z!Q%^!Q![;g![;'S%^;'S;=`%o<%lO%^~@nTjWOy%^z{@}{;'S%^;'S;=`%o<%lO%^~AUSo`#[~Oy%^z;'S%^;'S;=`%o<%lO%^lAg[#e[Oy%^z!O%^!O!P;g!P!Q%^!Q![>T![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^bBbU]QOy%^z![%^![!]Bt!];'S%^;'S;=`%o<%lO%^bB{S^Qo`Oy%^z;'S%^;'S;=`%o<%lO%^nC^S!Y^Oy%^z;'S%^;'S;=`%o<%lO%^dCoS|SOy%^z;'S%^;'S;=`%o<%lO%^bDQU!OQOy%^z!`%^!`!aDd!a;'S%^;'S;=`%o<%lO%^bDkS!OQo`Oy%^z;'S%^;'S;=`%o<%lO%^bDzWOy%^z!c%^!c!}Ed!}#T%^#T#oEd#o;'S%^;'S;=`%o<%lO%^bEk[![Qo`Oy%^z}%^}!OEd!O!Q%^!Q![Ed![!c%^!c!}Ed!}#T%^#T#oEd#o;'S%^;'S;=`%o<%lO%^nFfSq^Oy%^z;'S%^;'S;=`%o<%lO%^nFwSp^Oy%^z;'S%^;'S;=`%o<%lO%^bGWUOy%^z#b%^#b#cGj#c;'S%^;'S;=`%o<%lO%^bGoUo`Oy%^z#W%^#W#XHR#X;'S%^;'S;=`%o<%lO%^bHYS!bQo`Oy%^z;'S%^;'S;=`%o<%lO%^bHiUOy%^z#f%^#f#gHR#g;'S%^;'S;=`%o<%lO%^fIQS!TUOy%^z;'S%^;'S;=`%o<%lO%^nIcS!S^Oy%^z;'S%^;'S;=`%o<%lO%^fItU!RQOy%^z!_%^!_!`6y!`;'S%^;'S;=`%o<%lO%^`JZP;=`<%l$}",tokenizers:[Zu,Cu,$u,1,2,3,4,new kc("m~RRYZ[z{a~~g~aO#^~~dP!P!Qg~lO#_~~",28,105)],topRules:{StyleSheet:[0,4],Styles:[1,86]},specialized:[{term:100,get:t=>Au[t]||-1},{term:58,get:t=>_u[t]||-1},{term:101,get:t=>Eu[t]||-1}],tokenPrec:1200});let Ru=null;function Vu(){if(!Ru&&"object"==typeof document&&document.body){let{style:t}=document.body,e=[],i=new Set;for(let n in t)"cssText"!=n&&"cssFloat"!=n&&"string"==typeof t[n]&&(/[A-Z]/.test(n)&&(n=n.replace(/[A-Z]/g,(t=>"-"+t.toLowerCase()))),i.has(n)||(e.push(n),i.add(n)));Ru=e.sort().map((t=>({type:"property",label:t})))}return Ru||[]}const Yu=["active","after","any-link","autofill","backdrop","before","checked","cue","default","defined","disabled","empty","enabled","file-selector-button","first","first-child","first-letter","first-line","first-of-type","focus","focus-visible","focus-within","fullscreen","has","host","host-context","hover","in-range","indeterminate","invalid","is","lang","last-child","last-of-type","left","link","marker","modal","not","nth-child","nth-last-child","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","part","placeholder","placeholder-shown","read-only","read-write","required","right","root","scope","selection","slotted","target","target-text","valid","visited","where"].map((t=>({type:"class",label:t}))),Wu=["above","absolute","activeborder","additive","activecaption","after-white-space","ahead","alias","all","all-scroll","alphabetic","alternate","always","antialiased","appworkspace","asterisks","attr","auto","auto-flow","avoid","avoid-column","avoid-page","avoid-region","axis-pan","background","backwards","baseline","below","bidi-override","blink","block","block-axis","bold","bolder","border","border-box","both","bottom","break","break-all","break-word","bullets","button","button-bevel","buttonface","buttonhighlight","buttonshadow","buttontext","calc","capitalize","caps-lock-indicator","caption","captiontext","caret","cell","center","checkbox","circle","cjk-decimal","clear","clip","close-quote","col-resize","collapse","color","color-burn","color-dodge","column","column-reverse","compact","condensed","contain","content","contents","content-box","context-menu","continuous","copy","counter","counters","cover","crop","cross","crosshair","currentcolor","cursive","cyclic","darken","dashed","decimal","decimal-leading-zero","default","default-button","dense","destination-atop","destination-in","destination-out","destination-over","difference","disc","discard","disclosure-closed","disclosure-open","document","dot-dash","dot-dot-dash","dotted","double","down","e-resize","ease","ease-in","ease-in-out","ease-out","element","ellipse","ellipsis","embed","end","ethiopic-abegede-gez","ethiopic-halehame-aa-er","ethiopic-halehame-gez","ew-resize","exclusion","expanded","extends","extra-condensed","extra-expanded","fantasy","fast","fill","fill-box","fixed","flat","flex","flex-end","flex-start","footnotes","forwards","from","geometricPrecision","graytext","grid","groove","hand","hard-light","help","hidden","hide","higher","highlight","highlighttext","horizontal","hsl","hsla","hue","icon","ignore","inactiveborder","inactivecaption","inactivecaptiontext","infinite","infobackground","infotext","inherit","initial","inline","inline-axis","inline-block","inline-flex","inline-grid","inline-table","inset","inside","intrinsic","invert","italic","justify","keep-all","landscape","large","larger","left","level","lighter","lighten","line-through","linear","linear-gradient","lines","list-item","listbox","listitem","local","logical","loud","lower","lower-hexadecimal","lower-latin","lower-norwegian","lowercase","ltr","luminosity","manipulation","match","matrix","matrix3d","medium","menu","menutext","message-box","middle","min-intrinsic","mix","monospace","move","multiple","multiple_mask_images","multiply","n-resize","narrower","ne-resize","nesw-resize","no-close-quote","no-drop","no-open-quote","no-repeat","none","normal","not-allowed","nowrap","ns-resize","numbers","numeric","nw-resize","nwse-resize","oblique","opacity","open-quote","optimizeLegibility","optimizeSpeed","outset","outside","outside-shape","overlay","overline","padding","padding-box","painted","page","paused","perspective","pinch-zoom","plus-darker","plus-lighter","pointer","polygon","portrait","pre","pre-line","pre-wrap","preserve-3d","progress","push-button","radial-gradient","radio","read-only","read-write","read-write-plaintext-only","rectangle","region","relative","repeat","repeating-linear-gradient","repeating-radial-gradient","repeat-x","repeat-y","reset","reverse","rgb","rgba","ridge","right","rotate","rotate3d","rotateX","rotateY","rotateZ","round","row","row-resize","row-reverse","rtl","run-in","running","s-resize","sans-serif","saturation","scale","scale3d","scaleX","scaleY","scaleZ","screen","scroll","scrollbar","scroll-position","se-resize","self-start","self-end","semi-condensed","semi-expanded","separate","serif","show","single","skew","skewX","skewY","skip-white-space","slide","slider-horizontal","slider-vertical","sliderthumb-horizontal","sliderthumb-vertical","slow","small","small-caps","small-caption","smaller","soft-light","solid","source-atop","source-in","source-out","source-over","space","space-around","space-between","space-evenly","spell-out","square","start","static","status-bar","stretch","stroke","stroke-box","sub","subpixel-antialiased","svg_masks","super","sw-resize","symbolic","symbols","system-ui","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","text","text-bottom","text-top","textarea","textfield","thick","thin","threeddarkshadow","threedface","threedhighlight","threedlightshadow","threedshadow","to","top","transform","translate","translate3d","translateX","translateY","translateZ","transparent","ultra-condensed","ultra-expanded","underline","unidirectional-pan","unset","up","upper-latin","uppercase","url","var","vertical","vertical-text","view-box","visible","visibleFill","visiblePainted","visibleStroke","visual","w-resize","wait","wave","wider","window","windowframe","windowtext","words","wrap","wrap-reverse","x-large","x-small","xor","xx-large","xx-small"].map((t=>({type:"keyword",label:t}))).concat(["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen"].map((t=>({type:"constant",label:t})))),qu=["a","abbr","address","article","aside","b","bdi","bdo","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","dd","del","details","dfn","dialog","div","dl","dt","em","figcaption","figure","footer","form","header","hgroup","h1","h2","h3","h4","h5","h6","hr","html","i","iframe","img","input","ins","kbd","label","legend","li","main","meter","nav","ol","output","p","pre","ruby","section","select","small","source","span","strong","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","tr","u","ul"].map((t=>({type:"type",label:t}))),Iu=/^(\w[\w-]*|-\w[\w-]*|)$/,ju=/^-(-[\w-]*)?$/;const Du=new So,Mu=["Declaration"];function Nu(t){for(let e=t;;){if(e.type.isTop)return e;if(!(e=e.parent))return t}}function zu(t,e,i){if(e.to-e.from>4096){let n=Du.get(e);if(n)return n;let r=[],s=new Set,o=e.cursor(to.IncludeAnonymous);if(o.firstChild())do{for(let e of zu(t,o.node,i))s.has(e.label)||(s.add(e.label),r.push(e))}while(o.nextSibling());return Du.set(e,r),r}{let n=[],r=new Set;return e.cursor().iterate((e=>{var s;if(i(e)&&e.matchContext(Mu)&&":"==(null===(s=e.node.nextSibling)||void 0===s?void 0:s.name)){let i=t.sliceString(e.from,e.to);r.has(i)||(r.add(i),n.push({label:i,type:"variable"}))}})),n}}const Bu=t=>e=>{let{state:i,pos:n}=e,r=ga(i).resolveInner(n,-1),s=r.type.isError&&r.from==r.to-1&&"-"==i.doc.sliceString(r.from,r.to);if("PropertyName"==r.name||(s||"TagName"==r.name)&&/^(Block|Styles)$/.test(r.resolve(r.to).name))return{from:r.from,options:Vu(),validFor:Iu};if("ValueName"==r.name)return{from:r.from,options:Wu,validFor:Iu};if("PseudoClassName"==r.name)return{from:r.from,options:Yu,validFor:Iu};if(t(r)||(e.explicit||s)&&function(t,e){var i;if(("("==t.name||t.type.isError)&&(t=t.parent||t),"ArgList"!=t.name)return!1;let n=null===(i=t.parent)||void 0===i?void 0:i.firstChild;return"Callee"==(null==n?void 0:n.name)&&"var"==e.sliceString(n.from,n.to)}(r,i.doc))return{from:t(r)||s?r.from:n,options:zu(i.doc,Nu(r),t),validFor:ju};if("TagName"==r.name){for(let{parent:t}=r;t;t=t.parent)if("Block"==t.name)return{from:r.from,options:Vu(),validFor:Iu};return{from:r.from,options:qu,validFor:Iu}}if(!e.explicit)return null;let o=r.resolve(n),a=o.childBefore(n);return a&&":"==a.name&&"PseudoClassSelector"==o.name?{from:n,options:Yu,validFor:Iu}:a&&":"==a.name&&"Declaration"==o.name||"ArgList"==o.name?{from:n,options:Wu,validFor:Iu}:"Block"==o.name||"Styles"==o.name?{from:n,options:Vu(),validFor:Iu}:null},Lu=Bu((t=>"VariableName"==t.name)),Gu=ma.define({name:"css",parser:Xu.configure({props:[Xa.add({Declaration:Da()}),Ma.add({"Block KeyframeList":Na})]}),languageData:{commentTokens:{block:{open:"/*",close:"*/"}},indentOnInput:/^\s*\}$/,wordChars:"-"}});const Uu=[9,10,11,12,13,32,133,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288],Fu=new Wc({start:!1,shift:(t,e)=>4==e||5==e||312==e?t:313==e,strict:!1}),Hu=new Qc(((t,e)=>{let{next:i}=t;(125==i||-1==i||e.context)&&t.acceptToken(310)}),{contextual:!0,fallback:!0}),Ku=new Qc(((t,e)=>{let i,{next:n}=t;Uu.indexOf(n)>-1||(47!=n||47!=(i=t.peek(1))&&42!=i)&&(125==n||59==n||-1==n||e.context||t.acceptToken(309))}),{contextual:!0}),Ju=new Qc(((t,e)=>{let{next:i}=t;if((43==i||45==i)&&(t.advance(),i==t.next)){t.advance();let i=!e.context&&e.canShift(1);t.acceptToken(i?1:2)}}),{contextual:!0});function tp(t,e){return t>=65&&t<=90||t>=97&&t<=122||95==t||t>=192||!e&&t>=48&&t<=57}const ep=new Qc(((t,e)=>{if(60!=t.next||!e.dialectEnabled(0))return;if(t.advance(),47==t.next)return;let i=0;for(;Uu.indexOf(t.next)>-1;)t.advance(),i++;if(tp(t.next,!0)){for(t.advance(),i++;tp(t.next,!1);)t.advance(),i++;for(;Uu.indexOf(t.next)>-1;)t.advance(),i++;if(44==t.next)return;for(let e=0;;e++){if(7==e){if(!tp(t.next,!0))return;break}if(t.next!="extends".charCodeAt(e))break;t.advance(),i++}}t.acceptToken(3,-i)})),ip=jo({"get set async static":la.modifier,"for while do if else switch try catch finally return throw break continue default case":la.controlKeyword,"in of await yield void typeof delete instanceof":la.operatorKeyword,"let var const using function class extends":la.definitionKeyword,"import export from":la.moduleKeyword,"with debugger as new":la.keyword,TemplateString:la.special(la.string),super:la.atom,BooleanLiteral:la.bool,this:la.self,null:la.null,Star:la.modifier,VariableName:la.variableName,"CallExpression/VariableName TaggedTemplateExpression/VariableName":la.function(la.variableName),VariableDefinition:la.definition(la.variableName),Label:la.labelName,PropertyName:la.propertyName,PrivatePropertyName:la.special(la.propertyName),"CallExpression/MemberExpression/PropertyName":la.function(la.propertyName),"FunctionDeclaration/VariableDefinition":la.function(la.definition(la.variableName)),"ClassDeclaration/VariableDefinition":la.definition(la.className),PropertyDefinition:la.definition(la.propertyName),PrivatePropertyDefinition:la.definition(la.special(la.propertyName)),UpdateOp:la.updateOperator,"LineComment Hashbang":la.lineComment,BlockComment:la.blockComment,Number:la.number,String:la.string,Escape:la.escape,ArithOp:la.arithmeticOperator,LogicOp:la.logicOperator,BitOp:la.bitwiseOperator,CompareOp:la.compareOperator,RegExp:la.regexp,Equals:la.definitionOperator,Arrow:la.function(la.punctuation),": Spread":la.punctuation,"( )":la.paren,"[ ]":la.squareBracket,"{ }":la.brace,"InterpolationStart InterpolationEnd":la.special(la.brace),".":la.derefOperator,", ;":la.separator,"@":la.meta,TypeName:la.typeName,TypeDefinition:la.definition(la.typeName),"type enum interface implements namespace module declare":la.definitionKeyword,"abstract global Privacy readonly override":la.modifier,"is keyof unique infer":la.operatorKeyword,JSXAttributeValue:la.attributeValue,JSXText:la.content,"JSXStartTag JSXStartCloseTag JSXSelfCloseEndTag JSXEndTag":la.angleBracket,"JSXIdentifier JSXNameSpacedName":la.tagName,"JSXAttribute/JSXIdentifier JSXAttribute/JSXNameSpacedName":la.attributeName,"JSXBuiltin/JSXIdentifier":la.standard(la.tagName)}),np={__proto__:null,export:18,as:23,from:31,default:34,async:39,function:40,extends:52,this:56,true:64,false:64,null:76,void:80,typeof:84,super:102,new:136,delete:152,yield:161,await:165,class:170,public:227,private:227,protected:227,readonly:229,instanceof:248,satisfies:251,in:252,const:254,import:286,keyof:339,unique:343,infer:349,is:385,abstract:405,implements:407,type:409,let:412,var:414,using:417,interface:423,enum:427,namespace:433,module:435,declare:439,global:443,for:462,of:471,while:474,with:478,do:482,if:486,else:488,switch:492,case:498,try:504,catch:508,finally:512,return:516,throw:520,break:524,continue:528,debugger:532},rp={__proto__:null,async:123,get:125,set:127,declare:187,public:189,private:189,protected:189,static:191,abstract:193,override:195,readonly:201,accessor:203,new:389},sp={__proto__:null,"<":143},op=qc.deserialize({version:14,states:"$RQWO'#CdO>cQWO'#H[O>kQWO'#HbO>kQWO'#HdO`Q^O'#HfO>kQWO'#HhO>kQWO'#HkO>pQWO'#HqO>uQ07iO'#HwO%[Q^O'#HyO?QQ07iO'#H{O?]Q07iO'#H}O9kQ07hO'#IPO?hQ08SO'#ChO@jQ`O'#DiQOQWOOO%[Q^O'#EPOAQQWO'#ESO:RQ7[O'#EjOA]QWO'#EjOAhQpO'#FbOOQU'#Cf'#CfOOQ07`'#Dn'#DnOOQ07`'#Jm'#JmO%[Q^O'#JmOOQO'#Jq'#JqOOQO'#Ib'#IbOBhQ`O'#EcOOQ07`'#Eb'#EbOCdQ07pO'#EcOCnQ`O'#EVOOQO'#Jp'#JpODSQ`O'#JqOEaQ`O'#EVOCnQ`O'#EcPEnO!0LbO'#CaPOOO)CDu)CDuOOOO'#IX'#IXOEyO!bO,59TOOQ07b,59T,59TOOOO'#IY'#IYOFXO#tO,59TO%[Q^O'#D`OOOO'#I['#I[OFgO?MpO,59xOOQ07b,59x,59xOFuQ^O'#I]OGYQWO'#JkOI[QrO'#JkO+}Q^O'#JkOIcQWO,5:OOIyQWO'#ElOJWQWO'#JyOJcQWO'#JxOJcQWO'#JxOJkQWO,5;YOJpQWO'#JwOOQ07f,5:Z,5:ZOJwQ^O,5:ZOLxQ08SO,5:eOMiQWO,5:mONSQ07hO'#JvONZQWO'#JuO9ZQWO'#JuONoQWO'#JuONwQWO,5;XON|QWO'#JuO!#UQrO'#JjOOQ07b'#Ch'#ChO%[Q^O'#ERO!#tQpO,5:rOOQO'#Jr'#JrOOQO-EmOOQU'#J`'#J`OOQU,5>n,5>nOOQU-EpQWO'#HQO9aQWO'#HSO!CgQWO'#HSO:RQ7[O'#HUO!ClQWO'#HUOOQU,5=j,5=jO!CqQWO'#HVO!DSQWO'#CnO!DXQWO,59OO!DcQWO,59OO!FhQ^O,59OOOQU,59O,59OO!FxQ07hO,59OO%[Q^O,59OO!ITQ^O'#H^OOQU'#H_'#H_OOQU'#H`'#H`O`Q^O,5=vO!IkQWO,5=vO`Q^O,5=|O`Q^O,5>OO!IpQWO,5>QO`Q^O,5>SO!IuQWO,5>VO!IzQ^O,5>]OOQU,5>c,5>cO%[Q^O,5>cO9kQ07hO,5>eOOQU,5>g,5>gO!NUQWO,5>gOOQU,5>i,5>iO!NUQWO,5>iOOQU,5>k,5>kO!NZQ`O'#D[O%[Q^O'#JmO!NxQ`O'#JmO# gQ`O'#DjO# xQ`O'#DjO#$ZQ^O'#DjO#$bQWO'#JlO#$jQWO,5:TO#$oQWO'#EpO#$}QWO'#JzO#%VQWO,5;ZO#%[Q`O'#DjO#%iQ`O'#EUOOQ07b,5:n,5:nO%[Q^O,5:nO#%pQWO,5:nO>pQWO,5;UO!@}Q`O,5;UO!AVQ7[O,5;UO:RQ7[O,5;UO#%xQWO,5@XO#%}Q$ISO,5:rOOQO-E<`-E<`O#'TQ07pO,5:}OCnQ`O,5:qO#'_Q`O,5:qOCnQ`O,5:}O!@rQ07hO,5:qOOQ07`'#Ef'#EfOOQO,5:},5:}O%[Q^O,5:}O#'lQ07hO,5:}O#'wQ07hO,5:}O!@}Q`O,5:qOOQO,5;T,5;TO#(VQ07hO,5:}POOO'#IV'#IVP#(kO!0LbO,58{POOO,58{,58{OOOO-EwO+}Q^O,5>wOOQO,5>},5>}O#)VQ^O'#I]OOQO-EjQ08SO1G0{O#>wQ08SO1G0{O#@uQ08SO1G0{O#CuQ(CYO'#ChO#EsQ(CYO1G1^O#EzQ(CYO'#JjO!,lQWO1G1dO#F[Q08SO,5?TOOQ07`-EkQWO1G3lO$2dQ^O1G3nO$6hQ^O'#HmOOQU1G3q1G3qO$6uQWO'#HsO>pQWO'#HuOOQU1G3w1G3wO$6}Q^O1G3wO9kQ07hO1G3}OOQU1G4P1G4POOQ07`'#GY'#GYO9kQ07hO1G4RO9kQ07hO1G4TO$;UQWO,5@XO!*fQ^O,5;[O9ZQWO,5;[O>pQWO,5:UO!*fQ^O,5:UO!@}Q`O,5:UO$;ZQ(CYO,5:UOOQO,5;[,5;[O$;eQ`O'#I^O$;{QWO,5@WOOQ07b1G/o1G/oO$pQWO1G0pO!@}Q`O1G0pO!AVQ7[O1G0pOOQ07`1G5s1G5sO!@rQ07hO1G0]OOQO1G0i1G0iO%[Q^O1G0iO$PQrO1G4cOOQO1G4i1G4iO%[Q^O,5>wO$>ZQWO1G5qO$>cQWO1G6OO$>kQrO1G6PO9ZQWO,5>}O$>uQ08SO1G5|O%[Q^O1G5|O$?VQ07hO1G5|O$?hQWO1G5{O$?hQWO1G5{O9ZQWO1G5{O$?pQWO,5?QO9ZQWO,5?QOOQO,5?Q,5?QO$@UQWO,5?QO$'ZQWO,5?QOOQO-EXOOQU,5>X,5>XO%[Q^O'#HnO%7dQWO'#HpOOQU,5>_,5>_O9ZQWO,5>_OOQU,5>a,5>aOOQU7+)c7+)cOOQU7+)i7+)iOOQU7+)m7+)mOOQU7+)o7+)oO%7iQ`O1G5sO%7}Q(CYO1G0vO%8XQWO1G0vOOQO1G/p1G/pO%8dQ(CYO1G/pO>pQWO1G/pO!*fQ^O'#DjOOQO,5>x,5>xOOQO-E<[-E<[OOQO,5?O,5?OOOQO-EpQWO7+&[O!@}Q`O7+&[OOQO7+%w7+%wO$=mQ08SO7+&TOOQO7+&T7+&TO%[Q^O7+&TO%8nQ07hO7+&TO!@rQ07hO7+%wO!@}Q`O7+%wO%8yQ07hO7+&TO%9XQ08SO7++hO%[Q^O7++hO%9iQWO7++gO%9iQWO7++gOOQO1G4l1G4lO9ZQWO1G4lO%9qQWO1G4lOOQO7+%|7+%|O#%sQWO<zQ08SO1G2ZO%A]Q08SO1G2mO%ChQ08SO1G2oO%EsQ7[O,5>yOOQO-E<]-E<]O%E}QrO,5>zO%[Q^O,5>zOOQO-E<^-E<^O%FXQWO1G5uOOQ07b<YOOQU,5>[,5>[O&5oQWO1G3yO9ZQWO7+&bO!*fQ^O7+&bOOQO7+%[7+%[O&5tQ(CYO1G6PO>pQWO7+%[OOQ07b<pQWO<pQWO7+)eO'&sQWO<}AN>}O%[Q^OAN?ZOOQO<qQ(CYOG26}O!*fQ^O'#DyO1PQWO'#EWO'@gQrO'#JiO!*fQ^O'#DqO'@nQ^O'#D}O'@uQrO'#ChO'C]QrO'#ChO!*fQ^O'#EPO'CmQ^O,5;VO!*fQ^O,5;aO!*fQ^O,5;aO!*fQ^O,5;aO!*fQ^O,5;aO!*fQ^O,5;aO!*fQ^O,5;aO!*fQ^O,5;aO!*fQ^O,5;aO!*fQ^O,5;aO!*fQ^O,5;aO!*fQ^O,5;aO!*fQ^O'#IiO'EpQWO,5a#@O#@^#@d#Ax#BW#Cr#DQ#DW#D^#Dd#Dn#Dt#Dz#EU#Eh#EnPPPPPPPPPP#EtPPPPPPP#Fi#Ip#KP#KW#K`PPPP$!d$%Z$+r$+u$+x$,q$,t$,w$-O$-WPP$-^$-b$.Y$/X$/]$/qPP$/u$/{$0PP$0S$0W$0Z$1P$1h$2P$2T$2W$2Z$2a$2d$2h$2lR!{RoqOXst!Z#c%j&m&o&p&r,h,m1w1zY!uQ'Z-Y1[5]Q%pvQ%xyQ&P|Q&e!VS'R!e-QQ'a!iS'g!r!xS*c$|*hQ+f%yQ+s&RQ,X&_Q-W'YQ-b'bQ-j'hQ/|*jQ1f,YR;Y:g%OdOPWXYZstuvw!Z!`!g!o#R#V#Y#c#n#t#x#{$O$P$Q$R$S$T$U$V$W$X$Y$a$e%j%p%}&f&i&m&o&p&r&v'O']'m'}(P(V(^(r(v(z)y+O+S,e,h,m-^-f-t-z.l.s0[0a0q1_1o1p1r1t1w1z1|2m2s3Z5Y5d5t5u5x6]7w7|8]8gS#p]:d!r)[$[$m'S)n,y,|.{2]3p5W6S9W9i:c:f:g:j:k:l:m:n:o:p:q:r:s:t:u:v:w:{;Y;Z;[;^;e;f;o;p<]Q*u%ZQ+k%{Q,Z&bQ,b&jQ.c;QQ0h+^Q0l+`Q0w+lQ1n,`Q2{.[Q4v0rQ5k1gQ6i3PQ6u;RQ7h4wR8m6j&|kOPWXYZstuvw!Z!`!g!o#R#V#Y#c#n#t#x#{$O$P$Q$R$S$T$U$V$W$X$Y$[$a$e$m%j%p%}&f&i&j&m&o&p&r&v'O'S']'m'}(P(V(^(r(v(z)n)y+O+S+^,e,h,m,y,|-^-f-t-z.[.l.s.{0[0a0q1_1o1p1r1t1w1z1|2]2m2s3P3Z3p5W5Y5d5t5u5x6S6]6j7w7|8]8g9W9i:c:f:g:j:k:l:m:n:o:p:q:r:s:t:u:v:w:{;Y;Z;[;^;e;f;o;p<]t!nQ!r!u!x!y'R'Y'Z'g'h'i-Q-W-Y-j1[5]5_$v$si#u#w$c$d$x${%O%Q%[%]%a)u){)}*P*R*Y*`*p*q+]+`+w+z.Z.i/Z/j/k/m0Q0S0^1R1U1^3O3x4S4[4f4n4p5c6g7T7^7y8j8w9[9n:O:W:y:z:|:};O;P;S;T;U;V;W;X;_;`;a;b;c;d;g;h;i;j;k;l;m;n;q;r < TypeParamList TypeDefinition extends ThisType this LiteralType ArithOp Number BooleanLiteral TemplateType InterpolationEnd Interpolation InterpolationStart NullType null VoidType void TypeofType typeof MemberExpression . ?. PropertyName [ TemplateString Escape Interpolation super RegExp ] ArrayExpression Spread , } { ObjectExpression Property async get set PropertyDefinition Block : NewExpression new TypeArgList CompareOp < ) ( ArgList UnaryExpression delete LogicOp BitOp YieldExpression yield AwaitExpression await ParenthesizedExpression ClassExpression class ClassBody MethodDeclaration Decorator @ MemberExpression PrivatePropertyName CallExpression declare Privacy static abstract override PrivatePropertyDefinition PropertyDeclaration readonly accessor Optional TypeAnnotation Equals StaticBlock FunctionExpression ArrowFunction ParamList ParamList ArrayPattern ObjectPattern PatternProperty Privacy readonly Arrow MemberExpression BinaryExpression ArithOp ArithOp ArithOp ArithOp BitOp CompareOp instanceof satisfies in const CompareOp BitOp BitOp BitOp LogicOp LogicOp ConditionalExpression LogicOp LogicOp AssignmentExpression UpdateOp PostfixExpression CallExpression TaggedTemplateExpression DynamicImport import ImportMeta JSXElement JSXSelfCloseEndTag JSXSelfClosingTag JSXIdentifier JSXBuiltin JSXIdentifier JSXNamespacedName JSXMemberExpression JSXSpreadAttribute JSXAttribute JSXAttributeValue JSXEscape JSXEndTag JSXOpenTag JSXFragmentTag JSXText JSXEscape JSXStartCloseTag JSXCloseTag PrefixCast ArrowFunction TypeParamList SequenceExpression KeyofType keyof UniqueType unique ImportType InferredType infer TypeName ParenthesizedType FunctionSignature ParamList NewSignature IndexedType TupleType Label ArrayType ReadonlyType ObjectType MethodType PropertyType IndexSignature PropertyDefinition CallSignature TypePredicate is NewSignature new UnionType LogicOp IntersectionType LogicOp ConditionalType ParameterizedType ClassDeclaration abstract implements type VariableDeclaration let var using TypeAliasDeclaration InterfaceDeclaration interface EnumDeclaration enum EnumBody NamespaceDeclaration namespace module AmbientDeclaration declare GlobalDeclaration global ClassDeclaration ClassBody AmbientFunctionDeclaration ExportGroup VariableName VariableName ImportDeclaration ImportGroup ForStatement for ForSpec ForInSpec ForOfSpec of WhileStatement while WithStatement with DoStatement do IfStatement if else SwitchStatement switch SwitchBody CaseLabel case DefaultLabel TryStatement try CatchClause catch FinallyClause finally ReturnStatement return ThrowStatement throw BreakStatement break ContinueStatement continue DebuggerStatement debugger LabeledStatement ExpressionStatement SingleExpression SingleClassItem",maxTerm:371,context:Fu,nodeProps:[["isolate",-8,4,5,13,33,35,48,50,52,""],["group",-26,8,16,18,65,201,205,209,210,212,215,218,228,230,236,238,240,242,245,251,257,259,261,263,265,267,268,"Statement",-32,12,13,28,31,32,38,48,51,52,54,59,67,75,79,81,83,84,106,107,116,117,134,137,139,140,141,142,144,145,164,165,167,"Expression",-23,27,29,33,37,39,41,168,170,172,173,175,176,177,179,180,181,183,184,185,195,197,199,200,"Type",-3,87,99,105,"ClassItem"],["openedBy",22,"<",34,"InterpolationStart",53,"[",57,"{",72,"(",157,"JSXStartCloseTag"],["closedBy",23,">",36,"InterpolationEnd",47,"]",58,"}",73,")",162,"JSXEndTag"]],propSources:[ip],skippedNodes:[0,4,5,271],repeatNodeCount:37,tokenData:"$Fj(CSR!bOX%ZXY+gYZ-yZ[+g[]%Z]^.c^p%Zpq+gqr/mrs3cst:_tuEruvJSvwLkwx! Yxy!'iyz!(sz{!)}{|!,q|}!.O}!O!,q!O!P!/Y!P!Q!9j!Q!R#8g!R![#:v![!]#Gv!]!^#IS!^!_#J^!_!`#Ns!`!a$#_!a!b$(l!b!c$,k!c!}Er!}#O$-u#O#P$/P#P#Q$4h#Q#R$5r#R#SEr#S#T$7P#T#o$8Z#o#p$q#r#s$?}#s$f%Z$f$g+g$g#BYEr#BY#BZ$AX#BZ$ISEr$IS$I_$AX$I_$I|Er$I|$I}$Dd$I}$JO$Dd$JO$JTEr$JT$JU$AX$JU$KVEr$KV$KW$AX$KW&FUEr&FU&FV$AX&FV;'SEr;'S;=`I|<%l?HTEr?HT?HU$AX?HUOEr(n%d_$f&j(Op(R!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z&j&hT$f&jO!^&c!_#o&c#p;'S&c;'S;=`&w<%lO&c&j&zP;=`<%l&c'|'U]$f&j(R!bOY&}YZ&cZw&}wx&cx!^&}!^!_'}!_#O&}#O#P&c#P#o&}#o#p'}#p;'S&};'S;=`(l<%lO&}!b(SU(R!bOY'}Zw'}x#O'}#P;'S'};'S;=`(f<%lO'}!b(iP;=`<%l'}'|(oP;=`<%l&}'[(y]$f&j(OpOY(rYZ&cZr(rrs&cs!^(r!^!_)r!_#O(r#O#P&c#P#o(r#o#p)r#p;'S(r;'S;=`*a<%lO(rp)wU(OpOY)rZr)rs#O)r#P;'S)r;'S;=`*Z<%lO)rp*^P;=`<%l)r'[*dP;=`<%l(r#S*nX(Op(R!bOY*gZr*grs'}sw*gwx)rx#O*g#P;'S*g;'S;=`+Z<%lO*g#S+^P;=`<%l*g(n+dP;=`<%l%Z(CS+rq$f&j(Op(R!b't(;dOX%ZXY+gYZ&cZ[+g[p%Zpq+gqr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p$f%Z$f$g+g$g#BY%Z#BY#BZ+g#BZ$IS%Z$IS$I_+g$I_$JT%Z$JT$JU+g$JU$KV%Z$KV$KW+g$KW&FU%Z&FU&FV+g&FV;'S%Z;'S;=`+a<%l?HT%Z?HT?HU+g?HUO%Z(CS.ST(P#S$f&j'u(;dO!^&c!_#o&c#p;'S&c;'S;=`&w<%lO&c(CS.n_$f&j(Op(R!b'u(;dOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z%#`/x`$f&j!o$Ip(Op(R!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`0z!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z%#S1V`#t$Id$f&j(Op(R!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`2X!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z%#S2d_#t$Id$f&j(Op(R!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z$/|3l_'}$(n$f&j(R!bOY4kYZ5qZr4krs7nsw4kwx5qx!^4k!^!_8p!_#O4k#O#P5q#P#o4k#o#p8p#p;'S4k;'S;=`:X<%lO4k(^4r_$f&j(R!bOY4kYZ5qZr4krs7nsw4kwx5qx!^4k!^!_8p!_#O4k#O#P5q#P#o4k#o#p8p#p;'S4k;'S;=`:X<%lO4k&z5vX$f&jOr5qrs6cs!^5q!^!_6y!_#o5q#o#p6y#p;'S5q;'S;=`7h<%lO5q&z6jT$a`$f&jO!^&c!_#o&c#p;'S&c;'S;=`&w<%lO&c`6|TOr6yrs7]s;'S6y;'S;=`7b<%lO6y`7bO$a``7eP;=`<%l6y&z7kP;=`<%l5q(^7w]$a`$f&j(R!bOY&}YZ&cZw&}wx&cx!^&}!^!_'}!_#O&}#O#P&c#P#o&}#o#p'}#p;'S&};'S;=`(l<%lO&}!r8uZ(R!bOY8pYZ6yZr8prs9hsw8pwx6yx#O8p#O#P6y#P;'S8p;'S;=`:R<%lO8p!r9oU$a`(R!bOY'}Zw'}x#O'}#P;'S'};'S;=`(f<%lO'}!r:UP;=`<%l8p(^:[P;=`<%l4k#%|:hh$f&j(Op(R!bOY%ZYZ&cZq%Zqr`#P#o`x!^=^!^!_?q!_#O=^#O#P>`#P#o=^#o#p?q#p;'S=^;'S;=`@h<%lO=^&n>gXVS$f&jOY>`YZ&cZ!^>`!^!_?S!_#o>`#o#p?S#p;'S>`;'S;=`?k<%lO>`S?XSVSOY?SZ;'S?S;'S;=`?e<%lO?SS?hP;=`<%l?S&n?nP;=`<%l>`!f?xWVS(R!bOY?qZw?qwx?Sx#O?q#O#P?S#P;'S?q;'S;=`@b<%lO?q!f@eP;=`<%l?q(Q@kP;=`<%l=^'`@w]VS$f&j(OpOY@nYZ&cZr@nrs>`s!^@n!^!_Ap!_#O@n#O#P>`#P#o@n#o#pAp#p;'S@n;'S;=`Bg<%lO@ntAwWVS(OpOYApZrAprs?Ss#OAp#O#P?S#P;'SAp;'S;=`Ba<%lOAptBdP;=`<%lAp'`BjP;=`<%l@n#WBvYVS(Op(R!bOYBmZrBmrs?qswBmwxApx#OBm#O#P?S#P;'SBm;'S;=`Cf<%lOBm#WCiP;=`<%lBm(rCoP;=`<%lQ^$f&j!USOY!=yYZ&cZ!P!=y!P!Q!>|!Q!^!=y!^!_!@Y!_!}!=y!}#O!Bw#O#P!Dj#P#o!=y#o#p!@Y#p;'S!=y;'S;=`!E[<%lO!=y&n!?Ta$f&j!USO!^&c!_#Z&c#Z#[!>|#[#]&c#]#^!>|#^#a&c#a#b!>|#b#g&c#g#h!>|#h#i&c#i#j!>|#j#m&c#m#n!>|#n#o&c#p;'S&c;'S;=`&w<%lO&cS!@_X!USOY!@YZ!P!@Y!P!Q!@z!Q!}!@Y!}#O!Ac#O#P!Bb#P;'S!@Y;'S;=`!Bq<%lO!@YS!APU!US#Z#[!@z#]#^!@z#a#b!@z#g#h!@z#i#j!@z#m#n!@zS!AfVOY!AcZ#O!Ac#O#P!A{#P#Q!@Y#Q;'S!Ac;'S;=`!B[<%lO!AcS!BOSOY!AcZ;'S!Ac;'S;=`!B[<%lO!AcS!B_P;=`<%l!AcS!BeSOY!@YZ;'S!@Y;'S;=`!Bq<%lO!@YS!BtP;=`<%l!@Y&n!B|[$f&jOY!BwYZ&cZ!^!Bw!^!_!Ac!_#O!Bw#O#P!Cr#P#Q!=y#Q#o!Bw#o#p!Ac#p;'S!Bw;'S;=`!Dd<%lO!Bw&n!CwX$f&jOY!BwYZ&cZ!^!Bw!^!_!Ac!_#o!Bw#o#p!Ac#p;'S!Bw;'S;=`!Dd<%lO!Bw&n!DgP;=`<%l!Bw&n!DoX$f&jOY!=yYZ&cZ!^!=y!^!_!@Y!_#o!=y#o#p!@Y#p;'S!=y;'S;=`!E[<%lO!=y&n!E_P;=`<%l!=y(Q!Eki$f&j(R!b!USOY&}YZ&cZw&}wx&cx!^&}!^!_'}!_#O&}#O#P&c#P#Z&}#Z#[!Eb#[#]&}#]#^!Eb#^#a&}#a#b!Eb#b#g&}#g#h!Eb#h#i&}#i#j!Eb#j#m&}#m#n!Eb#n#o&}#o#p'}#p;'S&};'S;=`(l<%lO&}!f!GaZ(R!b!USOY!GYZw!GYwx!@Yx!P!GY!P!Q!HS!Q!}!GY!}#O!Ic#O#P!Bb#P;'S!GY;'S;=`!JZ<%lO!GY!f!HZb(R!b!USOY'}Zw'}x#O'}#P#Z'}#Z#[!HS#[#]'}#]#^!HS#^#a'}#a#b!HS#b#g'}#g#h!HS#h#i'}#i#j!HS#j#m'}#m#n!HS#n;'S'};'S;=`(f<%lO'}!f!IhX(R!bOY!IcZw!Icwx!Acx#O!Ic#O#P!A{#P#Q!GY#Q;'S!Ic;'S;=`!JT<%lO!Ic!f!JWP;=`<%l!Ic!f!J^P;=`<%l!GY(Q!Jh^$f&j(R!bOY!JaYZ&cZw!Jawx!Bwx!^!Ja!^!_!Ic!_#O!Ja#O#P!Cr#P#Q!Q#V#X%Z#X#Y!4|#Y#b%Z#b#c#Zd$f&j(Op(R!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q!R#?i!R!S#?i!S!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S#?i#S#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z$/l#?tf$f&j(Op(R!bo$'|OY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q!R#?i!R!S#?i!S!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S#?i#S#b%Z#b#c#np[t]||-1},{term:334,get:t=>rp[t]||-1},{term:70,get:t=>sp[t]||-1}],tokenPrec:14638}),ap=[uc("function ${name}(${params}) {\n\t${}\n}",{label:"function",detail:"definition",type:"keyword"}),uc("for (let ${index} = 0; ${index} < ${bound}; ${index}++) {\n\t${}\n}",{label:"for",detail:"loop",type:"keyword"}),uc("for (let ${name} of ${collection}) {\n\t${}\n}",{label:"for",detail:"of loop",type:"keyword"}),uc("do {\n\t${}\n} while (${})",{label:"do",detail:"loop",type:"keyword"}),uc("while (${}) {\n\t${}\n}",{label:"while",detail:"loop",type:"keyword"}),uc("try {\n\t${}\n} catch (${error}) {\n\t${}\n}",{label:"try",detail:"/ catch block",type:"keyword"}),uc("if (${}) {\n\t${}\n}",{label:"if",detail:"block",type:"keyword"}),uc("if (${}) {\n\t${}\n} else {\n\t${}\n}",{label:"if",detail:"/ else block",type:"keyword"}),uc("class ${name} {\n\tconstructor(${params}) {\n\t\t${}\n\t}\n}",{label:"class",detail:"definition",type:"keyword"}),uc('import {${names}} from "${module}"\n${}',{label:"import",detail:"named",type:"keyword"}),uc('import ${name} from "${module}"\n${}',{label:"import",detail:"default",type:"keyword"})],lp=ap.concat([uc("interface ${name} {\n\t${}\n}",{label:"interface",detail:"definition",type:"keyword"}),uc("type ${name} = ${type}",{label:"type",detail:"definition",type:"keyword"}),uc("enum ${name} {\n\t${}\n}",{label:"enum",detail:"definition",type:"keyword"})]),hp=new So,cp=new Set(["Script","Block","FunctionExpression","FunctionDeclaration","ArrowFunction","MethodDeclaration","ForStatement"]);function up(t){return(e,i)=>{let n=e.node.getChild("VariableDefinition");return n&&i(n,t),!0}}const pp=["FunctionDeclaration"],dp={FunctionDeclaration:up("function"),ClassDeclaration:up("class"),ClassExpression:()=>!0,EnumDeclaration:up("constant"),TypeAliasDeclaration:up("type"),NamespaceDeclaration:up("namespace"),VariableDefinition(t,e){t.matchContext(pp)||e(t,"variable")},TypeDefinition(t,e){e(t,"type")},__proto__:null};function fp(t,e){let i=hp.get(e);if(i)return i;let n=[],r=!0;function s(e,i){let r=t.sliceString(e.from,e.to);n.push({label:r,type:i})}return e.cursor(to.IncludeAnonymous).iterate((e=>{if(r)r=!1;else if(e.name){let t=dp[e.name];if(t&&t(e,s)||cp.has(e.name))return!1}else if(e.to-e.from>8192){for(let i of fp(t,e.node))n.push(i);return!1}})),hp.set(e,n),n}const Op=/^[\w$\xa1-\uffff][\w$\d\xa1-\uffff]*$/,mp=["TemplateString","String","RegExp","LineComment","BlockComment","VariableDefinition","TypeDefinition","Label","PropertyDefinition","PropertyName","PrivatePropertyDefinition","PrivatePropertyName",".","?."];function gp(t){let e=ga(t.state).resolveInner(t.pos,-1);if(mp.indexOf(e.name)>-1)return null;let i="VariableName"==e.name||e.to-e.from<20&&Op.test(t.state.sliceDoc(e.from,e.to));if(!i&&!t.explicit)return null;let n=[];for(let i=e;i;i=i.parent)cp.has(i.name)&&(n=n.concat(fp(t.state.doc,i)));return{options:n,from:i?e.from:t.pos,validFor:Op}}const yp=ma.define({name:"javascript",parser:op.configure({props:[Xa.add({IfStatement:Da({except:/^\s*({|else\b)/}),TryStatement:Da({except:/^\s*({|catch\b|finally\b)/}),LabeledStatement:t=>t.baseIndent,SwitchBody:t=>{let e=t.textAfter,i=/^\s*\}/.test(e),n=/^\s*(case|default)\b/.test(e);return t.baseIndent+(i?0:n?1:2)*t.unit},Block:Ia({closing:"}"}),ArrowFunction:t=>t.baseIndent+t.unit,"TemplateString BlockComment":()=>null,"Statement Property":Da({except:/^{/}),JSXElement(t){let e=/^\s*<\//.test(t.textAfter);return t.lineIndent(t.node.from)+(e?0:t.unit)},JSXEscape(t){let e=/\s*\}/.test(t.textAfter);return t.lineIndent(t.node.from)+(e?0:t.unit)},"JSXOpenTag JSXSelfClosingTag":t=>t.column(t.node.from)+t.unit}),Ma.add({"Block ClassBody SwitchBody EnumBody ObjectExpression ArrayExpression ObjectType":Na,BlockComment:t=>({from:t.from+2,to:t.to-2})})]}),languageData:{closeBrackets:{brackets:["(","[","{","'",'"',"`"]},commentTokens:{line:"//",block:{open:"/*",close:"*/"}},indentOnInput:/^\s*(?:case |default:|\{|\}|<\/)$/,wordChars:"$"}}),xp={test:t=>/^JSX/.test(t.name),facet:pa({commentTokens:{block:{open:"{/*",close:"*/}"}}})},Sp=yp.configure({dialect:"ts"},"typescript"),wp=yp.configure({dialect:"jsx",props:[da.add((t=>t.isTop?[xp]:void 0))]}),bp=yp.configure({dialect:"jsx ts",props:[da.add((t=>t.isTop?[xp]:void 0))]},"typescript");let vp=t=>({label:t,type:"keyword"});const kp="break case const continue default delete export extends false finally in instanceof let new return static super switch this throw true typeof var yield".split(" ").map(vp),Qp=kp.concat(["declare","implements","private","protected","public"].map(vp));function Pp(t={}){let e=t.jsx?t.typescript?bp:wp:t.typescript?Sp:yp,i=t.typescript?lp.concat(Qp):ap.concat(kp);return new $a(e,[yp.data.of({autocomplete:(n=mp,r=Lh(i),t=>{for(let e=ga(t.state).resolveInner(t.pos,-1);e;e=e.parent){if(n.indexOf(e.name)>-1)return null;if(e.type.isTop)break}return r(t)})}),yp.data.of({autocomplete:gp}),t.jsx?Cp:[]]);var n,r}function $p(t,e,i=t.length){for(let n=null==e?void 0:e.firstChild;n;n=n.nextSibling)if("JSXIdentifier"==n.name||"JSXBuiltin"==n.name||"JSXNamespacedName"==n.name||"JSXMemberExpression"==n.name)return t.sliceString(n.from,Math.min(n.to,i));return""}const Zp="object"==typeof navigator&&/Android\b/.test(navigator.userAgent),Cp=Dr.inputHandler.of(((t,e,i,n,r)=>{if((Zp?t.composing:t.compositionStarted)||t.state.readOnly||e!=i||">"!=n&&"/"!=n||!yp.isActiveAt(t.state,e,-1))return!1;let s=r(),{state:o}=s,a=o.changeByRange((t=>{var e;let i,{head:r}=t,s=ga(o).resolveInner(r-1,-1);if("JSXStartTag"==s.name&&(s=s.parent),o.doc.sliceString(r-1,r)!=n||"JSXAttributeValue"==s.name&&s.to>r);else{if(">"==n&&"JSXFragmentTag"==s.name)return{range:t,changes:{from:r,insert:""}};if("/"==n&&"JSXStartCloseTag"==s.name){let t=s.parent,n=t.parent;if(n&&t.from==r-2&&((i=$p(o.doc,n.firstChild,r))||"JSXFragmentTag"==(null===(e=n.firstChild)||void 0===e?void 0:e.name))){let t=`${i}>`;return{range:_.cursor(r+t.length,-1),changes:{from:r,insert:t}}}}else if(">"==n){let e=function(t){for(;;){if("JSXOpenTag"==t.name||"JSXSelfClosingTag"==t.name||"JSXFragmentTag"==t.name)return t;if("JSXEscape"==t.name||!t.parent)return null;t=t.parent}}(s);if(e&&"JSXOpenTag"==e.name&&!/^\/?>|^<\//.test(o.doc.sliceString(r,r+2))&&(i=$p(o.doc,e,r)))return{range:t,changes:{from:r,insert:``}}}}return{range:t}}));return!a.changes.empty&&(t.dispatch([s,o.update(a,{userEvent:"input.complete",scrollIntoView:!0})]),!0)})),Tp=["_blank","_self","_top","_parent"],Ap=["ascii","utf-8","utf-16","latin1","latin1"],_p=["get","post","put","delete"],Ep=["application/x-www-form-urlencoded","multipart/form-data","text/plain"],Xp=["true","false"],Rp={},Vp={a:{attrs:{href:null,ping:null,type:null,media:null,target:Tp,hreflang:null}},abbr:Rp,address:Rp,area:{attrs:{alt:null,coords:null,href:null,target:null,ping:null,media:null,hreflang:null,type:null,shape:["default","rect","circle","poly"]}},article:Rp,aside:Rp,audio:{attrs:{src:null,mediagroup:null,crossorigin:["anonymous","use-credentials"],preload:["none","metadata","auto"],autoplay:["autoplay"],loop:["loop"],controls:["controls"]}},b:Rp,base:{attrs:{href:null,target:Tp}},bdi:Rp,bdo:Rp,blockquote:{attrs:{cite:null}},body:Rp,br:Rp,button:{attrs:{form:null,formaction:null,name:null,value:null,autofocus:["autofocus"],disabled:["autofocus"],formenctype:Ep,formmethod:_p,formnovalidate:["novalidate"],formtarget:Tp,type:["submit","reset","button"]}},canvas:{attrs:{width:null,height:null}},caption:Rp,center:Rp,cite:Rp,code:Rp,col:{attrs:{span:null}},colgroup:{attrs:{span:null}},command:{attrs:{type:["command","checkbox","radio"],label:null,icon:null,radiogroup:null,command:null,title:null,disabled:["disabled"],checked:["checked"]}},data:{attrs:{value:null}},datagrid:{attrs:{disabled:["disabled"],multiple:["multiple"]}},datalist:{attrs:{data:null}},dd:Rp,del:{attrs:{cite:null,datetime:null}},details:{attrs:{open:["open"]}},dfn:Rp,div:Rp,dl:Rp,dt:Rp,em:Rp,embed:{attrs:{src:null,type:null,width:null,height:null}},eventsource:{attrs:{src:null}},fieldset:{attrs:{disabled:["disabled"],form:null,name:null}},figcaption:Rp,figure:Rp,footer:Rp,form:{attrs:{action:null,name:null,"accept-charset":Ap,autocomplete:["on","off"],enctype:Ep,method:_p,novalidate:["novalidate"],target:Tp}},h1:Rp,h2:Rp,h3:Rp,h4:Rp,h5:Rp,h6:Rp,head:{children:["title","base","link","style","meta","script","noscript","command"]},header:Rp,hgroup:Rp,hr:Rp,html:{attrs:{manifest:null}},i:Rp,iframe:{attrs:{src:null,srcdoc:null,name:null,width:null,height:null,sandbox:["allow-top-navigation","allow-same-origin","allow-forms","allow-scripts"],seamless:["seamless"]}},img:{attrs:{alt:null,src:null,ismap:null,usemap:null,width:null,height:null,crossorigin:["anonymous","use-credentials"]}},input:{attrs:{alt:null,dirname:null,form:null,formaction:null,height:null,list:null,max:null,maxlength:null,min:null,name:null,pattern:null,placeholder:null,size:null,src:null,step:null,value:null,width:null,accept:["audio/*","video/*","image/*"],autocomplete:["on","off"],autofocus:["autofocus"],checked:["checked"],disabled:["disabled"],formenctype:Ep,formmethod:_p,formnovalidate:["novalidate"],formtarget:Tp,multiple:["multiple"],readonly:["readonly"],required:["required"],type:["hidden","text","search","tel","url","email","password","datetime","date","month","week","time","datetime-local","number","range","color","checkbox","radio","file","submit","image","reset","button"]}},ins:{attrs:{cite:null,datetime:null}},kbd:Rp,keygen:{attrs:{challenge:null,form:null,name:null,autofocus:["autofocus"],disabled:["disabled"],keytype:["RSA"]}},label:{attrs:{for:null,form:null}},legend:Rp,li:{attrs:{value:null}},link:{attrs:{href:null,type:null,hreflang:null,media:null,sizes:["all","16x16","16x16 32x32","16x16 32x32 64x64"]}},map:{attrs:{name:null}},mark:Rp,menu:{attrs:{label:null,type:["list","context","toolbar"]}},meta:{attrs:{content:null,charset:Ap,name:["viewport","application-name","author","description","generator","keywords"],"http-equiv":["content-language","content-type","default-style","refresh"]}},meter:{attrs:{value:null,min:null,low:null,high:null,max:null,optimum:null}},nav:Rp,noscript:Rp,object:{attrs:{data:null,type:null,name:null,usemap:null,form:null,width:null,height:null,typemustmatch:["typemustmatch"]}},ol:{attrs:{reversed:["reversed"],start:null,type:["1","a","A","i","I"]},children:["li","script","template","ul","ol"]},optgroup:{attrs:{disabled:["disabled"],label:null}},option:{attrs:{disabled:["disabled"],label:null,selected:["selected"],value:null}},output:{attrs:{for:null,form:null,name:null}},p:Rp,param:{attrs:{name:null,value:null}},pre:Rp,progress:{attrs:{value:null,max:null}},q:{attrs:{cite:null}},rp:Rp,rt:Rp,ruby:Rp,samp:Rp,script:{attrs:{type:["text/javascript"],src:null,async:["async"],defer:["defer"],charset:Ap}},section:Rp,select:{attrs:{form:null,name:null,size:null,autofocus:["autofocus"],disabled:["disabled"],multiple:["multiple"]}},slot:{attrs:{name:null}},small:Rp,source:{attrs:{src:null,type:null,media:null}},span:Rp,strong:Rp,style:{attrs:{type:["text/css"],media:null,scoped:null}},sub:Rp,summary:Rp,sup:Rp,table:Rp,tbody:Rp,td:{attrs:{colspan:null,rowspan:null,headers:null}},template:Rp,textarea:{attrs:{dirname:null,form:null,maxlength:null,name:null,placeholder:null,rows:null,cols:null,autofocus:["autofocus"],disabled:["disabled"],readonly:["readonly"],required:["required"],wrap:["soft","hard"]}},tfoot:Rp,th:{attrs:{colspan:null,rowspan:null,headers:null,scope:["row","col","rowgroup","colgroup"]}},thead:Rp,time:{attrs:{datetime:null}},title:Rp,tr:Rp,track:{attrs:{src:null,label:null,default:null,kind:["subtitles","captions","descriptions","chapters","metadata"],srclang:null}},ul:{children:["li","script","template","ul","ol"]},var:Rp,video:{attrs:{src:null,poster:null,width:null,height:null,crossorigin:["anonymous","use-credentials"],preload:["auto","metadata","none"],autoplay:["autoplay"],mediagroup:["movie"],muted:["muted"],controls:["controls"]}},wbr:Rp},Yp={accesskey:null,class:null,contenteditable:Xp,contextmenu:null,dir:["ltr","rtl","auto"],draggable:["true","false","auto"],dropzone:["copy","move","link","string:","file:"],hidden:["hidden"],id:null,inert:["inert"],itemid:null,itemprop:null,itemref:null,itemscope:["itemscope"],itemtype:null,lang:["ar","bn","de","en-GB","en-US","es","fr","hi","id","ja","pa","pt","ru","tr","zh"],spellcheck:Xp,autocorrect:Xp,autocapitalize:Xp,style:null,tabindex:null,title:null,translate:["yes","no"],rel:["stylesheet","alternate","author","bookmark","help","license","next","nofollow","noreferrer","prefetch","prev","search","tag"],role:"alert application article banner button cell checkbox complementary contentinfo dialog document feed figure form grid gridcell heading img list listbox listitem main navigation region row rowgroup search switch tab table tabpanel textbox timer".split(" "),"aria-activedescendant":null,"aria-atomic":Xp,"aria-autocomplete":["inline","list","both","none"],"aria-busy":Xp,"aria-checked":["true","false","mixed","undefined"],"aria-controls":null,"aria-describedby":null,"aria-disabled":Xp,"aria-dropeffect":null,"aria-expanded":["true","false","undefined"],"aria-flowto":null,"aria-grabbed":["true","false","undefined"],"aria-haspopup":Xp,"aria-hidden":Xp,"aria-invalid":["true","false","grammar","spelling"],"aria-label":null,"aria-labelledby":null,"aria-level":null,"aria-live":["off","polite","assertive"],"aria-multiline":Xp,"aria-multiselectable":Xp,"aria-owns":null,"aria-posinset":null,"aria-pressed":["true","false","mixed","undefined"],"aria-readonly":Xp,"aria-relevant":null,"aria-required":Xp,"aria-selected":["true","false","undefined"],"aria-setsize":null,"aria-sort":["ascending","descending","none","other"],"aria-valuemax":null,"aria-valuemin":null,"aria-valuenow":null,"aria-valuetext":null},Wp="beforeunload copy cut dragstart dragover dragleave dragenter dragend drag paste focus blur change click load mousedown mouseenter mouseleave mouseup keydown keyup resize scroll unload".split(" ").map((t=>"on"+t));for(let t of Wp)Yp[t]=null;class qp{constructor(t,e){this.tags=Object.assign(Object.assign({},Vp),t),this.globalAttrs=Object.assign(Object.assign({},Yp),e),this.allTags=Object.keys(this.tags),this.globalAttrNames=Object.keys(this.globalAttrs)}}function Ip(t,e,i=t.length){if(!e)return"";let n=e.firstChild,r=n&&n.getChild("TagName");return r?t.sliceString(r.from,Math.min(r.to,i)):""}function jp(t,e=!1){for(;t;t=t.parent)if("Element"==t.name){if(!e)return t;e=!1}return null}function Dp(t,e,i){let n=i.tags[Ip(t,jp(e))];return(null==n?void 0:n.children)||i.allTags}function Mp(t,e){let i=[];for(let n=jp(e);n&&!n.type.isTop;n=jp(n.parent)){let r=Ip(t,n);if(r&&"CloseTag"==n.lastChild.name)break;r&&i.indexOf(r)<0&&("EndTag"==e.name||e.from>=n.firstChild.to)&&i.push(r)}return i}qp.default=new qp;const Np=/^[:\-\.\w\u00b7-\uffff]*$/;function zp(t,e,i,n,r){let s=/\s*>/.test(t.sliceDoc(r,r+5))?"":">",o=jp(i,!0);return{from:n,to:r,options:Dp(t.doc,o,e).map((t=>({label:t,type:"type"}))).concat(Mp(t.doc,i).map(((t,e)=>({label:"/"+t,apply:"/"+t+s,type:"type",boost:99-e})))),validFor:/^\/?[:\-\.\w\u00b7-\uffff]*$/}}function Bp(t,e,i,n){let r=/\s*>/.test(t.sliceDoc(n,n+5))?"":">";return{from:i,to:n,options:Mp(t.doc,e).map(((t,e)=>({label:t,apply:t+r,type:"type",boost:99-e}))),validFor:Np}}function Lp(t,e){let{state:i,pos:n}=e,r=ga(i).resolveInner(n,-1),s=r.resolve(n);for(let t,e=n;s==r&&(t=r.childBefore(e));){let i=t.lastChild;if(!i||!i.type.isError||i.from({label:t,type:"property"}))),validFor:Np}}(i,t,r,"AttributeName"==r.name?r.from:n,n):"Is"==r.name||"AttributeValue"==r.name||"UnquotedAttributeValue"==r.name?function(t,e,i,n,r){var s;let o,a=null===(s=i.parent)||void 0===s?void 0:s.getChild("AttributeName"),l=[];if(a){let s=t.sliceDoc(a.from,a.to),h=e.globalAttrs[s];if(!h){let n=jp(i),r=n?e.tags[Ip(t.doc,n)]:null;h=(null==r?void 0:r.attrs)&&r.attrs[s]}if(h){let e=t.sliceDoc(n,r).toLowerCase(),i='"',s='"';/^['"]/.test(e)?(o='"'==e[0]?/^[^"]*$/:/^[^']*$/,i="",s=t.sliceDoc(r,r+1)==e[0]?"":e[0],e=e.slice(1),n++):o=/^[^\s<>='"]*$/;for(let t of h)l.push({label:t,apply:i+t+s,type:"constant"})}}return{from:n,to:r,options:l,validFor:o}}(i,t,r,"Is"==r.name?n:r.from,n):!e.explicit||"Element"!=s.name&&"Text"!=s.name&&"Document"!=s.name?null:function(t,e,i,n){let r=[],s=0;for(let n of Dp(t.doc,i,e))r.push({label:"<"+n,type:"type"});for(let e of Mp(t.doc,i))r.push({label:"",type:"type",boost:99-s++});return{from:n,to:n,options:r,validFor:/^<\/?[:\-\.\w\u00b7-\uffff]*$/}}(i,t,r,n)}function Gp(t){let{extraTags:e,extraGlobalAttributes:i}=t,n=i||e?new qp(e,i):qp.default;return t=>Lp(n,t)}const Up=yp.parser.configure({top:"SingleExpression"}),Fp=[{tag:"script",attrs:t=>"text/typescript"==t.type||"ts"==t.lang,parser:Sp.parser},{tag:"script",attrs:t=>"text/babel"==t.type||"text/jsx"==t.type,parser:wp.parser},{tag:"script",attrs:t=>"text/typescript-jsx"==t.type,parser:bp.parser},{tag:"script",attrs:t=>/^(importmap|speculationrules|application\/(.+\+)?json)$/i.test(t.type),parser:Up},{tag:"script",attrs:t=>!t.type||/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i.test(t.type),parser:yp.parser},{tag:"style",attrs:t=>(!t.lang||"css"==t.lang)&&(!t.type||/^(text\/)?(x-)?(stylesheet|css)$/i.test(t.type)),parser:Gu.parser}],Hp=[{name:"style",parser:Gu.parser.configure({top:"Styles"})}].concat(Wp.map((t=>({name:t,parser:yp.parser})))),Kp=ma.define({name:"html",parser:xu.configure({props:[Xa.add({Element(t){let e=/^(\s*)(<\/)?/.exec(t.textAfter);return t.node.to<=t.pos+e[0].length?t.continue():t.lineIndent(t.node.from)+(e[2]?0:t.unit)},"OpenTag CloseTag SelfClosingTag":t=>t.column(t.node.from)+t.unit,Document(t){if(t.pos+/\s*/.exec(t.textAfter)[0].lengtht.getChild("TagName")})]}),languageData:{commentTokens:{block:{open:"\x3c!--",close:"--\x3e"}},indentOnInput:/^\s*<\/\w+\W$/,wordChars:"-._"}}),Jp=Kp.configure({wrap:vu(Fp,Hp)});function td(t={}){let e,i="";!1===t.matchClosingTags&&(i="noMatch"),!0===t.selfClosingTags&&(i=(i?i+" ":"")+"selfClosing"),(t.nestedLanguages&&t.nestedLanguages.length||t.nestedAttributes&&t.nestedAttributes.length)&&(e=vu((t.nestedLanguages||[]).concat(Fp),(t.nestedAttributes||[]).concat(Hp)));let n=e?Kp.configure({wrap:e,dialect:i}):i?Jp.configure({dialect:i}):Jp;return new $a(n,[Jp.data.of({autocomplete:Gp(t)}),!1!==t.autoCloseTags?id:[],Pp().support,new $a(Gu,Gu.data.of({autocomplete:Lu})).support])}const ed=new Set("area base br col command embed frame hr img input keygen link meta param source track wbr menuitem".split(" ")),id=Dr.inputHandler.of(((t,e,i,n,r)=>{if(t.composing||t.state.readOnly||e!=i||">"!=n&&"/"!=n||!Jp.isActiveAt(t.state,e,-1))return!1;let s=r(),{state:o}=s,a=o.changeByRange((t=>{var e,i,r;let s,a=o.doc.sliceString(t.from-1,t.to)==n,{head:l}=t,h=ga(o).resolveInner(l-1,-1);if("TagName"!=h.name&&"StartTag"!=h.name||(h=h.parent),a&&">"==n&&"OpenTag"==h.name){if("CloseTag"!=(null===(i=null===(e=h.parent)||void 0===e?void 0:e.lastChild)||void 0===i?void 0:i.name)&&(s=Ip(o.doc,h.parent,l))&&!ed.has(s)){return{range:t,changes:{from:l,to:l+(">"===o.doc.sliceString(l,l+1)?1:0),insert:``}}}}else if(a&&"/"==n&&"IncompleteCloseTag"==h.name){let t=h.parent;if(h.from==l-2&&"CloseTag"!=(null===(r=t.lastChild)||void 0===r?void 0:r.name)&&(s=Ip(o.doc,t,l))&&!ed.has(s)){let t=l+(">"===o.doc.sliceString(l,l+1)?1:0),e=`${s}>`;return{range:_.cursor(l+e.length,-1),changes:{from:l,to:t,insert:e}}}}return{range:t}}));return!a.changes.empty&&(t.dispatch([s,o.update(a,{userEvent:"input.complete",scrollIntoView:!0})]),!0)}));let nd=new F;function rd(t,e,i=[]){return vt.create({doc:t,extensions:[i,nd.of("html"==e?td():Pp()),fc,Ua(ha),ll(),Ds(),Dr.contentAttributes.of({"aria-label":"Code editor"})]})}var sd=[509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,574,3,9,9,370,1,81,2,71,10,50,3,123,2,54,14,32,10,3,1,11,3,46,10,8,0,46,9,7,2,37,13,2,9,6,1,45,0,13,2,49,13,9,3,2,11,83,11,7,0,3,0,158,11,6,9,7,3,56,1,2,6,3,1,3,2,10,0,11,1,3,6,4,4,193,17,10,9,5,0,82,19,13,9,214,6,3,8,28,1,83,16,16,9,82,12,9,9,84,14,5,9,243,14,166,9,71,5,2,1,3,3,2,0,2,1,13,9,120,6,3,6,4,0,29,9,41,6,2,3,9,0,10,10,47,15,406,7,2,7,17,9,57,21,2,13,123,5,4,0,2,1,2,6,2,0,9,9,49,4,2,1,2,4,9,9,330,3,10,1,2,0,49,6,4,4,14,9,5351,0,7,14,13835,9,87,9,39,4,60,6,26,9,1014,0,2,54,8,3,82,0,12,1,19628,1,4706,45,3,22,543,4,4,5,9,7,3,6,31,3,149,2,1418,49,513,54,5,49,9,0,15,0,23,4,2,14,1361,6,2,16,3,6,2,1,2,4,101,0,161,6,10,9,357,0,62,13,499,13,983,6,110,6,6,9,4759,9,787719,239],od=[0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,14,29,6,37,11,29,3,35,5,7,2,4,43,157,19,35,5,35,5,39,9,51,13,10,2,14,2,6,2,1,2,10,2,14,2,6,2,1,68,310,10,21,11,7,25,5,2,41,2,8,70,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,66,18,2,1,11,21,11,25,71,55,7,1,65,0,16,3,2,2,2,28,43,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,56,50,14,50,14,35,349,41,7,1,79,28,11,0,9,21,43,17,47,20,28,22,13,52,58,1,3,0,14,44,33,24,27,35,30,0,3,0,9,34,4,0,13,47,15,3,22,0,2,0,36,17,2,24,20,1,64,6,2,0,2,3,2,14,2,9,8,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,19,0,13,4,159,52,19,3,21,2,31,47,21,1,2,0,185,46,42,3,37,47,21,0,60,42,14,0,72,26,38,6,186,43,117,63,32,7,3,0,3,7,2,1,2,23,16,0,2,0,95,7,3,38,17,0,2,0,29,0,11,39,8,0,22,0,12,45,20,0,19,72,264,8,2,36,18,0,50,29,113,6,2,1,2,37,22,0,26,5,2,1,2,31,15,0,328,18,16,0,2,12,2,33,125,0,80,921,103,110,18,195,2637,96,16,1071,18,5,4026,582,8634,568,8,30,18,78,18,29,19,47,17,3,32,20,6,18,689,63,129,74,6,0,67,12,65,1,2,0,29,6135,9,1237,43,8,8936,3,2,6,2,1,2,290,16,0,30,2,3,0,15,3,9,395,2309,106,6,12,4,8,8,9,5991,84,2,70,2,1,3,0,3,1,3,3,2,11,2,0,2,6,2,64,2,3,3,7,2,6,2,27,2,3,2,4,2,0,4,6,2,339,3,24,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,7,1845,30,7,5,262,61,147,44,11,6,17,0,322,29,19,43,485,27,757,6,2,3,2,1,2,14,2,196,60,67,8,0,1205,3,2,26,2,1,2,0,3,0,2,9,2,3,2,0,2,0,7,0,5,0,2,0,2,0,2,2,2,1,2,0,3,0,2,0,2,0,2,0,2,0,2,1,2,0,3,3,2,6,2,3,2,3,2,0,2,9,2,16,6,2,2,4,2,16,4421,42719,33,4153,7,221,3,5761,15,7472,16,621,2467,541,1507,4938,6,4191],ad="ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙՠ-ֈא-תׯ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࡠ-ࡪࡰ-ࢇࢉ-ࢎࢠ-ࣉऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱৼਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡૹଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘ-ౚౝౠౡಀಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೝೞೠೡೱೲഄ-ഌഎ-ഐഒ-ഺഽൎൔ-ൖൟ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄຆ-ຊຌ-ຣລວ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜑᜟ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡸᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭌᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᲀ-ᲈᲐ-ᲺᲽ-Ჿᳩ-ᳬᳮ-ᳳᳵᳶᳺᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕ℘-ℝℤΩℨK-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞ々-〇〡-〩〱-〵〸-〼ぁ-ゖ゛-ゟァ-ヺー-ヿㄅ-ㄯㄱ-ㆎㆠ-ㆿㇰ-ㇿ㐀-䶿一-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꟊꟐꟑꟓꟕ-ꟙꟲ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꣽꣾꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭩꭰ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ",ld={3:"abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile",5:"class enum extends super const export import",6:"enum",strict:"implements interface let package private protected public static yield",strictBind:"eval arguments"},hd="break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this",cd={5:hd,"5module":hd+" export import",6:hd+" const class extends export import super"},ud=/^in(stanceof)?$/,pd=new RegExp("["+ad+"]"),dd=new RegExp("["+ad+"‌‍·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-٩ٰۖ-ۜ۟-۪ۤۧۨ-ۭ۰-۹ܑܰ-݊ަ-ް߀-߉߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࢘-࢟࣊-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣ०-९ঁ-ঃ়া-ৄেৈো-্ৗৢৣ০-৯৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑ੦-ੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣ૦-૯ૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍୕-ୗୢୣ୦-୯ஂா-ூெ-ைொ-்ௗ௦-௯ఀ-ఄ఼ా-ౄె-ైొ-్ౕౖౢౣ౦-౯ಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣ೦-೯ೳഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣ൦-൯ඁ-ඃ්ා-ුූෘ-ෟ෦-෯ෲෳัิ-ฺ็-๎๐-๙ັິ-ຼ່-໎໐-໙༘༙༠-༩༹༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှ၀-၉ၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏ-ႝ፝-፟፩-፱ᜒ-᜕ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝០-៩᠋-᠍᠏-᠙ᢩᤠ-ᤫᤰ-᤻᥆-᥏᧐-᧚ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼-᪉᪐-᪙᪰-᪽ᪿ-ᫎᬀ-ᬄ᬴-᭄᭐-᭙᭫-᭳ᮀ-ᮂᮡ-ᮭ᮰-᮹᯦-᯳ᰤ-᰷᱀-᱉᱐-᱙᳐-᳔᳒-᳨᳭᳴᳷-᳹᷀-᷿‌‍‿⁀⁔⃐-⃥⃜⃡-⃰⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯・꘠-꘩꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧ꠬ꢀꢁꢴ-ꣅ꣐-꣙꣠-꣱ꣿ-꤉ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀꧐-꧙ꧥ꧰-꧹ꨩ-ꨶꩃꩌꩍ꩐-꩙ꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭꯰-꯹ﬞ︀-️︠-︯︳︴﹍-﹏0-9_・]");function fd(t,e){for(var i=65536,n=0;nt)return!1;if((i+=e[n+1])>=t)return!0}return!1}function Od(t,e){return t<65?36===t:t<91||(t<97?95===t:t<123||(t<=65535?t>=170&&pd.test(String.fromCharCode(t)):!1!==e&&fd(t,od)))}function md(t,e){return t<48?36===t:t<58||!(t<65)&&(t<91||(t<97?95===t:t<123||(t<=65535?t>=170&&dd.test(String.fromCharCode(t)):!1!==e&&(fd(t,od)||fd(t,sd)))))}var gd=function(t,e){void 0===e&&(e={}),this.label=t,this.keyword=e.keyword,this.beforeExpr=!!e.beforeExpr,this.startsExpr=!!e.startsExpr,this.isLoop=!!e.isLoop,this.isAssign=!!e.isAssign,this.prefix=!!e.prefix,this.postfix=!!e.postfix,this.binop=e.binop||null,this.updateContext=null};function yd(t,e){return new gd(t,{beforeExpr:!0,binop:e})}var xd={beforeExpr:!0},Sd={startsExpr:!0},wd={};function bd(t,e){return void 0===e&&(e={}),e.keyword=t,wd[t]=new gd(t,e)}var vd={num:new gd("num",Sd),regexp:new gd("regexp",Sd),string:new gd("string",Sd),name:new gd("name",Sd),privateId:new gd("privateId",Sd),eof:new gd("eof"),bracketL:new gd("[",{beforeExpr:!0,startsExpr:!0}),bracketR:new gd("]"),braceL:new gd("{",{beforeExpr:!0,startsExpr:!0}),braceR:new gd("}"),parenL:new gd("(",{beforeExpr:!0,startsExpr:!0}),parenR:new gd(")"),comma:new gd(",",xd),semi:new gd(";",xd),colon:new gd(":",xd),dot:new gd("."),question:new gd("?",xd),questionDot:new gd("?."),arrow:new gd("=>",xd),template:new gd("template"),invalidTemplate:new gd("invalidTemplate"),ellipsis:new gd("...",xd),backQuote:new gd("`",Sd),dollarBraceL:new gd("${",{beforeExpr:!0,startsExpr:!0}),eq:new gd("=",{beforeExpr:!0,isAssign:!0}),assign:new gd("_=",{beforeExpr:!0,isAssign:!0}),incDec:new gd("++/--",{prefix:!0,postfix:!0,startsExpr:!0}),prefix:new gd("!/~",{beforeExpr:!0,prefix:!0,startsExpr:!0}),logicalOR:yd("||",1),logicalAND:yd("&&",2),bitwiseOR:yd("|",3),bitwiseXOR:yd("^",4),bitwiseAND:yd("&",5),equality:yd("==/!=/===/!==",6),relational:yd("/<=/>=",7),bitShift:yd("<>/>>>",8),plusMin:new gd("+/-",{beforeExpr:!0,binop:9,prefix:!0,startsExpr:!0}),modulo:yd("%",10),star:yd("*",10),slash:yd("/",10),starstar:new gd("**",{beforeExpr:!0}),coalesce:yd("??",1),_break:bd("break"),_case:bd("case",xd),_catch:bd("catch"),_continue:bd("continue"),_debugger:bd("debugger"),_default:bd("default",xd),_do:bd("do",{isLoop:!0,beforeExpr:!0}),_else:bd("else",xd),_finally:bd("finally"),_for:bd("for",{isLoop:!0}),_function:bd("function",Sd),_if:bd("if"),_return:bd("return",xd),_switch:bd("switch"),_throw:bd("throw",xd),_try:bd("try"),_var:bd("var"),_const:bd("const"),_while:bd("while",{isLoop:!0}),_with:bd("with"),_new:bd("new",{beforeExpr:!0,startsExpr:!0}),_this:bd("this",Sd),_super:bd("super",Sd),_class:bd("class",Sd),_extends:bd("extends",xd),_export:bd("export"),_import:bd("import",Sd),_null:bd("null",Sd),_true:bd("true",Sd),_false:bd("false",Sd),_in:bd("in",{beforeExpr:!0,binop:7}),_instanceof:bd("instanceof",{beforeExpr:!0,binop:7}),_typeof:bd("typeof",{beforeExpr:!0,prefix:!0,startsExpr:!0}),_void:bd("void",{beforeExpr:!0,prefix:!0,startsExpr:!0}),_delete:bd("delete",{beforeExpr:!0,prefix:!0,startsExpr:!0})},kd=/\r\n?|\n|\u2028|\u2029/,Qd=new RegExp(kd.source,"g");function Pd(t){return 10===t||13===t||8232===t||8233===t}function $d(t,e,i){void 0===i&&(i=t.length);for(var n=e;n>10),56320+(1023&t)))}var Wd=/(?:[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/,qd=function(t,e){this.line=t,this.column=e};qd.prototype.offset=function(t){return new qd(this.line,this.column+t)};var Id=function(t,e,i){this.start=e,this.end=i,null!==t.sourceFile&&(this.source=t.sourceFile)};function jd(t,e){for(var i=1,n=0;;){var r=$d(t,n,e);if(r<0)return new qd(i,e-n);++i,n=r}}var Dd={ecmaVersion:null,sourceType:"script",onInsertedSemicolon:null,onTrailingComma:null,allowReserved:null,allowReturnOutsideFunction:!1,allowImportExportEverywhere:!1,allowAwaitOutsideFunction:null,allowSuperOutsideMethod:null,allowHashBang:!1,checkPrivateFields:!0,locations:!1,onToken:null,onComment:null,ranges:!1,program:null,sourceFile:null,directSourceFile:null,preserveParens:!1},Md=!1;function Nd(t){var e={};for(var i in Dd)e[i]=t&&Ed(t,i)?t[i]:Dd[i];if("latest"===e.ecmaVersion?e.ecmaVersion=1e8:null==e.ecmaVersion?(!Md&&"object"==typeof console&&console.warn&&(Md=!0,console.warn("Since Acorn 8.0.0, options.ecmaVersion is required.\nDefaulting to 2020, but this will stop working in the future.")),e.ecmaVersion=11):e.ecmaVersion>=2015&&(e.ecmaVersion-=2009),null==e.allowReserved&&(e.allowReserved=e.ecmaVersion<5),t&&null!=t.allowHashBang||(e.allowHashBang=e.ecmaVersion>=14),Xd(e.onToken)){var n=e.onToken;e.onToken=function(t){return n.push(t)}}return Xd(e.onComment)&&(e.onComment=function(t,e){return function(i,n,r,s,o,a){var l={type:i?"Block":"Line",value:n,start:r,end:s};t.locations&&(l.loc=new Id(this,o,a)),t.ranges&&(l.range=[r,s]),e.push(l)}}(e,e.onComment)),e}var zd=256;function Bd(t,e){return 2|(t?4:0)|(e?8:0)}var Ld=function(t,e,i){this.options=t=Nd(t),this.sourceFile=t.sourceFile,this.keywords=Vd(cd[t.ecmaVersion>=6?6:"module"===t.sourceType?"5module":5]);var n="";!0!==t.allowReserved&&(n=ld[t.ecmaVersion>=6?6:5===t.ecmaVersion?5:3],"module"===t.sourceType&&(n+=" await")),this.reservedWords=Vd(n);var r=(n?n+" ":"")+ld.strict;this.reservedWordsStrict=Vd(r),this.reservedWordsStrictBind=Vd(r+" "+ld.strictBind),this.input=String(e),this.containsEsc=!1,i?(this.pos=i,this.lineStart=this.input.lastIndexOf("\n",i-1)+1,this.curLine=this.input.slice(0,this.lineStart).split(kd).length):(this.pos=this.lineStart=0,this.curLine=1),this.type=vd.eof,this.value=null,this.start=this.end=this.pos,this.startLoc=this.endLoc=this.curPosition(),this.lastTokEndLoc=this.lastTokStartLoc=null,this.lastTokStart=this.lastTokEnd=this.pos,this.context=this.initialContext(),this.exprAllowed=!0,this.inModule="module"===t.sourceType,this.strict=this.inModule||this.strictDirective(this.pos),this.potentialArrowAt=-1,this.potentialArrowInForAwait=!1,this.yieldPos=this.awaitPos=this.awaitIdentPos=0,this.labels=[],this.undefinedExports=Object.create(null),0===this.pos&&t.allowHashBang&&"#!"===this.input.slice(0,2)&&this.skipLineComment(2),this.scopeStack=[],this.enterScope(1),this.regexpState=null,this.privateNameStack=[]},Gd={inFunction:{configurable:!0},inGenerator:{configurable:!0},inAsync:{configurable:!0},canAwait:{configurable:!0},allowSuper:{configurable:!0},allowDirectSuper:{configurable:!0},treatFunctionsAsVar:{configurable:!0},allowNewDotTarget:{configurable:!0},inClassStaticBlock:{configurable:!0}};Ld.prototype.parse=function(){var t=this.options.program||this.startNode();return this.nextToken(),this.parseTopLevel(t)},Gd.inFunction.get=function(){return(2&this.currentVarScope().flags)>0},Gd.inGenerator.get=function(){return(8&this.currentVarScope().flags)>0&&!this.currentVarScope().inClassFieldInit},Gd.inAsync.get=function(){return(4&this.currentVarScope().flags)>0&&!this.currentVarScope().inClassFieldInit},Gd.canAwait.get=function(){for(var t=this.scopeStack.length-1;t>=0;t--){var e=this.scopeStack[t];if(e.inClassFieldInit||e.flags&zd)return!1;if(2&e.flags)return(4&e.flags)>0}return this.inModule&&this.options.ecmaVersion>=13||this.options.allowAwaitOutsideFunction},Gd.allowSuper.get=function(){var t=this.currentThisScope(),e=t.flags,i=t.inClassFieldInit;return(64&e)>0||i||this.options.allowSuperOutsideMethod},Gd.allowDirectSuper.get=function(){return(128&this.currentThisScope().flags)>0},Gd.treatFunctionsAsVar.get=function(){return this.treatFunctionsAsVarInScope(this.currentScope())},Gd.allowNewDotTarget.get=function(){var t=this.currentThisScope(),e=t.flags,i=t.inClassFieldInit;return(258&e)>0||i},Gd.inClassStaticBlock.get=function(){return(this.currentVarScope().flags&zd)>0},Ld.extend=function(){for(var t=[],e=arguments.length;e--;)t[e]=arguments[e];for(var i=this,n=0;n=,?^&]/.test(r)||"!"===r&&"="===this.input.charAt(n+1))}t+=e[0].length,Cd.lastIndex=t,t+=Cd.exec(this.input)[0].length,";"===this.input[t]&&t++}},Ud.eat=function(t){return this.type===t&&(this.next(),!0)},Ud.isContextual=function(t){return this.type===vd.name&&this.value===t&&!this.containsEsc},Ud.eatContextual=function(t){return!!this.isContextual(t)&&(this.next(),!0)},Ud.expectContextual=function(t){this.eatContextual(t)||this.unexpected()},Ud.canInsertSemicolon=function(){return this.type===vd.eof||this.type===vd.braceR||kd.test(this.input.slice(this.lastTokEnd,this.start))},Ud.insertSemicolon=function(){if(this.canInsertSemicolon())return this.options.onInsertedSemicolon&&this.options.onInsertedSemicolon(this.lastTokEnd,this.lastTokEndLoc),!0},Ud.semicolon=function(){this.eat(vd.semi)||this.insertSemicolon()||this.unexpected()},Ud.afterTrailingComma=function(t,e){if(this.type===t)return this.options.onTrailingComma&&this.options.onTrailingComma(this.lastTokStart,this.lastTokStartLoc),e||this.next(),!0},Ud.expect=function(t){this.eat(t)||this.unexpected()},Ud.unexpected=function(t){this.raise(null!=t?t:this.start,"Unexpected token")};var Hd=function(){this.shorthandAssign=this.trailingComma=this.parenthesizedAssign=this.parenthesizedBind=this.doubleProto=-1};Ud.checkPatternErrors=function(t,e){if(t){t.trailingComma>-1&&this.raiseRecoverable(t.trailingComma,"Comma is not permitted after the rest element");var i=e?t.parenthesizedAssign:t.parenthesizedBind;i>-1&&this.raiseRecoverable(i,e?"Assigning to rvalue":"Parenthesized pattern")}},Ud.checkExpressionErrors=function(t,e){if(!t)return!1;var i=t.shorthandAssign,n=t.doubleProto;if(!e)return i>=0||n>=0;i>=0&&this.raise(i,"Shorthand property assignments are valid only in destructuring patterns"),n>=0&&this.raiseRecoverable(n,"Redefinition of __proto__ property")},Ud.checkYieldAwaitInDefaultParams=function(){this.yieldPos&&(!this.awaitPos||this.yieldPos55295&&n<56320)return!0;if(Od(n,!0)){for(var r=i+1;md(n=this.input.charCodeAt(r),!0);)++r;if(92===n||n>55295&&n<56320)return!0;var s=this.input.slice(i,r);if(!ud.test(s))return!0}return!1},Kd.isAsyncFunction=function(){if(this.options.ecmaVersion<8||!this.isContextual("async"))return!1;Cd.lastIndex=this.pos;var t,e=Cd.exec(this.input),i=this.pos+e[0].length;return!(kd.test(this.input.slice(this.pos,i))||"function"!==this.input.slice(i,i+8)||i+8!==this.input.length&&(md(t=this.input.charCodeAt(i+8))||t>55295&&t<56320))},Kd.parseStatement=function(t,e,i){var n,r=this.type,s=this.startNode();switch(this.isLet(t)&&(r=vd._var,n="let"),r){case vd._break:case vd._continue:return this.parseBreakContinueStatement(s,r.keyword);case vd._debugger:return this.parseDebuggerStatement(s);case vd._do:return this.parseDoStatement(s);case vd._for:return this.parseForStatement(s);case vd._function:return t&&(this.strict||"if"!==t&&"label"!==t)&&this.options.ecmaVersion>=6&&this.unexpected(),this.parseFunctionStatement(s,!1,!t);case vd._class:return t&&this.unexpected(),this.parseClass(s,!0);case vd._if:return this.parseIfStatement(s);case vd._return:return this.parseReturnStatement(s);case vd._switch:return this.parseSwitchStatement(s);case vd._throw:return this.parseThrowStatement(s);case vd._try:return this.parseTryStatement(s);case vd._const:case vd._var:return n=n||this.value,t&&"var"!==n&&this.unexpected(),this.parseVarStatement(s,n);case vd._while:return this.parseWhileStatement(s);case vd._with:return this.parseWithStatement(s);case vd.braceL:return this.parseBlock(!0,s);case vd.semi:return this.parseEmptyStatement(s);case vd._export:case vd._import:if(this.options.ecmaVersion>10&&r===vd._import){Cd.lastIndex=this.pos;var o=Cd.exec(this.input),a=this.pos+o[0].length,l=this.input.charCodeAt(a);if(40===l||46===l)return this.parseExpressionStatement(s,this.parseExpression())}return this.options.allowImportExportEverywhere||(e||this.raise(this.start,"'import' and 'export' may only appear at the top level"),this.inModule||this.raise(this.start,"'import' and 'export' may appear only with 'sourceType: module'")),r===vd._import?this.parseImport(s):this.parseExport(s,i);default:if(this.isAsyncFunction())return t&&this.unexpected(),this.next(),this.parseFunctionStatement(s,!0,!t);var h=this.value,c=this.parseExpression();return r===vd.name&&"Identifier"===c.type&&this.eat(vd.colon)?this.parseLabeledStatement(s,h,c,t):this.parseExpressionStatement(s,c)}},Kd.parseBreakContinueStatement=function(t,e){var i="break"===e;this.next(),this.eat(vd.semi)||this.insertSemicolon()?t.label=null:this.type!==vd.name?this.unexpected():(t.label=this.parseIdent(),this.semicolon());for(var n=0;n=6?this.eat(vd.semi):this.semicolon(),this.finishNode(t,"DoWhileStatement")},Kd.parseForStatement=function(t){this.next();var e=this.options.ecmaVersion>=9&&this.canAwait&&this.eatContextual("await")?this.lastTokStart:-1;if(this.labels.push(Jd),this.enterScope(0),this.expect(vd.parenL),this.type===vd.semi)return e>-1&&this.unexpected(e),this.parseFor(t,null);var i=this.isLet();if(this.type===vd._var||this.type===vd._const||i){var n=this.startNode(),r=i?"let":this.value;return this.next(),this.parseVar(n,!0,r),this.finishNode(n,"VariableDeclaration"),(this.type===vd._in||this.options.ecmaVersion>=6&&this.isContextual("of"))&&1===n.declarations.length?(this.options.ecmaVersion>=9&&(this.type===vd._in?e>-1&&this.unexpected(e):t.await=e>-1),this.parseForIn(t,n)):(e>-1&&this.unexpected(e),this.parseFor(t,n))}var s=this.isContextual("let"),o=!1,a=new Hd,l=this.parseExpression(!(e>-1)||"await",a);return this.type===vd._in||(o=this.options.ecmaVersion>=6&&this.isContextual("of"))?(this.options.ecmaVersion>=9&&(this.type===vd._in?e>-1&&this.unexpected(e):t.await=e>-1),s&&o&&this.raise(l.start,"The left-hand side of a for-of loop may not start with 'let'."),this.toAssignable(l,!1,a),this.checkLValPattern(l),this.parseForIn(t,l)):(this.checkExpressionErrors(a,!0),e>-1&&this.unexpected(e),this.parseFor(t,l))},Kd.parseFunctionStatement=function(t,e,i){return this.next(),this.parseFunction(t,nf|(i?0:rf),!1,e)},Kd.parseIfStatement=function(t){return this.next(),t.test=this.parseParenExpression(),t.consequent=this.parseStatement("if"),t.alternate=this.eat(vd._else)?this.parseStatement("if"):null,this.finishNode(t,"IfStatement")},Kd.parseReturnStatement=function(t){return this.inFunction||this.options.allowReturnOutsideFunction||this.raise(this.start,"'return' outside of function"),this.next(),this.eat(vd.semi)||this.insertSemicolon()?t.argument=null:(t.argument=this.parseExpression(),this.semicolon()),this.finishNode(t,"ReturnStatement")},Kd.parseSwitchStatement=function(t){var e;this.next(),t.discriminant=this.parseParenExpression(),t.cases=[],this.expect(vd.braceL),this.labels.push(tf),this.enterScope(0);for(var i=!1;this.type!==vd.braceR;)if(this.type===vd._case||this.type===vd._default){var n=this.type===vd._case;e&&this.finishNode(e,"SwitchCase"),t.cases.push(e=this.startNode()),e.consequent=[],this.next(),n?e.test=this.parseExpression():(i&&this.raiseRecoverable(this.lastTokStart,"Multiple default clauses"),i=!0,e.test=null),this.expect(vd.colon)}else e||this.unexpected(),e.consequent.push(this.parseStatement(null));return this.exitScope(),e&&this.finishNode(e,"SwitchCase"),this.next(),this.labels.pop(),this.finishNode(t,"SwitchStatement")},Kd.parseThrowStatement=function(t){return this.next(),kd.test(this.input.slice(this.lastTokEnd,this.start))&&this.raise(this.lastTokEnd,"Illegal newline after throw"),t.argument=this.parseExpression(),this.semicolon(),this.finishNode(t,"ThrowStatement")};var ef=[];Kd.parseCatchClauseParam=function(){var t=this.parseBindingAtom(),e="Identifier"===t.type;return this.enterScope(e?32:0),this.checkLValPattern(t,e?4:2),this.expect(vd.parenR),t},Kd.parseTryStatement=function(t){if(this.next(),t.block=this.parseBlock(),t.handler=null,this.type===vd._catch){var e=this.startNode();this.next(),this.eat(vd.parenL)?e.param=this.parseCatchClauseParam():(this.options.ecmaVersion<10&&this.unexpected(),e.param=null,this.enterScope(0)),e.body=this.parseBlock(!1),this.exitScope(),t.handler=this.finishNode(e,"CatchClause")}return t.finalizer=this.eat(vd._finally)?this.parseBlock():null,t.handler||t.finalizer||this.raise(t.start,"Missing catch or finally clause"),this.finishNode(t,"TryStatement")},Kd.parseVarStatement=function(t,e,i){return this.next(),this.parseVar(t,!1,e,i),this.semicolon(),this.finishNode(t,"VariableDeclaration")},Kd.parseWhileStatement=function(t){return this.next(),t.test=this.parseParenExpression(),this.labels.push(Jd),t.body=this.parseStatement("while"),this.labels.pop(),this.finishNode(t,"WhileStatement")},Kd.parseWithStatement=function(t){return this.strict&&this.raise(this.start,"'with' in strict mode"),this.next(),t.object=this.parseParenExpression(),t.body=this.parseStatement("with"),this.finishNode(t,"WithStatement")},Kd.parseEmptyStatement=function(t){return this.next(),this.finishNode(t,"EmptyStatement")},Kd.parseLabeledStatement=function(t,e,i,n){for(var r=0,s=this.labels;r=0;a--){var l=this.labels[a];if(l.statementStart!==t.start)break;l.statementStart=this.start,l.kind=o}return this.labels.push({name:e,kind:o,statementStart:this.start}),t.body=this.parseStatement(n?-1===n.indexOf("label")?n+"label":n:"label"),this.labels.pop(),t.label=i,this.finishNode(t,"LabeledStatement")},Kd.parseExpressionStatement=function(t,e){return t.expression=e,this.semicolon(),this.finishNode(t,"ExpressionStatement")},Kd.parseBlock=function(t,e,i){for(void 0===t&&(t=!0),void 0===e&&(e=this.startNode()),e.body=[],this.expect(vd.braceL),t&&this.enterScope(0);this.type!==vd.braceR;){var n=this.parseStatement(null);e.body.push(n)}return i&&(this.strict=!1),this.next(),t&&this.exitScope(),this.finishNode(e,"BlockStatement")},Kd.parseFor=function(t,e){return t.init=e,this.expect(vd.semi),t.test=this.type===vd.semi?null:this.parseExpression(),this.expect(vd.semi),t.update=this.type===vd.parenR?null:this.parseExpression(),this.expect(vd.parenR),t.body=this.parseStatement("for"),this.exitScope(),this.labels.pop(),this.finishNode(t,"ForStatement")},Kd.parseForIn=function(t,e){var i=this.type===vd._in;return this.next(),"VariableDeclaration"===e.type&&null!=e.declarations[0].init&&(!i||this.options.ecmaVersion<8||this.strict||"var"!==e.kind||"Identifier"!==e.declarations[0].id.type)&&this.raise(e.start,(i?"for-in":"for-of")+" loop variable declaration may not have an initializer"),t.left=e,t.right=i?this.parseExpression():this.parseMaybeAssign(),this.expect(vd.parenR),t.body=this.parseStatement("for"),this.exitScope(),this.labels.pop(),this.finishNode(t,i?"ForInStatement":"ForOfStatement")},Kd.parseVar=function(t,e,i,n){for(t.declarations=[],t.kind=i;;){var r=this.startNode();if(this.parseVarId(r,i),this.eat(vd.eq)?r.init=this.parseMaybeAssign(e):n||"const"!==i||this.type===vd._in||this.options.ecmaVersion>=6&&this.isContextual("of")?n||"Identifier"===r.id.type||e&&(this.type===vd._in||this.isContextual("of"))?r.init=null:this.raise(this.lastTokEnd,"Complex binding patterns require an initialization value"):this.unexpected(),t.declarations.push(this.finishNode(r,"VariableDeclarator")),!this.eat(vd.comma))break}return t},Kd.parseVarId=function(t,e){t.id=this.parseBindingAtom(),this.checkLValPattern(t.id,"var"===e?1:2,!1)};var nf=1,rf=2;function sf(t,e){var i=e.key.name,n=t[i],r="true";return"MethodDefinition"!==e.type||"get"!==e.kind&&"set"!==e.kind||(r=(e.static?"s":"i")+e.kind),"iget"===n&&"iset"===r||"iset"===n&&"iget"===r||"sget"===n&&"sset"===r||"sset"===n&&"sget"===r?(t[i]="true",!1):!!n||(t[i]=r,!1)}function of(t,e){var i=t.computed,n=t.key;return!i&&("Identifier"===n.type&&n.name===e||"Literal"===n.type&&n.value===e)}Kd.parseFunction=function(t,e,i,n,r){this.initFunction(t),(this.options.ecmaVersion>=9||this.options.ecmaVersion>=6&&!n)&&(this.type===vd.star&&e&rf&&this.unexpected(),t.generator=this.eat(vd.star)),this.options.ecmaVersion>=8&&(t.async=!!n),e&nf&&(t.id=4&e&&this.type!==vd.name?null:this.parseIdent(),!t.id||e&rf||this.checkLValSimple(t.id,this.strict||t.generator||t.async?this.treatFunctionsAsVar?1:2:3));var s=this.yieldPos,o=this.awaitPos,a=this.awaitIdentPos;return this.yieldPos=0,this.awaitPos=0,this.awaitIdentPos=0,this.enterScope(Bd(t.async,t.generator)),e&nf||(t.id=this.type===vd.name?this.parseIdent():null),this.parseFunctionParams(t),this.parseFunctionBody(t,i,!1,r),this.yieldPos=s,this.awaitPos=o,this.awaitIdentPos=a,this.finishNode(t,e&nf?"FunctionDeclaration":"FunctionExpression")},Kd.parseFunctionParams=function(t){this.expect(vd.parenL),t.params=this.parseBindingList(vd.parenR,!1,this.options.ecmaVersion>=8),this.checkYieldAwaitInDefaultParams()},Kd.parseClass=function(t,e){this.next();var i=this.strict;this.strict=!0,this.parseClassId(t,e),this.parseClassSuper(t);var n=this.enterClassBody(),r=this.startNode(),s=!1;for(r.body=[],this.expect(vd.braceL);this.type!==vd.braceR;){var o=this.parseClassElement(null!==t.superClass);o&&(r.body.push(o),"MethodDefinition"===o.type&&"constructor"===o.kind?(s&&this.raiseRecoverable(o.start,"Duplicate constructor in the same class"),s=!0):o.key&&"PrivateIdentifier"===o.key.type&&sf(n,o)&&this.raiseRecoverable(o.key.start,"Identifier '#"+o.key.name+"' has already been declared"))}return this.strict=i,this.next(),t.body=this.finishNode(r,"ClassBody"),this.exitClassBody(),this.finishNode(t,e?"ClassDeclaration":"ClassExpression")},Kd.parseClassElement=function(t){if(this.eat(vd.semi))return null;var e=this.options.ecmaVersion,i=this.startNode(),n="",r=!1,s=!1,o="method",a=!1;if(this.eatContextual("static")){if(e>=13&&this.eat(vd.braceL))return this.parseClassStaticBlock(i),i;this.isClassElementNameStart()||this.type===vd.star?a=!0:n="static"}if(i.static=a,!n&&e>=8&&this.eatContextual("async")&&(!this.isClassElementNameStart()&&this.type!==vd.star||this.canInsertSemicolon()?n="async":s=!0),!n&&(e>=9||!s)&&this.eat(vd.star)&&(r=!0),!n&&!s&&!r){var l=this.value;(this.eatContextual("get")||this.eatContextual("set"))&&(this.isClassElementNameStart()?o=l:n=l)}if(n?(i.computed=!1,i.key=this.startNodeAt(this.lastTokStart,this.lastTokStartLoc),i.key.name=n,this.finishNode(i.key,"Identifier")):this.parseClassElementName(i),e<13||this.type===vd.parenL||"method"!==o||r||s){var h=!i.static&&of(i,"constructor"),c=h&&t;h&&"method"!==o&&this.raise(i.key.start,"Constructor can't have get/set modifier"),i.kind=h?"constructor":o,this.parseClassMethod(i,r,s,c)}else this.parseClassField(i);return i},Kd.isClassElementNameStart=function(){return this.type===vd.name||this.type===vd.privateId||this.type===vd.num||this.type===vd.string||this.type===vd.bracketL||this.type.keyword},Kd.parseClassElementName=function(t){this.type===vd.privateId?("constructor"===this.value&&this.raise(this.start,"Classes can't have an element named '#constructor'"),t.computed=!1,t.key=this.parsePrivateIdent()):this.parsePropertyName(t)},Kd.parseClassMethod=function(t,e,i,n){var r=t.key;"constructor"===t.kind?(e&&this.raise(r.start,"Constructor can't be a generator"),i&&this.raise(r.start,"Constructor can't be an async method")):t.static&&of(t,"prototype")&&this.raise(r.start,"Classes may not have a static property named prototype");var s=t.value=this.parseMethod(e,i,n);return"get"===t.kind&&0!==s.params.length&&this.raiseRecoverable(s.start,"getter should have no params"),"set"===t.kind&&1!==s.params.length&&this.raiseRecoverable(s.start,"setter should have exactly one param"),"set"===t.kind&&"RestElement"===s.params[0].type&&this.raiseRecoverable(s.params[0].start,"Setter cannot use rest params"),this.finishNode(t,"MethodDefinition")},Kd.parseClassField=function(t){if(of(t,"constructor")?this.raise(t.key.start,"Classes can't have a field named 'constructor'"):t.static&&of(t,"prototype")&&this.raise(t.key.start,"Classes can't have a static field named 'prototype'"),this.eat(vd.eq)){var e=this.currentThisScope(),i=e.inClassFieldInit;e.inClassFieldInit=!0,t.value=this.parseMaybeAssign(),e.inClassFieldInit=i}else t.value=null;return this.semicolon(),this.finishNode(t,"PropertyDefinition")},Kd.parseClassStaticBlock=function(t){t.body=[];var e=this.labels;for(this.labels=[],this.enterScope(320);this.type!==vd.braceR;){var i=this.parseStatement(null);t.body.push(i)}return this.next(),this.exitScope(),this.labels=e,this.finishNode(t,"StaticBlock")},Kd.parseClassId=function(t,e){this.type===vd.name?(t.id=this.parseIdent(),e&&this.checkLValSimple(t.id,2,!1)):(!0===e&&this.unexpected(),t.id=null)},Kd.parseClassSuper=function(t){t.superClass=this.eat(vd._extends)?this.parseExprSubscripts(null,!1):null},Kd.enterClassBody=function(){var t={declared:Object.create(null),used:[]};return this.privateNameStack.push(t),t.declared},Kd.exitClassBody=function(){var t=this.privateNameStack.pop(),e=t.declared,i=t.used;if(this.options.checkPrivateFields)for(var n=this.privateNameStack.length,r=0===n?null:this.privateNameStack[n-1],s=0;s=11&&(this.eatContextual("as")?(t.exported=this.parseModuleExportName(),this.checkExport(e,t.exported,this.lastTokStart)):t.exported=null),this.expectContextual("from"),this.type!==vd.string&&this.unexpected(),t.source=this.parseExprAtom(),this.semicolon(),this.finishNode(t,"ExportAllDeclaration")},Kd.parseExport=function(t,e){if(this.next(),this.eat(vd.star))return this.parseExportAllDeclaration(t,e);if(this.eat(vd._default))return this.checkExport(e,"default",this.lastTokStart),t.declaration=this.parseExportDefaultDeclaration(),this.finishNode(t,"ExportDefaultDeclaration");if(this.shouldParseExportStatement())t.declaration=this.parseExportDeclaration(t),"VariableDeclaration"===t.declaration.type?this.checkVariableExport(e,t.declaration.declarations):this.checkExport(e,t.declaration.id,t.declaration.id.start),t.specifiers=[],t.source=null;else{if(t.declaration=null,t.specifiers=this.parseExportSpecifiers(e),this.eatContextual("from"))this.type!==vd.string&&this.unexpected(),t.source=this.parseExprAtom();else{for(var i=0,n=t.specifiers;i=13&&this.type===vd.string){var t=this.parseLiteral(this.value);return Wd.test(t.value)&&this.raise(t.start,"An export name cannot include a lone surrogate."),t}return this.parseIdent(!0)},Kd.adaptDirectivePrologue=function(t){for(var e=0;e=5&&"ExpressionStatement"===t.type&&"Literal"===t.expression.type&&"string"==typeof t.expression.value&&('"'===this.input[t.start]||"'"===this.input[t.start])};var af=Ld.prototype;af.toAssignable=function(t,e,i){if(this.options.ecmaVersion>=6&&t)switch(t.type){case"Identifier":this.inAsync&&"await"===t.name&&this.raise(t.start,"Cannot use 'await' as identifier inside an async function");break;case"ObjectPattern":case"ArrayPattern":case"AssignmentPattern":case"RestElement":break;case"ObjectExpression":t.type="ObjectPattern",i&&this.checkPatternErrors(i,!0);for(var n=0,r=t.properties;n=8&&!a&&"async"===l.name&&!this.canInsertSemicolon()&&this.eat(vd._function))return this.overrideContext(hf.f_expr),this.parseFunction(this.startNodeAt(s,o),0,!1,!0,e);if(r&&!this.canInsertSemicolon()){if(this.eat(vd.arrow))return this.parseArrowExpression(this.startNodeAt(s,o),[l],!1,e);if(this.options.ecmaVersion>=8&&"async"===l.name&&this.type===vd.name&&!a&&(!this.potentialArrowInForAwait||"of"!==this.value||this.containsEsc))return l=this.parseIdent(!1),!this.canInsertSemicolon()&&this.eat(vd.arrow)||this.unexpected(),this.parseArrowExpression(this.startNodeAt(s,o),[l],!0,e)}return l;case vd.regexp:var h=this.value;return(n=this.parseLiteral(h.value)).regex={pattern:h.pattern,flags:h.flags},n;case vd.num:case vd.string:return this.parseLiteral(this.value);case vd._null:case vd._true:case vd._false:return(n=this.startNode()).value=this.type===vd._null?null:this.type===vd._true,n.raw=this.type.keyword,this.next(),this.finishNode(n,"Literal");case vd.parenL:var c=this.start,u=this.parseParenAndDistinguishExpression(r,e);return t&&(t.parenthesizedAssign<0&&!this.isSimpleAssignTarget(u)&&(t.parenthesizedAssign=c),t.parenthesizedBind<0&&(t.parenthesizedBind=c)),u;case vd.bracketL:return n=this.startNode(),this.next(),n.elements=this.parseExprList(vd.bracketR,!0,!0,t),this.finishNode(n,"ArrayExpression");case vd.braceL:return this.overrideContext(hf.b_expr),this.parseObj(!1,t);case vd._function:return n=this.startNode(),this.next(),this.parseFunction(n,0);case vd._class:return this.parseClass(this.startNode(),!1);case vd._new:return this.parseNew();case vd.backQuote:return this.parseTemplate();case vd._import:return this.options.ecmaVersion>=11?this.parseExprImport(i):this.unexpected();default:return this.parseExprAtomDefault()}},uf.parseExprAtomDefault=function(){this.unexpected()},uf.parseExprImport=function(t){var e=this.startNode();if(this.containsEsc&&this.raiseRecoverable(this.start,"Escape sequence in keyword import"),this.next(),this.type===vd.parenL&&!t)return this.parseDynamicImport(e);if(this.type===vd.dot){var i=this.startNodeAt(e.start,e.loc&&e.loc.start);return i.name="import",e.meta=this.finishNode(i,"Identifier"),this.parseImportMeta(e)}this.unexpected()},uf.parseDynamicImport=function(t){if(this.next(),t.source=this.parseMaybeAssign(),!this.eat(vd.parenR)){var e=this.start;this.eat(vd.comma)&&this.eat(vd.parenR)?this.raiseRecoverable(e,"Trailing comma is not allowed in import()"):this.unexpected(e)}return this.finishNode(t,"ImportExpression")},uf.parseImportMeta=function(t){this.next();var e=this.containsEsc;return t.property=this.parseIdent(!0),"meta"!==t.property.name&&this.raiseRecoverable(t.property.start,"The only valid meta property for import is 'import.meta'"),e&&this.raiseRecoverable(t.start,"'import.meta' must not contain escaped characters"),"module"===this.options.sourceType||this.options.allowImportExportEverywhere||this.raiseRecoverable(t.start,"Cannot use 'import.meta' outside a module"),this.finishNode(t,"MetaProperty")},uf.parseLiteral=function(t){var e=this.startNode();return e.value=t,e.raw=this.input.slice(this.start,this.end),110===e.raw.charCodeAt(e.raw.length-1)&&(e.bigint=e.raw.slice(0,-1).replace(/_/g,"")),this.next(),this.finishNode(e,"Literal")},uf.parseParenExpression=function(){this.expect(vd.parenL);var t=this.parseExpression();return this.expect(vd.parenR),t},uf.shouldParseArrow=function(t){return!this.canInsertSemicolon()},uf.parseParenAndDistinguishExpression=function(t,e){var i,n=this.start,r=this.startLoc,s=this.options.ecmaVersion>=8;if(this.options.ecmaVersion>=6){this.next();var o,a=this.start,l=this.startLoc,h=[],c=!0,u=!1,p=new Hd,d=this.yieldPos,f=this.awaitPos;for(this.yieldPos=0,this.awaitPos=0;this.type!==vd.parenR;){if(c?c=!1:this.expect(vd.comma),s&&this.afterTrailingComma(vd.parenR,!0)){u=!0;break}if(this.type===vd.ellipsis){o=this.start,h.push(this.parseParenItem(this.parseRestBinding())),this.type===vd.comma&&this.raiseRecoverable(this.start,"Comma is not permitted after the rest element");break}h.push(this.parseMaybeAssign(!1,p,this.parseParenItem))}var O=this.lastTokEnd,m=this.lastTokEndLoc;if(this.expect(vd.parenR),t&&this.shouldParseArrow(h)&&this.eat(vd.arrow))return this.checkPatternErrors(p,!1),this.checkYieldAwaitInDefaultParams(),this.yieldPos=d,this.awaitPos=f,this.parseParenArrowList(n,r,h,e);h.length&&!u||this.unexpected(this.lastTokStart),o&&this.unexpected(o),this.checkExpressionErrors(p,!0),this.yieldPos=d||this.yieldPos,this.awaitPos=f||this.awaitPos,h.length>1?((i=this.startNodeAt(a,l)).expressions=h,this.finishNodeAt(i,"SequenceExpression",O,m)):i=h[0]}else i=this.parseParenExpression();if(this.options.preserveParens){var g=this.startNodeAt(n,r);return g.expression=i,this.finishNode(g,"ParenthesizedExpression")}return i},uf.parseParenItem=function(t){return t},uf.parseParenArrowList=function(t,e,i,n){return this.parseArrowExpression(this.startNodeAt(t,e),i,!1,n)};var df=[];uf.parseNew=function(){this.containsEsc&&this.raiseRecoverable(this.start,"Escape sequence in keyword new");var t=this.startNode();if(this.next(),this.options.ecmaVersion>=6&&this.type===vd.dot){var e=this.startNodeAt(t.start,t.loc&&t.loc.start);e.name="new",t.meta=this.finishNode(e,"Identifier"),this.next();var i=this.containsEsc;return t.property=this.parseIdent(!0),"target"!==t.property.name&&this.raiseRecoverable(t.property.start,"The only valid meta property for new is 'new.target'"),i&&this.raiseRecoverable(t.start,"'new.target' must not contain escaped characters"),this.allowNewDotTarget||this.raiseRecoverable(t.start,"'new.target' can only be used in functions and class static block"),this.finishNode(t,"MetaProperty")}var n=this.start,r=this.startLoc;return t.callee=this.parseSubscripts(this.parseExprAtom(null,!1,!0),n,r,!0,!1),this.eat(vd.parenL)?t.arguments=this.parseExprList(vd.parenR,this.options.ecmaVersion>=8,!1):t.arguments=df,this.finishNode(t,"NewExpression")},uf.parseTemplateElement=function(t){var e=t.isTagged,i=this.startNode();return this.type===vd.invalidTemplate?(e||this.raiseRecoverable(this.start,"Bad escape sequence in untagged template literal"),i.value={raw:this.value,cooked:null}):i.value={raw:this.input.slice(this.start,this.end).replace(/\r\n?/g,"\n"),cooked:this.value},this.next(),i.tail=this.type===vd.backQuote,this.finishNode(i,"TemplateElement")},uf.parseTemplate=function(t){void 0===t&&(t={});var e=t.isTagged;void 0===e&&(e=!1);var i=this.startNode();this.next(),i.expressions=[];var n=this.parseTemplateElement({isTagged:e});for(i.quasis=[n];!n.tail;)this.type===vd.eof&&this.raise(this.pos,"Unterminated template literal"),this.expect(vd.dollarBraceL),i.expressions.push(this.parseExpression()),this.expect(vd.braceR),i.quasis.push(n=this.parseTemplateElement({isTagged:e}));return this.next(),this.finishNode(i,"TemplateLiteral")},uf.isAsyncProp=function(t){return!t.computed&&"Identifier"===t.key.type&&"async"===t.key.name&&(this.type===vd.name||this.type===vd.num||this.type===vd.string||this.type===vd.bracketL||this.type.keyword||this.options.ecmaVersion>=9&&this.type===vd.star)&&!kd.test(this.input.slice(this.lastTokEnd,this.start))},uf.parseObj=function(t,e){var i=this.startNode(),n=!0,r={};for(i.properties=[],this.next();!this.eat(vd.braceR);){if(n)n=!1;else if(this.expect(vd.comma),this.options.ecmaVersion>=5&&this.afterTrailingComma(vd.braceR))break;var s=this.parseProperty(t,e);t||this.checkPropClash(s,r,e),i.properties.push(s)}return this.finishNode(i,t?"ObjectPattern":"ObjectExpression")},uf.parseProperty=function(t,e){var i,n,r,s,o=this.startNode();if(this.options.ecmaVersion>=9&&this.eat(vd.ellipsis))return t?(o.argument=this.parseIdent(!1),this.type===vd.comma&&this.raiseRecoverable(this.start,"Comma is not permitted after the rest element"),this.finishNode(o,"RestElement")):(o.argument=this.parseMaybeAssign(!1,e),this.type===vd.comma&&e&&e.trailingComma<0&&(e.trailingComma=this.start),this.finishNode(o,"SpreadElement"));this.options.ecmaVersion>=6&&(o.method=!1,o.shorthand=!1,(t||e)&&(r=this.start,s=this.startLoc),t||(i=this.eat(vd.star)));var a=this.containsEsc;return this.parsePropertyName(o),!t&&!a&&this.options.ecmaVersion>=8&&!i&&this.isAsyncProp(o)?(n=!0,i=this.options.ecmaVersion>=9&&this.eat(vd.star),this.parsePropertyName(o)):n=!1,this.parsePropertyValue(o,t,i,n,r,s,e,a),this.finishNode(o,"Property")},uf.parseGetterSetter=function(t){t.kind=t.key.name,this.parsePropertyName(t),t.value=this.parseMethod(!1);var e="get"===t.kind?0:1;if(t.value.params.length!==e){var i=t.value.start;"get"===t.kind?this.raiseRecoverable(i,"getter should have no params"):this.raiseRecoverable(i,"setter should have exactly one param")}else"set"===t.kind&&"RestElement"===t.value.params[0].type&&this.raiseRecoverable(t.value.params[0].start,"Setter cannot use rest params")},uf.parsePropertyValue=function(t,e,i,n,r,s,o,a){(i||n)&&this.type===vd.colon&&this.unexpected(),this.eat(vd.colon)?(t.value=e?this.parseMaybeDefault(this.start,this.startLoc):this.parseMaybeAssign(!1,o),t.kind="init"):this.options.ecmaVersion>=6&&this.type===vd.parenL?(e&&this.unexpected(),t.kind="init",t.method=!0,t.value=this.parseMethod(i,n)):e||a||!(this.options.ecmaVersion>=5)||t.computed||"Identifier"!==t.key.type||"get"!==t.key.name&&"set"!==t.key.name||this.type===vd.comma||this.type===vd.braceR||this.type===vd.eq?this.options.ecmaVersion>=6&&!t.computed&&"Identifier"===t.key.type?((i||n)&&this.unexpected(),this.checkUnreserved(t.key),"await"!==t.key.name||this.awaitIdentPos||(this.awaitIdentPos=r),t.kind="init",e?t.value=this.parseMaybeDefault(r,s,this.copyNode(t.key)):this.type===vd.eq&&o?(o.shorthandAssign<0&&(o.shorthandAssign=this.start),t.value=this.parseMaybeDefault(r,s,this.copyNode(t.key))):t.value=this.copyNode(t.key),t.shorthand=!0):this.unexpected():((i||n)&&this.unexpected(),this.parseGetterSetter(t))},uf.parsePropertyName=function(t){if(this.options.ecmaVersion>=6){if(this.eat(vd.bracketL))return t.computed=!0,t.key=this.parseMaybeAssign(),this.expect(vd.bracketR),t.key;t.computed=!1}return t.key=this.type===vd.num||this.type===vd.string?this.parseExprAtom():this.parseIdent("never"!==this.options.allowReserved)},uf.initFunction=function(t){t.id=null,this.options.ecmaVersion>=6&&(t.generator=t.expression=!1),this.options.ecmaVersion>=8&&(t.async=!1)},uf.parseMethod=function(t,e,i){var n=this.startNode(),r=this.yieldPos,s=this.awaitPos,o=this.awaitIdentPos;return this.initFunction(n),this.options.ecmaVersion>=6&&(n.generator=t),this.options.ecmaVersion>=8&&(n.async=!!e),this.yieldPos=0,this.awaitPos=0,this.awaitIdentPos=0,this.enterScope(64|Bd(e,n.generator)|(i?128:0)),this.expect(vd.parenL),n.params=this.parseBindingList(vd.parenR,!1,this.options.ecmaVersion>=8),this.checkYieldAwaitInDefaultParams(),this.parseFunctionBody(n,!1,!0,!1),this.yieldPos=r,this.awaitPos=s,this.awaitIdentPos=o,this.finishNode(n,"FunctionExpression")},uf.parseArrowExpression=function(t,e,i,n){var r=this.yieldPos,s=this.awaitPos,o=this.awaitIdentPos;return this.enterScope(16|Bd(i,!1)),this.initFunction(t),this.options.ecmaVersion>=8&&(t.async=!!i),this.yieldPos=0,this.awaitPos=0,this.awaitIdentPos=0,t.params=this.toAssignableList(e,!0),this.parseFunctionBody(t,!0,!1,n),this.yieldPos=r,this.awaitPos=s,this.awaitIdentPos=o,this.finishNode(t,"ArrowFunctionExpression")},uf.parseFunctionBody=function(t,e,i,n){var r=e&&this.type!==vd.braceL,s=this.strict,o=!1;if(r)t.body=this.parseMaybeAssign(n),t.expression=!0,this.checkParams(t,!1);else{var a=this.options.ecmaVersion>=7&&!this.isSimpleParamList(t.params);s&&!a||(o=this.strictDirective(this.end))&&a&&this.raiseRecoverable(t.start,"Illegal 'use strict' directive in function with non-simple parameter list");var l=this.labels;this.labels=[],o&&(this.strict=!0),this.checkParams(t,!s&&!o&&!e&&!i&&this.isSimpleParamList(t.params)),this.strict&&t.id&&this.checkLValSimple(t.id,5),t.body=this.parseBlock(!1,void 0,o&&!s),t.expression=!1,this.adaptDirectivePrologue(t.body.body),this.labels=l}this.exitScope()},uf.isSimpleParamList=function(t){for(var e=0,i=t;e-1||r.functions.indexOf(t)>-1||r.var.indexOf(t)>-1,r.lexical.push(t),this.inModule&&1&r.flags&&delete this.undefinedExports[t]}else if(4===e){this.currentScope().lexical.push(t)}else if(3===e){var s=this.currentScope();n=this.treatFunctionsAsVar?s.lexical.indexOf(t)>-1:s.lexical.indexOf(t)>-1||s.var.indexOf(t)>-1,s.functions.push(t)}else for(var o=this.scopeStack.length-1;o>=0;--o){var a=this.scopeStack[o];if(a.lexical.indexOf(t)>-1&&!(32&a.flags&&a.lexical[0]===t)||!this.treatFunctionsAsVarInScope(a)&&a.functions.indexOf(t)>-1){n=!0;break}if(a.var.push(t),this.inModule&&1&a.flags&&delete this.undefinedExports[t],259&a.flags)break}n&&this.raiseRecoverable(i,"Identifier '"+t+"' has already been declared")},Of.checkLocalExport=function(t){-1===this.scopeStack[0].lexical.indexOf(t.name)&&-1===this.scopeStack[0].var.indexOf(t.name)&&(this.undefinedExports[t.name]=t)},Of.currentScope=function(){return this.scopeStack[this.scopeStack.length-1]},Of.currentVarScope=function(){for(var t=this.scopeStack.length-1;;t--){var e=this.scopeStack[t];if(259&e.flags)return e}},Of.currentThisScope=function(){for(var t=this.scopeStack.length-1;;t--){var e=this.scopeStack[t];if(259&e.flags&&!(16&e.flags))return e}};var gf=function(t,e,i){this.type="",this.start=e,this.end=0,t.options.locations&&(this.loc=new Id(t,i)),t.options.directSourceFile&&(this.sourceFile=t.options.directSourceFile),t.options.ranges&&(this.range=[e,0])},yf=Ld.prototype;function xf(t,e,i,n){return t.type=e,t.end=i,this.options.locations&&(t.loc.end=n),this.options.ranges&&(t.range[1]=i),t}yf.startNode=function(){return new gf(this,this.start,this.startLoc)},yf.startNodeAt=function(t,e){return new gf(this,t,e)},yf.finishNode=function(t,e){return xf.call(this,t,e,this.lastTokEnd,this.lastTokEndLoc)},yf.finishNodeAt=function(t,e,i,n){return xf.call(this,t,e,i,n)},yf.copyNode=function(t){var e=new gf(this,t.start,this.startLoc);for(var i in t)e[i]=t[i];return e};var Sf="ASCII ASCII_Hex_Digit AHex Alphabetic Alpha Any Assigned Bidi_Control Bidi_C Bidi_Mirrored Bidi_M Case_Ignorable CI Cased Changes_When_Casefolded CWCF Changes_When_Casemapped CWCM Changes_When_Lowercased CWL Changes_When_NFKC_Casefolded CWKCF Changes_When_Titlecased CWT Changes_When_Uppercased CWU Dash Default_Ignorable_Code_Point DI Deprecated Dep Diacritic Dia Emoji Emoji_Component Emoji_Modifier Emoji_Modifier_Base Emoji_Presentation Extender Ext Grapheme_Base Gr_Base Grapheme_Extend Gr_Ext Hex_Digit Hex IDS_Binary_Operator IDSB IDS_Trinary_Operator IDST ID_Continue IDC ID_Start IDS Ideographic Ideo Join_Control Join_C Logical_Order_Exception LOE Lowercase Lower Math Noncharacter_Code_Point NChar Pattern_Syntax Pat_Syn Pattern_White_Space Pat_WS Quotation_Mark QMark Radical Regional_Indicator RI Sentence_Terminal STerm Soft_Dotted SD Terminal_Punctuation Term Unified_Ideograph UIdeo Uppercase Upper Variation_Selector VS White_Space space XID_Continue XIDC XID_Start XIDS",wf=Sf+" Extended_Pictographic",bf=wf+" EBase EComp EMod EPres ExtPict",vf={9:Sf,10:wf,11:wf,12:bf,13:bf,14:bf},kf={9:"",10:"",11:"",12:"",13:"",14:"Basic_Emoji Emoji_Keycap_Sequence RGI_Emoji_Modifier_Sequence RGI_Emoji_Flag_Sequence RGI_Emoji_Tag_Sequence RGI_Emoji_ZWJ_Sequence RGI_Emoji"},Qf="Cased_Letter LC Close_Punctuation Pe Connector_Punctuation Pc Control Cc cntrl Currency_Symbol Sc Dash_Punctuation Pd Decimal_Number Nd digit Enclosing_Mark Me Final_Punctuation Pf Format Cf Initial_Punctuation Pi Letter L Letter_Number Nl Line_Separator Zl Lowercase_Letter Ll Mark M Combining_Mark Math_Symbol Sm Modifier_Letter Lm Modifier_Symbol Sk Nonspacing_Mark Mn Number N Open_Punctuation Ps Other C Other_Letter Lo Other_Number No Other_Punctuation Po Other_Symbol So Paragraph_Separator Zp Private_Use Co Punctuation P punct Separator Z Space_Separator Zs Spacing_Mark Mc Surrogate Cs Symbol S Titlecase_Letter Lt Unassigned Cn Uppercase_Letter Lu",Pf="Adlam Adlm Ahom Anatolian_Hieroglyphs Hluw Arabic Arab Armenian Armn Avestan Avst Balinese Bali Bamum Bamu Bassa_Vah Bass Batak Batk Bengali Beng Bhaiksuki Bhks Bopomofo Bopo Brahmi Brah Braille Brai Buginese Bugi Buhid Buhd Canadian_Aboriginal Cans Carian Cari Caucasian_Albanian Aghb Chakma Cakm Cham Cham Cherokee Cher Common Zyyy Coptic Copt Qaac Cuneiform Xsux Cypriot Cprt Cyrillic Cyrl Deseret Dsrt Devanagari Deva Duployan Dupl Egyptian_Hieroglyphs Egyp Elbasan Elba Ethiopic Ethi Georgian Geor Glagolitic Glag Gothic Goth Grantha Gran Greek Grek Gujarati Gujr Gurmukhi Guru Han Hani Hangul Hang Hanunoo Hano Hatran Hatr Hebrew Hebr Hiragana Hira Imperial_Aramaic Armi Inherited Zinh Qaai Inscriptional_Pahlavi Phli Inscriptional_Parthian Prti Javanese Java Kaithi Kthi Kannada Knda Katakana Kana Kayah_Li Kali Kharoshthi Khar Khmer Khmr Khojki Khoj Khudawadi Sind Lao Laoo Latin Latn Lepcha Lepc Limbu Limb Linear_A Lina Linear_B Linb Lisu Lisu Lycian Lyci Lydian Lydi Mahajani Mahj Malayalam Mlym Mandaic Mand Manichaean Mani Marchen Marc Masaram_Gondi Gonm Meetei_Mayek Mtei Mende_Kikakui Mend Meroitic_Cursive Merc Meroitic_Hieroglyphs Mero Miao Plrd Modi Mongolian Mong Mro Mroo Multani Mult Myanmar Mymr Nabataean Nbat New_Tai_Lue Talu Newa Newa Nko Nkoo Nushu Nshu Ogham Ogam Ol_Chiki Olck Old_Hungarian Hung Old_Italic Ital Old_North_Arabian Narb Old_Permic Perm Old_Persian Xpeo Old_South_Arabian Sarb Old_Turkic Orkh Oriya Orya Osage Osge Osmanya Osma Pahawh_Hmong Hmng Palmyrene Palm Pau_Cin_Hau Pauc Phags_Pa Phag Phoenician Phnx Psalter_Pahlavi Phlp Rejang Rjng Runic Runr Samaritan Samr Saurashtra Saur Sharada Shrd Shavian Shaw Siddham Sidd SignWriting Sgnw Sinhala Sinh Sora_Sompeng Sora Soyombo Soyo Sundanese Sund Syloti_Nagri Sylo Syriac Syrc Tagalog Tglg Tagbanwa Tagb Tai_Le Tale Tai_Tham Lana Tai_Viet Tavt Takri Takr Tamil Taml Tangut Tang Telugu Telu Thaana Thaa Thai Thai Tibetan Tibt Tifinagh Tfng Tirhuta Tirh Ugaritic Ugar Vai Vaii Warang_Citi Wara Yi Yiii Zanabazar_Square Zanb",$f=Pf+" Dogra Dogr Gunjala_Gondi Gong Hanifi_Rohingya Rohg Makasar Maka Medefaidrin Medf Old_Sogdian Sogo Sogdian Sogd",Zf=$f+" Elymaic Elym Nandinagari Nand Nyiakeng_Puachue_Hmong Hmnp Wancho Wcho",Cf=Zf+" Chorasmian Chrs Diak Dives_Akuru Khitan_Small_Script Kits Yezi Yezidi",Tf=Cf+" Cypro_Minoan Cpmn Old_Uyghur Ougr Tangsa Tnsa Toto Vithkuqi Vith",Af={9:Pf,10:$f,11:Zf,12:Cf,13:Tf,14:Tf+" Hrkt Katakana_Or_Hiragana Kawi Nag_Mundari Nagm Unknown Zzzz"},_f={};function Ef(t){var e=_f[t]={binary:Vd(vf[t]+" "+Qf),binaryOfStrings:Vd(kf[t]),nonBinary:{General_Category:Vd(Qf),Script:Vd(Af[t])}};e.nonBinary.Script_Extensions=e.nonBinary.Script,e.nonBinary.gc=e.nonBinary.General_Category,e.nonBinary.sc=e.nonBinary.Script,e.nonBinary.scx=e.nonBinary.Script_Extensions}for(var Xf=0,Rf=[9,10,11,12,13,14];Xf=6?"uy":"")+(t.options.ecmaVersion>=9?"s":"")+(t.options.ecmaVersion>=13?"d":"")+(t.options.ecmaVersion>=15?"v":""),this.unicodeProperties=_f[t.options.ecmaVersion>=14?14:t.options.ecmaVersion],this.source="",this.flags="",this.start=0,this.switchU=!1,this.switchV=!1,this.switchN=!1,this.pos=0,this.lastIntValue=0,this.lastStringValue="",this.lastAssertionIsQuantifiable=!1,this.numCapturingParens=0,this.maxBackReference=0,this.groupNames=[],this.backReferenceNames=[]};function Wf(t){return 36===t||t>=40&&t<=43||46===t||63===t||t>=91&&t<=94||t>=123&&t<=125}function qf(t){return t>=65&&t<=90||t>=97&&t<=122}Yf.prototype.reset=function(t,e,i){var n=-1!==i.indexOf("v"),r=-1!==i.indexOf("u");this.start=0|t,this.source=e+"",this.flags=i,n&&this.parser.options.ecmaVersion>=15?(this.switchU=!0,this.switchV=!0,this.switchN=!0):(this.switchU=r&&this.parser.options.ecmaVersion>=6,this.switchV=!1,this.switchN=r&&this.parser.options.ecmaVersion>=9)},Yf.prototype.raise=function(t){this.parser.raiseRecoverable(this.start,"Invalid regular expression: /"+this.source+"/: "+t)},Yf.prototype.at=function(t,e){void 0===e&&(e=!1);var i=this.source,n=i.length;if(t>=n)return-1;var r=i.charCodeAt(t);if(!e&&!this.switchU||r<=55295||r>=57344||t+1>=n)return r;var s=i.charCodeAt(t+1);return s>=56320&&s<=57343?(r<<10)+s-56613888:r},Yf.prototype.nextIndex=function(t,e){void 0===e&&(e=!1);var i=this.source,n=i.length;if(t>=n)return n;var r,s=i.charCodeAt(t);return!e&&!this.switchU||s<=55295||s>=57344||t+1>=n||(r=i.charCodeAt(t+1))<56320||r>57343?t+1:t+2},Yf.prototype.current=function(t){return void 0===t&&(t=!1),this.at(this.pos,t)},Yf.prototype.lookahead=function(t){return void 0===t&&(t=!1),this.at(this.nextIndex(this.pos,t),t)},Yf.prototype.advance=function(t){void 0===t&&(t=!1),this.pos=this.nextIndex(this.pos,t)},Yf.prototype.eat=function(t,e){return void 0===e&&(e=!1),this.current(e)===t&&(this.advance(e),!0)},Yf.prototype.eatChars=function(t,e){void 0===e&&(e=!1);for(var i=this.pos,n=0,r=t;n-1&&this.raise(t.start,"Duplicate regular expression flag"),"u"===o&&(n=!0),"v"===o&&(r=!0)}this.options.ecmaVersion>=15&&n&&r&&this.raise(t.start,"Invalid regular expression flag")},Vf.validateRegExpPattern=function(t){this.regexp_pattern(t),!t.switchN&&this.options.ecmaVersion>=9&&t.groupNames.length>0&&(t.switchN=!0,this.regexp_pattern(t))},Vf.regexp_pattern=function(t){t.pos=0,t.lastIntValue=0,t.lastStringValue="",t.lastAssertionIsQuantifiable=!1,t.numCapturingParens=0,t.maxBackReference=0,t.groupNames.length=0,t.backReferenceNames.length=0,this.regexp_disjunction(t),t.pos!==t.source.length&&(t.eat(41)&&t.raise("Unmatched ')'"),(t.eat(93)||t.eat(125))&&t.raise("Lone quantifier brackets")),t.maxBackReference>t.numCapturingParens&&t.raise("Invalid escape");for(var e=0,i=t.backReferenceNames;e=9&&(i=t.eat(60)),t.eat(61)||t.eat(33))return this.regexp_disjunction(t),t.eat(41)||t.raise("Unterminated group"),t.lastAssertionIsQuantifiable=!i,!0}return t.pos=e,!1},Vf.regexp_eatQuantifier=function(t,e){return void 0===e&&(e=!1),!!this.regexp_eatQuantifierPrefix(t,e)&&(t.eat(63),!0)},Vf.regexp_eatQuantifierPrefix=function(t,e){return t.eat(42)||t.eat(43)||t.eat(63)||this.regexp_eatBracedQuantifier(t,e)},Vf.regexp_eatBracedQuantifier=function(t,e){var i=t.pos;if(t.eat(123)){var n=0,r=-1;if(this.regexp_eatDecimalDigits(t)&&(n=t.lastIntValue,t.eat(44)&&this.regexp_eatDecimalDigits(t)&&(r=t.lastIntValue),t.eat(125)))return-1!==r&&r=9?this.regexp_groupSpecifier(t):63===t.current()&&t.raise("Invalid group"),this.regexp_disjunction(t),t.eat(41))return t.numCapturingParens+=1,!0;t.raise("Unterminated group")}return!1},Vf.regexp_eatExtendedAtom=function(t){return t.eat(46)||this.regexp_eatReverseSolidusAtomEscape(t)||this.regexp_eatCharacterClass(t)||this.regexp_eatUncapturingGroup(t)||this.regexp_eatCapturingGroup(t)||this.regexp_eatInvalidBracedQuantifier(t)||this.regexp_eatExtendedPatternCharacter(t)},Vf.regexp_eatInvalidBracedQuantifier=function(t){return this.regexp_eatBracedQuantifier(t,!0)&&t.raise("Nothing to repeat"),!1},Vf.regexp_eatSyntaxCharacter=function(t){var e=t.current();return!!Wf(e)&&(t.lastIntValue=e,t.advance(),!0)},Vf.regexp_eatPatternCharacters=function(t){for(var e=t.pos,i=0;-1!==(i=t.current())&&!Wf(i);)t.advance();return t.pos!==e},Vf.regexp_eatExtendedPatternCharacter=function(t){var e=t.current();return!(-1===e||36===e||e>=40&&e<=43||46===e||63===e||91===e||94===e||124===e)&&(t.advance(),!0)},Vf.regexp_groupSpecifier=function(t){if(t.eat(63)){if(this.regexp_eatGroupName(t))return-1!==t.groupNames.indexOf(t.lastStringValue)&&t.raise("Duplicate capture group name"),void t.groupNames.push(t.lastStringValue);t.raise("Invalid group")}},Vf.regexp_eatGroupName=function(t){if(t.lastStringValue="",t.eat(60)){if(this.regexp_eatRegExpIdentifierName(t)&&t.eat(62))return!0;t.raise("Invalid capture group name")}return!1},Vf.regexp_eatRegExpIdentifierName=function(t){if(t.lastStringValue="",this.regexp_eatRegExpIdentifierStart(t)){for(t.lastStringValue+=Yd(t.lastIntValue);this.regexp_eatRegExpIdentifierPart(t);)t.lastStringValue+=Yd(t.lastIntValue);return!0}return!1},Vf.regexp_eatRegExpIdentifierStart=function(t){var e=t.pos,i=this.options.ecmaVersion>=11,n=t.current(i);return t.advance(i),92===n&&this.regexp_eatRegExpUnicodeEscapeSequence(t,i)&&(n=t.lastIntValue),function(t){return Od(t,!0)||36===t||95===t}(n)?(t.lastIntValue=n,!0):(t.pos=e,!1)},Vf.regexp_eatRegExpIdentifierPart=function(t){var e=t.pos,i=this.options.ecmaVersion>=11,n=t.current(i);return t.advance(i),92===n&&this.regexp_eatRegExpUnicodeEscapeSequence(t,i)&&(n=t.lastIntValue),function(t){return md(t,!0)||36===t||95===t||8204===t||8205===t}(n)?(t.lastIntValue=n,!0):(t.pos=e,!1)},Vf.regexp_eatAtomEscape=function(t){return!!(this.regexp_eatBackReference(t)||this.regexp_eatCharacterClassEscape(t)||this.regexp_eatCharacterEscape(t)||t.switchN&&this.regexp_eatKGroupName(t))||(t.switchU&&(99===t.current()&&t.raise("Invalid unicode escape"),t.raise("Invalid escape")),!1)},Vf.regexp_eatBackReference=function(t){var e=t.pos;if(this.regexp_eatDecimalEscape(t)){var i=t.lastIntValue;if(t.switchU)return i>t.maxBackReference&&(t.maxBackReference=i),!0;if(i<=t.numCapturingParens)return!0;t.pos=e}return!1},Vf.regexp_eatKGroupName=function(t){if(t.eat(107)){if(this.regexp_eatGroupName(t))return t.backReferenceNames.push(t.lastStringValue),!0;t.raise("Invalid named reference")}return!1},Vf.regexp_eatCharacterEscape=function(t){return this.regexp_eatControlEscape(t)||this.regexp_eatCControlLetter(t)||this.regexp_eatZero(t)||this.regexp_eatHexEscapeSequence(t)||this.regexp_eatRegExpUnicodeEscapeSequence(t,!1)||!t.switchU&&this.regexp_eatLegacyOctalEscapeSequence(t)||this.regexp_eatIdentityEscape(t)},Vf.regexp_eatCControlLetter=function(t){var e=t.pos;if(t.eat(99)){if(this.regexp_eatControlLetter(t))return!0;t.pos=e}return!1},Vf.regexp_eatZero=function(t){return 48===t.current()&&!Df(t.lookahead())&&(t.lastIntValue=0,t.advance(),!0)},Vf.regexp_eatControlEscape=function(t){var e=t.current();return 116===e?(t.lastIntValue=9,t.advance(),!0):110===e?(t.lastIntValue=10,t.advance(),!0):118===e?(t.lastIntValue=11,t.advance(),!0):102===e?(t.lastIntValue=12,t.advance(),!0):114===e&&(t.lastIntValue=13,t.advance(),!0)},Vf.regexp_eatControlLetter=function(t){var e=t.current();return!!qf(e)&&(t.lastIntValue=e%32,t.advance(),!0)},Vf.regexp_eatRegExpUnicodeEscapeSequence=function(t,e){void 0===e&&(e=!1);var i,n=t.pos,r=e||t.switchU;if(t.eat(117)){if(this.regexp_eatFixedHexDigits(t,4)){var s=t.lastIntValue;if(r&&s>=55296&&s<=56319){var o=t.pos;if(t.eat(92)&&t.eat(117)&&this.regexp_eatFixedHexDigits(t,4)){var a=t.lastIntValue;if(a>=56320&&a<=57343)return t.lastIntValue=1024*(s-55296)+(a-56320)+65536,!0}t.pos=o,t.lastIntValue=s}return!0}if(r&&t.eat(123)&&this.regexp_eatHexDigits(t)&&t.eat(125)&&((i=t.lastIntValue)>=0&&i<=1114111))return!0;r&&t.raise("Invalid unicode escape"),t.pos=n}return!1},Vf.regexp_eatIdentityEscape=function(t){if(t.switchU)return!!this.regexp_eatSyntaxCharacter(t)||!!t.eat(47)&&(t.lastIntValue=47,!0);var e=t.current();return!(99===e||t.switchN&&107===e)&&(t.lastIntValue=e,t.advance(),!0)},Vf.regexp_eatDecimalEscape=function(t){t.lastIntValue=0;var e=t.current();if(e>=49&&e<=57){do{t.lastIntValue=10*t.lastIntValue+(e-48),t.advance()}while((e=t.current())>=48&&e<=57);return!0}return!1};function If(t){return qf(t)||95===t}function jf(t){return If(t)||Df(t)}function Df(t){return t>=48&&t<=57}function Mf(t){return t>=48&&t<=57||t>=65&&t<=70||t>=97&&t<=102}function Nf(t){return t>=65&&t<=70?t-65+10:t>=97&&t<=102?t-97+10:t-48}function zf(t){return t>=48&&t<=55}Vf.regexp_eatCharacterClassEscape=function(t){var e=t.current();if(function(t){return 100===t||68===t||115===t||83===t||119===t||87===t}(e))return t.lastIntValue=-1,t.advance(),1;var i=!1;if(t.switchU&&this.options.ecmaVersion>=9&&((i=80===e)||112===e)){var n;if(t.lastIntValue=-1,t.advance(),t.eat(123)&&(n=this.regexp_eatUnicodePropertyValueExpression(t))&&t.eat(125))return i&&2===n&&t.raise("Invalid property name"),n;t.raise("Invalid property name")}return 0},Vf.regexp_eatUnicodePropertyValueExpression=function(t){var e=t.pos;if(this.regexp_eatUnicodePropertyName(t)&&t.eat(61)){var i=t.lastStringValue;if(this.regexp_eatUnicodePropertyValue(t)){var n=t.lastStringValue;return this.regexp_validateUnicodePropertyNameAndValue(t,i,n),1}}if(t.pos=e,this.regexp_eatLoneUnicodePropertyNameOrValue(t)){var r=t.lastStringValue;return this.regexp_validateUnicodePropertyNameOrValue(t,r)}return 0},Vf.regexp_validateUnicodePropertyNameAndValue=function(t,e,i){Ed(t.unicodeProperties.nonBinary,e)||t.raise("Invalid property name"),t.unicodeProperties.nonBinary[e].test(i)||t.raise("Invalid property value")},Vf.regexp_validateUnicodePropertyNameOrValue=function(t,e){return t.unicodeProperties.binary.test(e)?1:t.switchV&&t.unicodeProperties.binaryOfStrings.test(e)?2:void t.raise("Invalid property name")},Vf.regexp_eatUnicodePropertyName=function(t){var e=0;for(t.lastStringValue="";If(e=t.current());)t.lastStringValue+=Yd(e),t.advance();return""!==t.lastStringValue},Vf.regexp_eatUnicodePropertyValue=function(t){var e=0;for(t.lastStringValue="";jf(e=t.current());)t.lastStringValue+=Yd(e),t.advance();return""!==t.lastStringValue},Vf.regexp_eatLoneUnicodePropertyNameOrValue=function(t){return this.regexp_eatUnicodePropertyValue(t)},Vf.regexp_eatCharacterClass=function(t){if(t.eat(91)){var e=t.eat(94),i=this.regexp_classContents(t);return t.eat(93)||t.raise("Unterminated character class"),e&&2===i&&t.raise("Negated character class may contain strings"),!0}return!1},Vf.regexp_classContents=function(t){return 93===t.current()?1:t.switchV?this.regexp_classSetExpression(t):(this.regexp_nonEmptyClassRanges(t),1)},Vf.regexp_nonEmptyClassRanges=function(t){for(;this.regexp_eatClassAtom(t);){var e=t.lastIntValue;if(t.eat(45)&&this.regexp_eatClassAtom(t)){var i=t.lastIntValue;!t.switchU||-1!==e&&-1!==i||t.raise("Invalid character class"),-1!==e&&-1!==i&&e>i&&t.raise("Range out of order in character class")}}},Vf.regexp_eatClassAtom=function(t){var e=t.pos;if(t.eat(92)){if(this.regexp_eatClassEscape(t))return!0;if(t.switchU){var i=t.current();(99===i||zf(i))&&t.raise("Invalid class escape"),t.raise("Invalid escape")}t.pos=e}var n=t.current();return 93!==n&&(t.lastIntValue=n,t.advance(),!0)},Vf.regexp_eatClassEscape=function(t){var e=t.pos;if(t.eat(98))return t.lastIntValue=8,!0;if(t.switchU&&t.eat(45))return t.lastIntValue=45,!0;if(!t.switchU&&t.eat(99)){if(this.regexp_eatClassControlLetter(t))return!0;t.pos=e}return this.regexp_eatCharacterClassEscape(t)||this.regexp_eatCharacterEscape(t)},Vf.regexp_classSetExpression=function(t){var e,i=1;if(this.regexp_eatClassSetRange(t));else if(e=this.regexp_eatClassSetOperand(t)){2===e&&(i=2);for(var n=t.pos;t.eatChars([38,38]);)38!==t.current()&&(e=this.regexp_eatClassSetOperand(t))?2!==e&&(i=1):t.raise("Invalid character in character class");if(n!==t.pos)return i;for(;t.eatChars([45,45]);)this.regexp_eatClassSetOperand(t)||t.raise("Invalid character in character class");if(n!==t.pos)return i}else t.raise("Invalid character in character class");for(;;)if(!this.regexp_eatClassSetRange(t)){if(!(e=this.regexp_eatClassSetOperand(t)))return i;2===e&&(i=2)}},Vf.regexp_eatClassSetRange=function(t){var e=t.pos;if(this.regexp_eatClassSetCharacter(t)){var i=t.lastIntValue;if(t.eat(45)&&this.regexp_eatClassSetCharacter(t)){var n=t.lastIntValue;return-1!==i&&-1!==n&&i>n&&t.raise("Range out of order in character class"),!0}t.pos=e}return!1},Vf.regexp_eatClassSetOperand=function(t){return this.regexp_eatClassSetCharacter(t)?1:this.regexp_eatClassStringDisjunction(t)||this.regexp_eatNestedClass(t)},Vf.regexp_eatNestedClass=function(t){var e=t.pos;if(t.eat(91)){var i=t.eat(94),n=this.regexp_classContents(t);if(t.eat(93))return i&&2===n&&t.raise("Negated character class may contain strings"),n;t.pos=e}if(t.eat(92)){var r=this.regexp_eatCharacterClassEscape(t);if(r)return r;t.pos=e}return null},Vf.regexp_eatClassStringDisjunction=function(t){var e=t.pos;if(t.eatChars([92,113])){if(t.eat(123)){var i=this.regexp_classStringDisjunctionContents(t);if(t.eat(125))return i}else t.raise("Invalid escape");t.pos=e}return null},Vf.regexp_classStringDisjunctionContents=function(t){for(var e=this.regexp_classString(t);t.eat(124);)2===this.regexp_classString(t)&&(e=2);return e},Vf.regexp_classString=function(t){for(var e=0;this.regexp_eatClassSetCharacter(t);)e++;return 1===e?1:2},Vf.regexp_eatClassSetCharacter=function(t){var e=t.pos;if(t.eat(92))return!(!this.regexp_eatCharacterEscape(t)&&!this.regexp_eatClassSetReservedPunctuator(t))||(t.eat(98)?(t.lastIntValue=8,!0):(t.pos=e,!1));var i=t.current();return!(i<0||i===t.lookahead()&&function(t){return 33===t||t>=35&&t<=38||t>=42&&t<=44||46===t||t>=58&&t<=64||94===t||96===t||126===t}(i))&&(!function(t){return 40===t||41===t||45===t||47===t||t>=91&&t<=93||t>=123&&t<=125}(i)&&(t.advance(),t.lastIntValue=i,!0))},Vf.regexp_eatClassSetReservedPunctuator=function(t){var e=t.current();return!!function(t){return 33===t||35===t||37===t||38===t||44===t||45===t||t>=58&&t<=62||64===t||96===t||126===t}(e)&&(t.lastIntValue=e,t.advance(),!0)},Vf.regexp_eatClassControlLetter=function(t){var e=t.current();return!(!Df(e)&&95!==e)&&(t.lastIntValue=e%32,t.advance(),!0)},Vf.regexp_eatHexEscapeSequence=function(t){var e=t.pos;if(t.eat(120)){if(this.regexp_eatFixedHexDigits(t,2))return!0;t.switchU&&t.raise("Invalid escape"),t.pos=e}return!1},Vf.regexp_eatDecimalDigits=function(t){var e=t.pos,i=0;for(t.lastIntValue=0;Df(i=t.current());)t.lastIntValue=10*t.lastIntValue+(i-48),t.advance();return t.pos!==e},Vf.regexp_eatHexDigits=function(t){var e=t.pos,i=0;for(t.lastIntValue=0;Mf(i=t.current());)t.lastIntValue=16*t.lastIntValue+Nf(i),t.advance();return t.pos!==e},Vf.regexp_eatLegacyOctalEscapeSequence=function(t){if(this.regexp_eatOctalDigit(t)){var e=t.lastIntValue;if(this.regexp_eatOctalDigit(t)){var i=t.lastIntValue;e<=3&&this.regexp_eatOctalDigit(t)?t.lastIntValue=64*e+8*i+t.lastIntValue:t.lastIntValue=8*e+i}else t.lastIntValue=e;return!0}return!1},Vf.regexp_eatOctalDigit=function(t){var e=t.current();return zf(e)?(t.lastIntValue=e-48,t.advance(),!0):(t.lastIntValue=0,!1)},Vf.regexp_eatFixedHexDigits=function(t,e){var i=t.pos;t.lastIntValue=0;for(var n=0;n=this.input.length?this.finishToken(vd.eof):t.override?t.override(this):void this.readToken(this.fullCharCodeAtPos())},Lf.readToken=function(t){return Od(t,this.options.ecmaVersion>=6)||92===t?this.readWord():this.getTokenFromCode(t)},Lf.fullCharCodeAtPos=function(){var t=this.input.charCodeAt(this.pos);if(t<=55295||t>=56320)return t;var e=this.input.charCodeAt(this.pos+1);return e<=56319||e>=57344?t:(t<<10)+e-56613888},Lf.skipBlockComment=function(){var t=this.options.onComment&&this.curPosition(),e=this.pos,i=this.input.indexOf("*/",this.pos+=2);if(-1===i&&this.raise(this.pos-2,"Unterminated comment"),this.pos=i+2,this.options.locations)for(var n=void 0,r=e;(n=$d(this.input,r,this.pos))>-1;)++this.curLine,r=this.lineStart=n;this.options.onComment&&this.options.onComment(!0,this.input.slice(e+2,i),e,this.pos,t,this.curPosition())},Lf.skipLineComment=function(t){for(var e=this.pos,i=this.options.onComment&&this.curPosition(),n=this.input.charCodeAt(this.pos+=t);this.pos8&&t<14||t>=5760&&Zd.test(String.fromCharCode(t))))break t;++this.pos}}},Lf.finishToken=function(t,e){this.end=this.pos,this.options.locations&&(this.endLoc=this.curPosition());var i=this.type;this.type=t,this.value=e,this.updateContext(i)},Lf.readToken_dot=function(){var t=this.input.charCodeAt(this.pos+1);if(t>=48&&t<=57)return this.readNumber(!0);var e=this.input.charCodeAt(this.pos+2);return this.options.ecmaVersion>=6&&46===t&&46===e?(this.pos+=3,this.finishToken(vd.ellipsis)):(++this.pos,this.finishToken(vd.dot))},Lf.readToken_slash=function(){var t=this.input.charCodeAt(this.pos+1);return this.exprAllowed?(++this.pos,this.readRegexp()):61===t?this.finishOp(vd.assign,2):this.finishOp(vd.slash,1)},Lf.readToken_mult_modulo_exp=function(t){var e=this.input.charCodeAt(this.pos+1),i=1,n=42===t?vd.star:vd.modulo;return this.options.ecmaVersion>=7&&42===t&&42===e&&(++i,n=vd.starstar,e=this.input.charCodeAt(this.pos+2)),61===e?this.finishOp(vd.assign,i+1):this.finishOp(n,i)},Lf.readToken_pipe_amp=function(t){var e=this.input.charCodeAt(this.pos+1);if(e===t){if(this.options.ecmaVersion>=12)if(61===this.input.charCodeAt(this.pos+2))return this.finishOp(vd.assign,3);return this.finishOp(124===t?vd.logicalOR:vd.logicalAND,2)}return 61===e?this.finishOp(vd.assign,2):this.finishOp(124===t?vd.bitwiseOR:vd.bitwiseAND,1)},Lf.readToken_caret=function(){return 61===this.input.charCodeAt(this.pos+1)?this.finishOp(vd.assign,2):this.finishOp(vd.bitwiseXOR,1)},Lf.readToken_plus_min=function(t){var e=this.input.charCodeAt(this.pos+1);return e===t?45!==e||this.inModule||62!==this.input.charCodeAt(this.pos+2)||0!==this.lastTokEnd&&!kd.test(this.input.slice(this.lastTokEnd,this.pos))?this.finishOp(vd.incDec,2):(this.skipLineComment(3),this.skipSpace(),this.nextToken()):61===e?this.finishOp(vd.assign,2):this.finishOp(vd.plusMin,1)},Lf.readToken_lt_gt=function(t){var e=this.input.charCodeAt(this.pos+1),i=1;return e===t?(i=62===t&&62===this.input.charCodeAt(this.pos+2)?3:2,61===this.input.charCodeAt(this.pos+i)?this.finishOp(vd.assign,i+1):this.finishOp(vd.bitShift,i)):33!==e||60!==t||this.inModule||45!==this.input.charCodeAt(this.pos+2)||45!==this.input.charCodeAt(this.pos+3)?(61===e&&(i=2),this.finishOp(vd.relational,i)):(this.skipLineComment(4),this.skipSpace(),this.nextToken())},Lf.readToken_eq_excl=function(t){var e=this.input.charCodeAt(this.pos+1);return 61===e?this.finishOp(vd.equality,61===this.input.charCodeAt(this.pos+2)?3:2):61===t&&62===e&&this.options.ecmaVersion>=6?(this.pos+=2,this.finishToken(vd.arrow)):this.finishOp(61===t?vd.eq:vd.prefix,1)},Lf.readToken_question=function(){var t=this.options.ecmaVersion;if(t>=11){var e=this.input.charCodeAt(this.pos+1);if(46===e){var i=this.input.charCodeAt(this.pos+2);if(i<48||i>57)return this.finishOp(vd.questionDot,2)}if(63===e){if(t>=12)if(61===this.input.charCodeAt(this.pos+2))return this.finishOp(vd.assign,3);return this.finishOp(vd.coalesce,2)}}return this.finishOp(vd.question,1)},Lf.readToken_numberSign=function(){var t=35;if(this.options.ecmaVersion>=13&&(++this.pos,Od(t=this.fullCharCodeAtPos(),!0)||92===t))return this.finishToken(vd.privateId,this.readWord1());this.raise(this.pos,"Unexpected character '"+Yd(t)+"'")},Lf.getTokenFromCode=function(t){switch(t){case 46:return this.readToken_dot();case 40:return++this.pos,this.finishToken(vd.parenL);case 41:return++this.pos,this.finishToken(vd.parenR);case 59:return++this.pos,this.finishToken(vd.semi);case 44:return++this.pos,this.finishToken(vd.comma);case 91:return++this.pos,this.finishToken(vd.bracketL);case 93:return++this.pos,this.finishToken(vd.bracketR);case 123:return++this.pos,this.finishToken(vd.braceL);case 125:return++this.pos,this.finishToken(vd.braceR);case 58:return++this.pos,this.finishToken(vd.colon);case 96:if(this.options.ecmaVersion<6)break;return++this.pos,this.finishToken(vd.backQuote);case 48:var e=this.input.charCodeAt(this.pos+1);if(120===e||88===e)return this.readRadixNumber(16);if(this.options.ecmaVersion>=6){if(111===e||79===e)return this.readRadixNumber(8);if(98===e||66===e)return this.readRadixNumber(2)}case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return this.readNumber(!1);case 34:case 39:return this.readString(t);case 47:return this.readToken_slash();case 37:case 42:return this.readToken_mult_modulo_exp(t);case 124:case 38:return this.readToken_pipe_amp(t);case 94:return this.readToken_caret();case 43:case 45:return this.readToken_plus_min(t);case 60:case 62:return this.readToken_lt_gt(t);case 61:case 33:return this.readToken_eq_excl(t);case 63:return this.readToken_question();case 126:return this.finishOp(vd.prefix,1);case 35:return this.readToken_numberSign()}this.raise(this.pos,"Unexpected character '"+Yd(t)+"'")},Lf.finishOp=function(t,e){var i=this.input.slice(this.pos,this.pos+e);return this.pos+=e,this.finishToken(t,i)},Lf.readRegexp=function(){for(var t,e,i=this.pos;;){this.pos>=this.input.length&&this.raise(i,"Unterminated regular expression");var n=this.input.charAt(this.pos);if(kd.test(n)&&this.raise(i,"Unterminated regular expression"),t)t=!1;else{if("["===n)e=!0;else if("]"===n&&e)e=!1;else if("/"===n&&!e)break;t="\\"===n}++this.pos}var r=this.input.slice(i,this.pos);++this.pos;var s=this.pos,o=this.readWord1();this.containsEsc&&this.unexpected(s);var a=this.regexpState||(this.regexpState=new Yf(this));a.reset(i,r,o),this.validateRegExpFlags(a),this.validateRegExpPattern(a);var l=null;try{l=new RegExp(r,o)}catch(t){}return this.finishToken(vd.regexp,{pattern:r,flags:o,value:l})},Lf.readInt=function(t,e,i){for(var n=this.options.ecmaVersion>=12&&void 0===e,r=i&&48===this.input.charCodeAt(this.pos),s=this.pos,o=0,a=0,l=0,h=null==e?1/0:e;l=97?c-97+10:c>=65?c-65+10:c>=48&&c<=57?c-48:1/0)>=t)break;a=c,o=o*t+u}}return n&&95===a&&this.raiseRecoverable(this.pos-1,"Numeric separator is not allowed at the last of digits"),this.pos===s||null!=e&&this.pos-s!==e?null:o},Lf.readRadixNumber=function(t){var e=this.pos;this.pos+=2;var i=this.readInt(t);return null==i&&this.raise(this.start+2,"Expected number in radix "+t),this.options.ecmaVersion>=11&&110===this.input.charCodeAt(this.pos)?(i=Gf(this.input.slice(e,this.pos)),++this.pos):Od(this.fullCharCodeAtPos())&&this.raise(this.pos,"Identifier directly after number"),this.finishToken(vd.num,i)},Lf.readNumber=function(t){var e=this.pos;t||null!==this.readInt(10,void 0,!0)||this.raise(e,"Invalid number");var i=this.pos-e>=2&&48===this.input.charCodeAt(e);i&&this.strict&&this.raise(e,"Invalid number");var n=this.input.charCodeAt(this.pos);if(!i&&!t&&this.options.ecmaVersion>=11&&110===n){var r=Gf(this.input.slice(e,this.pos));return++this.pos,Od(this.fullCharCodeAtPos())&&this.raise(this.pos,"Identifier directly after number"),this.finishToken(vd.num,r)}i&&/[89]/.test(this.input.slice(e,this.pos))&&(i=!1),46!==n||i||(++this.pos,this.readInt(10),n=this.input.charCodeAt(this.pos)),69!==n&&101!==n||i||(43!==(n=this.input.charCodeAt(++this.pos))&&45!==n||++this.pos,null===this.readInt(10)&&this.raise(e,"Invalid number")),Od(this.fullCharCodeAtPos())&&this.raise(this.pos,"Identifier directly after number");var s,o=(s=this.input.slice(e,this.pos),i?parseInt(s,8):parseFloat(s.replace(/_/g,"")));return this.finishToken(vd.num,o)},Lf.readCodePoint=function(){var t;if(123===this.input.charCodeAt(this.pos)){this.options.ecmaVersion<6&&this.unexpected();var e=++this.pos;t=this.readHexChar(this.input.indexOf("}",this.pos)-this.pos),++this.pos,t>1114111&&this.invalidStringToken(e,"Code point out of bounds")}else t=this.readHexChar(4);return t},Lf.readString=function(t){for(var e="",i=++this.pos;;){this.pos>=this.input.length&&this.raise(this.start,"Unterminated string constant");var n=this.input.charCodeAt(this.pos);if(n===t)break;92===n?(e+=this.input.slice(i,this.pos),e+=this.readEscapedChar(!1),i=this.pos):8232===n||8233===n?(this.options.ecmaVersion<10&&this.raise(this.start,"Unterminated string constant"),++this.pos,this.options.locations&&(this.curLine++,this.lineStart=this.pos)):(Pd(n)&&this.raise(this.start,"Unterminated string constant"),++this.pos)}return e+=this.input.slice(i,this.pos++),this.finishToken(vd.string,e)};var Uf={};Lf.tryReadTemplateToken=function(){this.inTemplateElement=!0;try{this.readTmplToken()}catch(t){if(t!==Uf)throw t;this.readInvalidTemplateToken()}this.inTemplateElement=!1},Lf.invalidStringToken=function(t,e){if(this.inTemplateElement&&this.options.ecmaVersion>=9)throw Uf;this.raise(t,e)},Lf.readTmplToken=function(){for(var t="",e=this.pos;;){this.pos>=this.input.length&&this.raise(this.start,"Unterminated template");var i=this.input.charCodeAt(this.pos);if(96===i||36===i&&123===this.input.charCodeAt(this.pos+1))return this.pos!==this.start||this.type!==vd.template&&this.type!==vd.invalidTemplate?(t+=this.input.slice(e,this.pos),this.finishToken(vd.template,t)):36===i?(this.pos+=2,this.finishToken(vd.dollarBraceL)):(++this.pos,this.finishToken(vd.backQuote));if(92===i)t+=this.input.slice(e,this.pos),t+=this.readEscapedChar(!0),e=this.pos;else if(Pd(i)){switch(t+=this.input.slice(e,this.pos),++this.pos,i){case 13:10===this.input.charCodeAt(this.pos)&&++this.pos;case 10:t+="\n";break;default:t+=String.fromCharCode(i)}this.options.locations&&(++this.curLine,this.lineStart=this.pos),e=this.pos}else++this.pos}},Lf.readInvalidTemplateToken=function(){for(;this.pos=48&&e<=55){var n=this.input.substr(this.pos-1,3).match(/^[0-7]+/)[0],r=parseInt(n,8);return r>255&&(n=n.slice(0,-1),r=parseInt(n,8)),this.pos+=n.length-1,e=this.input.charCodeAt(this.pos),"0"===n&&56!==e&&57!==e||!this.strict&&!t||this.invalidStringToken(this.pos-1-n.length,t?"Octal literal in template string":"Octal literal in strict mode"),String.fromCharCode(r)}return Pd(e)?"":String.fromCharCode(e)}},Lf.readHexChar=function(t){var e=this.pos,i=this.readInt(16,t);return null===i&&this.invalidStringToken(e,"Bad character escape sequence"),i},Lf.readWord1=function(){this.containsEsc=!1;for(var t="",e=!0,i=this.pos,n=this.options.ecmaVersion>=6;this.pos{let e=i.win.document.createElement("script");return e.src=t,i.win.document.body.appendChild(e),gO(e,"load")}));return await Promise.all(n),i}constructor(t){this.startedAt=null,this.extraSecs=2,this.output=null,this.handleDeps=!0,this.callbacks={},this.timeouts=[],this.intervals=[],this.frames=[],this.framePos=0,this.loaded=new hO((t=>cO.compute(t).then((({name:t,code:e})=>this.evalModule(t,e))))),this.frame=t,this.win=t.contentWindow,this.setupEnv();const e=()=>{"none"!=this.frame.style.display&&this.resizeFrame()};this.frame.addEventListener("load",e);const i=()=>{this.win.clearTimeout(null),this.win.__setTimeout(e,200)};this.win.addEventListener("keydown",i),this.win.addEventListener("mousedown",i)}async run(t,e){return e&&(this.output=e),this.startedAt=Date.now(),this.extraSecs="string"==typeof t&&/promtDirection/.test(t)?.1:2,this.win.__c=0,this.prepare(t).then((t=>this.sandbox_run(t))).catch((t=>this.error(t)))}sandbox_run(t){return t instanceof Function?t():this.win.eval(t)}prepare(t){let{code:e,dependencies:i}=nO(t,this);return(this.handleDeps?Promise.all(i.map((t=>this.loaded.compute(t)))):Promise.resolve([])).then((()=>e))}evalModule(t,e){if(/\.json$/.test(t))return this.loaded.store(t,{exports:JSON.parse(e)});let i=function(t){let e,i=[];try{e=Ff(t)}catch(t){return i}return Hf(e,{CallExpression(t){"Identifier"!=t.callee.type||"require"!=t.callee.name||1!=t.arguments.length||"Literal"!=t.arguments[0].type||"string"!=typeof t.arguments[0].value||i.includes(t.arguments[0].value)||i.push(t.arguments[0].value)}}),i}(e).map((e=>this.loaded.compute(lO(t,e))));return Promise.all(i).then((()=>{let i=new this.win.Function("require, exports, module, __dirname, __filename",e+"\n//# sourceURL=code"+aO()),n=this.loaded.store(t,{exports:{}});return i((e=>this.require(lO(t,e))),n.exports,n,t,t),n}))}require(t){let e=cO.get(t);if(!e)throw new Error(`Could not load module '${t}'`);return this.loaded.get(e.name).exports}async setHTML(t,e){this.clearEvents();let i=String(this.win.document.location);if(i!=String(document.location)&&!/\/empty\.html$/.test(i))return this.frame.src=this.emptyPath+"empty.html",await avaitEvent(this.frame,"load"),this.setupEnv(),this.setHTML(t,e);let n=[],r=this,s=this.win.document;this.frame.style.display="block";s.documentElement.innerHTML=t.replace(/]*?(?:\bsrc\s*=\s*('[^']+'|"[^"]+"|[^\s>]+)[^>]*)?>([\s\S]*?)<\/script>/g,(function(t,e,i){let o=s.createElement("script");return e?(/["']/.test(e.charAt(0))&&(e=e.slice(1,e.length-1)),o.src=e):o.text=nO(i,r).code,n.push(o),""})),this.frame.style.height="80px",this.resizeFrame(),e&&(this.output=e);for(let t of n)r.startedAt=Date.now(),r.extraSecs=2,r.win.__c=0,t.src?(s.body.appendChild(t),await gO(t,"load")):await new Promise((e=>{let i=aO();r.callbacks[i]=()=>{delete r.callbacks[i],e()},t.text+="\n;__sandbox.callbacks['"+i+"']();",s.body.appendChild(t)}));(n.length||s.querySelector("img"))&&setTimeout((()=>r.resizeFrame()),100)}setupEnv(){let t=this.win;t.__sandbox=this,t.onerror=(t,e,i)=>{if(this.output)return this.output.out("error",[t+(null!=i?" (line "+i+")":"")]),!0},t.console={log:(...t)=>this.out("log",t),error:(...t)=>this.out("error",t),warn:(...t)=>this.out("warn",t),info:(...t)=>this.out("log",t)},t.setInterval((()=>{this.startedAt=null}),1e3),t.__setTimeout=t.setTimeout,t.__setInterval=t.setInterval,t.setTimeout=(e,i,...n)=>{if(n.length&&"string"!=typeof e){let t=e;e=()=>t(...n)}let r=t.__setTimeout((()=>this.run(e)),i);return this.timeouts.push(r),r},t.setInterval=(e,i,...n)=>{if(n.length&&"string"!=typeof e){let t=e;e=()=>t(...n)}let r=t.__setInterval((()=>this.run(e)),i);return this.intervals.push(r),r};let e=t.requestAnimationFrame;t.requestAnimationFrame=i=>{let n=e.call(t,i);return this.frames.length>50?this.frames[this.framePos++%50]=n:this.frames.push(n),n},t.addEventListener("unhandledrejection",(t=>this.error(t.reason))),t.require=t=>this.require(t),t.require.preload=(t,e)=>cO.store(t,{name:t,code:e}),t.module={exports:{}},t.exports=t.module.exports}resizeFrame(){this.frame.style.height=Math.max(80,Math.min(this.win.document.documentElement.offsetHeight+2,500))+"px";let t=this.frame.getBoundingClientRect();t.bottom>t.top&&t.top>=0&&t.topwindow.innerHeight&&window.scrollBy(0,Math.min(t.top,t.bottom-window.innerHeight))}tick(){let t=Date.now();if(null==this.startedAt&&(this.startedAt=t),t1){this.output.div.lastChild.appendChild(document.createTextNode(" "));let t=this.output.div.lastChild.appendChild(document.createElement("span"));t.innerHTML="…",t.className="sandbox-output-etc",t.addEventListener("click",(()=>{t.className="",t.innerHTML="\n called from "+e.slice(1).map(eO).join("\n called from ")}))}}clearEvents(){for(;this.timeouts.length;)this.win.clearTimeout(this.timeouts.pop());for(;this.intervals.length;)this.win.clearInterval(this.intervals.pop());for(;this.frames.length;)this.win.cancelAnimationFrame(this.frames.pop());this.timeouts.length=this.intervals.length=this.frames.length=this.framePos=0}}function nO(t,e){if("string"!=typeof t){if(t.apply){let i=t;t=(...t)=>{try{return i.apply(null,t)}catch(t){e.error(t)}}}return{code:t,dependencies:[]}}/\n$/.test(t)||(t+="\n");let i,n=/^(\s|\/\/.*)*["']use strict['"]/.test(t);try{i=Ff(t,{sourceType:oO(t),ecmaVersion:"latest"})}catch(e){return{code:t,dependencies:[]}}let r=[],s=";if (++__c % 1000 === 0) __sandbox.tick();";function o(t){"BlockStatement"==t.body.type?r.push({from:t.body.end-1,text:s}):r.push({from:t.body.start,text:"{"},{from:t.body.end,text:s+"}"})}let a=[];Hf(i,{ForStatement:o,ForInStatement:o,WhileStatement:o,DoWhileStatement:o,CallExpression(t){"Identifier"!=t.callee.type||"require"!=t.callee.name||1!=t.arguments.length||"Literal"!=t.arguments[0].type||"string"!=typeof t.arguments[0].value||a.includes(t.arguments[0].value)||a.push(t.arguments[0].value)},ImportDeclaration(t){a.push(t.source.value);let e,i="require("+t.source.raw+")";if(0==t.specifiers.length)e=i;else if(t.specifiers.length>1||"ImportDefaultSpecifier"==t.specifiers[0].type){let n="m_"+t.source.value.replace(/\W+/g,"_")+"__";e="var "+n+" = "+i,t.specifiers.forEach((t=>{"ImportDefaultSpecifier"==t.type?e+=", "+t.local.name+" = "+n+".default || "+n:null!=n&&(e+=", "+t.local.name+" = "+n+"."+t.imported.name)}))}else e="var ",t.specifiers.forEach((t=>{"ImportNamespaceSpecifier"==t.type?e+=t.local.name+" = "+i:e+=t.local.name+" = "+i+"."+t.imported.name}));r.push({from:t.start,to:t.end,text:e+";"})},ExportNamedDeclaration(t){t.source||!t.declaration?r.push({from:t.start,to:t.end,text:""}):r.push({from:t.start,to:t.declaration.start,text:""})},ExportDefaultDeclaration(t){/Declaration/.test(t.declaration.type)?r.push({from:t.start,to:t.declaration.start,text:""}):r.push({from:t.start,to:t.declaration.start,text:";("},{from:t.declaration.end,text:")"})},ExportAllDeclaration:function(t){r.push({from:t.start,to:t.end,text:""})}});let l=0,h=i.end;for(let t=n?1:0;tt.from-e.from||(t.to||t.from)-(e.to||e.from)||(e.priority||0)-(t.priority||0)));let c="",u=0;for(let e=0;ethis.done[t]=e)))}store(t,e){return this.work[t]=Promise.resolve(e),this.done[t]=e}get(t){return this.done[t]}}const cO=new hO((t=>fetch("https://unpkg.com/"+t.replace(/\/$/,"")).then((e=>{if(e.status>=400)throw new Error(`Failed to resolve package '${t}'`);let i=e.url.replace(/.*unpkg\.com\//,"");return cO.get(i)||e.text().then((t=>cO.store(i,{name:i,code:t})))}))));function uO(t,e){let i=document.createElement("span");return i.className="sandbox-output-"+t,i.appendChild(document.createTextNode(e)),i}function pO(t){return t.textContent.length}function dO(t,e){return"boolean"==typeof t?uO("bool",String(t)):"number"==typeof t?uO("number",String(t)):"string"==typeof t?uO("string",JSON.stringify(t)):"symbol"==typeof t?uO("symbol",String(t)):null==t?uO("null",String(t)):Array.isArray(t)?function(t,e){e-=2;let i=document.createElement("span");i.appendChild(document.createTextNode("["));for(let n=0;n0&&dO(t[n],e),s=r?pO(r):0;if(e-s<=0){i.appendChild(uO("etc","…")).addEventListener("click",(()=>mO(i,"array",t)));break}e-=s,i.appendChild(r)}return i.appendChild(document.createTextNode("]")),i}(t,e):function(t,e){let i,n="function"==typeof t.toString&&t.toString();if(!n||/^\[object .*\]$/.test(n))return function(t,e){e-=2;let i=document.createElement("span"),n=fO(t);n&&(e-=n.length,i.appendChild(document.createTextNode(n)));i.appendChild(document.createTextNode("{"));try{let n=!0;for(let r in t)if(OO(t,r)){n?n=!1:(e-=2,i.appendChild(document.createTextNode(", ")));let s=e>0&&dO(t[r],e),o=s?r.length+2+pO(s):0;if(e-o<=0){i.appendChild(uO("etc","…")).addEventListener("click",(()=>mO(i,"obj",t)));break}e-=o,i.appendChild(uO("prop",r+": ")),i.appendChild(s)}}catch(t){i.appendChild(document.createTextNode("…"))}return i.appendChild(document.createTextNode("}")),i}(t,e);if(t.call&&(i=n.match(/^\s*(function[^(]*\([^)]*\))/)))return uO("fun",i[1]+"{…}");let r=uO("etc",n);return r.addEventListener("click",(()=>mO(r,"obj",t))),r}(t,e)}function fO(t){if(!t.constructor)return null;let e=String(t.constructor).match(/^function\s*([^\s(]+)/);return e&&"Object"!=e[1]?e[1]:void 0}function OO(t,e){return Object.prototype.hasOwnProperty.call(t,e)}function mO(t,e,i){let n,r=document.createElement("span"),s="array"==e?"[":"{";"{"==s&&(n=fO(i))&&(s=n+" {"),r.appendChild(document.createTextNode(s));let o=r.appendChild(document.createElement("div"));o.className="sandbox-output-etc-block";let a=o.appendChild(document.createElement("table"));function l(t){let e=a.appendChild(document.createElement("tr"));e.appendChild(document.createElement("td")).appendChild(uO("prop",t+":")),e.appendChild(document.createElement("td")).appendChild(dO(i[t],40))}if("array"==e)for(let t=0;t{let n=()=>{t.removeEventListener(e,n),i()};t.addEventListener(e,n)}))}function yO(t,e=50){let i;return n=>{i&&clearTimeout(i),i=setTimeout((()=>t(n)),e)}}if(iO.Output=class{constructor(t){this.div=t}clear(){let t=this.div.cloneNode(!1);this.div.parentNode.replaceChild(t,this.div),this.div=t}get empty(){return!this.div.firstChild}out(t,e){let i=document.createElement("pre");i.className="sandbox-output-"+t;for(let t=0;t{t.style.display="inline",t.addEventListener("click",e)})),document.body.addEventListener("keydown",(t=>{let i=document.activeElement;if("?"!=t.key||t.ctrlKey||t.altKey||t.metaKey||(!i||"true"!=i.contentEditable&&"INPUT"!=i.nodeName)&&(t.preventDefault(),e()),"Enter"==t.key&&!t.ctrlKey&&!t.altKey&&!t.metaKey){let e=i&&s(i);e&&(t.preventDefault(),e.focus())}}));let t=/Mac/.test(navigator.platform)?"Cmd-":"Ctrl-";function e(){let e=document.body.appendChild(document.createElement("div"));e.className="popup",e.appendChild(document.createElement("h2")).textContent="Instrucciones",e.appendChild(document.createElement("p")).textContent="El código de esta página se puede editar y ejecutar haciendo clic en él o moviendo el foco hacia él y presionando Enter. El código ejecutado de esta manera comparte su entorno con otro código ejecutado en la página y con algún código predefinido para el capítulo. Dentro del editor de código, los siguientes atajos de teclado están disponibles.";for(let[i,n]of[[t+"Enter","Ejecutar código"],[t+"j","Revertir código"],[t+"↓","Desactivar editor"],[t+"Escape","Restablecer entorno"]]){let t=e.appendChild(document.createElement("div"));t.appendChild(document.createElement("kbd")).textContent=i,t.appendChild(document.createTextNode(": "+n))}e.tabIndex=0,e.addEventListener("blur",(()=>e.remove())),e.addEventListener("keydown",(t=>{"Escape"==t.key&&(t.preventDefault(),e.remove())})),e.focus()}function i(t,e){let i=1,n=document.createElement(t);if(e&&"object"==typeof e&&null==e.nodeType){for(let t in e)if(e.hasOwnProperty(t)){let i=e[t];"css"==t?n.style.cssText=i:"string"!=typeof i?n[t]=i:n.setAttribute(t,i)}i=2}for(let t=i;t{for(let e=t.target;e;e=e.parentNode){if("c_ident"==e.className)return;let i=s(e);if(i)return t.preventDefault(),void setTimeout((()=>{let e=i.posAtCoords({x:t.clientX,y:t.clientY},!1);i.dispatch({selection:{anchor:e}}),i.focus()}),20)}}));const n=R.define({combine:t=>t[0]}),r=Fr.of([{key:"ArrowDown",run(t){let{main:e}=t.state.selection;return!(!e.empty||e.head0||(document.activeElement.blur(),0))}},{key:"Escape",run:t=>(t.contentDOM.blur(),!0)},{key:"Mod-Enter",run:t=>(l(t),!0)},{key:"Mod-j",run:t=>(h(t),!0)},{key:"Mod-ArrowDown",run:t=>(function(t){let e=t.state.facet(n);e.isHTML&&e.sandbox||(e.wrap.remove(),e.orig.style.display="")}(t),!0)},{key:"Mod-Escape",run:t=>(p(t.state.facet(n).sandbox),!0)}]);function s(t){if("PRE"==t.nodeName){let e=t.getAttribute("data-language");if(/^(javascript|html)$/.test(e))return a(t,e)}}let o=0;function a(e,s){let a=pageYOffset,u=e.getBoundingClientRect();u.top<0&&u.height>500&&(a-=Math.min(-u.top,u.height-500));let d=e.querySelector("a").id,f=window.localStorage&&localStorage.getItem(d)||e.textContent,O=e.parentNode.insertBefore(i("div",{class:"editor-wrap"}),e),m=null;function g(){if(document.activeElement!=w.contentDOM)return;let t=w.dom.getBoundingClientRect();t.bottom<0||t.top>innerHeight?w.contentDOM.blur():m=setTimeout(g,500)}let y=e.getAttribute("data-sandbox"),x={wrap:O,orig:e,isHTML:"html"==s,sandbox:y,meta:e.getAttribute("data-meta")},S=rd(f,s,[r,Dr.domEventHandlers({focus:(e,r)=>{clearTimeout(m),m=setTimeout(g,500),function(e){e.dom.parentNode.querySelector(".editor-controls")||e.dom.parentNode.appendChild(i("div",{class:"editor-controls"},i("button",{onmousedown:t=>{l(e),t.preventDefault()},title:`Run code (${t}Enter)`,"aria-label":"Run code"},"▸"),i("button",{onmousedown:t=>{h(e),t.preventDefault()},title:`Revert code (${t}j)`,"aria-label":"Revert code"},"▫"),i("button",{onmousedown:t=>{p(e.state.facet(n).sandbox),t.preventDefault()},title:`Reset sandbox (${t}Escape)`,"aria-label":"Reset sandbox"},"ø")))}(r)},blur:(t,e)=>{setTimeout((()=>{e.hasFocus||function(t){let e=t.dom.parentNode.querySelector(".editor-controls");e&&e.remove()}(e)}),100)}}),Dr.updateListener.of(yO((t=>{t.docChanged&&window.localStorage&&localStorage.setItem(d,w.state.doc.toString())}),250)),n.of(x)]),w=new Dr({state:S,parent:O}),b=O.appendChild(i("div",{class:"sandbox-output","aria-live":"polite"}));return x.output=new iO.Output(b),"html"!=s||y||(y=x.sandbox="html"+o++,e.setAttribute("data-sandbox",y),c[y]=e),e.style.display="none",window.scrollTo(pageXOffset,a),setTimeout((()=>window.scrollTo(pageXOffset,a)),20),w}function l(t){let e=t.state.facet(n);e.output.clear();let i=t.state.doc.toString();(async function(t,e){if(t=t||"null",u.hasOwnProperty(t))return u[t];let i,n={loadFiles:window.page.load_files};if(c.hasOwnProperty(t)){let r=c[t];n.place=t=>function(t,e){let i,n=e.previousSibling;if(n&&"editor-wrap"==n.className?i=n.getBoundingClientRect().bottom:(i=e.getBoundingClientRect().bottom,a(e,"html"),n=e.previousSibling),n.insertBefore(t,n.childNodes[1]),i<50){let t=n.getBoundingClientRect().bottom;window.scrollBy(0,t-i)}}(t,r),e||(i=r.textContent)}let r=await iO.create(n);return null!=i&&(r.win.document.documentElement.innerHTML=i),u[t]=r,r})(e.sandbox,e.isHTML).then((t=>{e.isHTML?t.setHTML(i,e.output).then((()=>{e.orig.getAttribute("data-focus")&&(t.win.focus(),t.win.document.body.focus())})):t.run(i,e.output).then((i=>{null!=i&&e.meta&&/\bexpr\b/.test(e.meta)&&e.output.empty&&t.out("log",[i])}))}))}function h(t){let e=t.state.facet(n);t.dispatch({selection:{anchor:0},changes:{from:0,to:t.state.doc.length,insert:e.orig.textContent}})}document.getElementsByTagName("article")[0];let c={};{let t=document.getElementsByClassName("snippet");for(let e=0;et[0]});function SO(t){let e;if("string"==typeof t)e=t;else for(let i=1;i<=t.lines;i++){const n=t.line(i).text;if(/\S/.test(n)){e=n;break}}return/^[\s\w]*{let e;return t.docChanged&&null==t.startState.facet(xO).type&&(e=SO(t.newDoc))!=t.startState.facet(Pa).name?{effects:(i=e,nd.reconfigure("html"==i?td():Pp()))}:null;var i}));class bO{constructor(){this.extensions=[Fr.of([{key:"Ctrl-Enter",run:()=>(this.runCode(),!0)},{key:"Cmd-Enter",run:()=>(this.runCode(),!0)}]),wO],this.editor=new Dr({state:rd("","javascript",[this.extensions,xO.of({include:[],mode:null})]),parent:document.querySelector("#editor")}),this.output=new iO.Output(document.querySelector(".sandbox-output")),this.sandbox=null,this.chapters=document.querySelector("#chapters"),chapterData.forEach((t=>{this.chapters.appendChild(kO(t.number,t.number+". "+t.title)),t.exercises.forEach((e=>{e.chapter=t}))})),this.chapters.addEventListener("change",(()=>{this.selectChapter(this.chapters.value),document.location.hash="#"+this.chapters.value})),this.per=document.querySelector("#per_chapter"),this.per.addEventListener("change",(()=>{this.selectContext(this.per.value),document.location.hash="#"+("box"==this.per.value?this.chapters.value:this.per.value)})),this.fileList=document.querySelector("#files"),this.fileInfo=document.querySelector("#fileInfo"),this.runLocally=document.querySelector("#runLocally"),this.localFileList=document.querySelector("#local-files"),document.querySelector("#run").addEventListener("click",(()=>this.runCode())),document.querySelector("#solution").addEventListener("click",(()=>{const t=this.editor.state.facet(xO);this.setEditorState(t.solution,t)})),this.parseFragment()||this.selectChapter(0,"box"),addEventListener("hashchange",(()=>this.parseFragment()))}setEditorState(t,e){this.editor.setState(rd(t,e.type||SO(t),[this.extensions,xO.of(e)]))}selectContext(t){this.output.clear(),this.clearSandbox();const e=QO(this.chapters.value);let i;if("box"==t){let t=this.chapters.value<20||this.chapters.value>21?"Run code here in the context of Chapter "+e.number:"Code from Node.js chapters can't be run in the browser";t="javascript"==SO(e.start_code)?"// "+t:"\x3c!-- "+t+" --\x3e",e.start_code&&(t+="\n\n"+e.start_code),this.setEditorState(t,{include:e.include}),i="box"}else{const n=$O(t,e);if(n.goto)return void(document.location=n.goto);this.setEditorState(n.code,{include:e.include,solution:n.solution,type:n.type}),i="exercise";const r=document.querySelector("#download");r.setAttribute("download","solution"+t+".js"),/\.zip$/.test(n.file)?r.href="../"+n.file:r.href="data:text/plain;charset=UTF-8,"+encodeURIComponent(n.solution)}["box","exercise"].forEach((t=>{document.querySelector("#"+t+"_info").style.display=t==i?"":"none"}))}clearSandbox(){this.sandbox&&(this.sandbox.frame.remove(),this.sandbox=null)}runCode(){this.clearSandbox();const t=this.editor.state.doc.toString(),e=this.editor.state.facet(Pa).name,i=this.editor.state.facet(xO);iO.create({loadFiles:vO(t,i.include)?[]:i.include,emptyPath:"../",place:"html"==e&&function(t){const e=document.querySelector(".sandbox-output");e.parentNode.insertBefore(t,e)}}).then((i=>{this.sandbox=i,this.output.clear(),"html"==e?i.setHTML(t,this.output):i.run(t,this.output)}))}selectChapter(t,e){this.per.textContent="";const i=QO(t);i.exercises.length?(this.per.appendChild(kO("box","Select an exercise")),i.exercises.forEach((t=>{const e=i.number+"."+t.number;this.per.appendChild(kO(e,e+" "+t.name))}))):this.per.appendChild(kO("box","This chapter has no exercises")),this.fileInfo.style.display=this.runLocally.style.display="none",this.fileList.textContent=this.localFileList.textContent="",i.links&&i.links.forEach(((t,e)=>{e||(this.runLocally.style.display=""),PO(this.localFileList,t)})),i.include&&i.include.forEach(((t,e)=>{e||(this.fileInfo.style.display=""),/(^|\/)_/.test(t)||PO(this.fileList,t)})),this.selectContext(e||"box")}parseFragment(){const t=document.location.hash.slice(1);let e,i,n=/^(\d+)(?:\.(\d+.*))?$/.exec(t);if(n&&(e=QO(Number(n[1])),i=e&&n[2]&&$O(t,e),(!e||n[2]&&!i)&&(n=null)),n){const e=i?t:"box";let r=!1;return this.chapters.value!=n[1]&&(this.chapters.value=n[1],this.selectChapter(Number(n[1]),e),r=!0),this.per.value!=e&&(this.per.value=e,r||this.selectContext(e)),!0}}}function vO(t,e){if(!e)return t;const i=/(?:\s|)* - -
- - -

Funciones

- -
- -

La gente piensa que la informática es el arte de los genios, cuando en realidad es al contrario, se trata simplemente de muchas personas construyendo cosas una encima de otra, como un muro de piedrecitas.

- -
Donald Knuth
- -
Ilustración de hojas de helecho con una forma fractal, abejas en el fondo
- -

Las funciones son una de las herramientas más fundamentales en la programación en JavaScript. El concepto de envolver un fragmento de programa en un valor tiene mucha utilidad. Nos proporciona una manera de estructurar programas más grandes, de reducir la repetición, de asociar nombres con subprogramas y de aislar estos subprogramas entre sí.

- -

La aplicación más evidente de las funciones es definir nuevo vocabulario. Crear palabras nuevas en el lenguaje usual no suele quedar bien, pero en programación es indispensable.

- -

Un angloparlante adulto estándar tiene alrededor de 20000 palabras en su vocabulario. Pocos lenguajes de programación vienen con 20000 comandos ya incorporados, y el vocabulario que hay a disposición tiende a estar más precisamente definido —y por tanto a ser menos flexible— que en el caso del lenguaje natural humano. Así pues, tenemos que introducir palabras nuevas para evitar una verbosidad excesiva.

- -

Definir una función

- -

Una definición de función es una asociación cualquiera en la que el valor de la asociación es una función. Por ejemplo, este código define la asociación cuadrado para referirse a una función que produce el cuadrado de un número dado:

- -
const cuadrado = function(x) {
-  return x * x;
-};
-
-console.log(cuadrado(12));
-// → 144
- -

Una función se crea con una expresión que comienza con la palabra clave function. Las funciones tienen un conjunto de parámetros (en este ejemplo, solo x) y un cuerpo, que contiene las declaraciones que se ejecutarán cuando se llame a la función. El cuerpo de una función creada de esta manera siempre debe estar envuelto entre llaves, incluso aunque consista en una única declaración.

- -

Una función puede tener varios parámetros o ninguno en absoluto. En el siguiente ejemplo, hacerRuido no enumera nombre de parámetro alguno, mientras que redondearA (que redondea n al múltiplo más cercano de paso) enumera dos:

- -
const hacerRuido = function() {
-  console.log("¡Cling!");
-};
-
-hacerRuido();
-// → ¡Cling!
-
-const redondearA = function(n, paso) {
-  let resto = n % paso;
-  return n - resto + (resto < paso / 2 ? 0 : paso);
-};
-
-console.log(redondearA(23, 10));
-// → 20
- -

Algunas funciones, como redondearA y cuadrado, producen un valor, y otras no, como hacerRuido, cuyo único resultado es un efecto secundario. Una instrucción return determina el valor que devuelve la función. Cuando el control llega a una instrucción de ese tipo, salta inmediatamente fuera de la función actual y le da el valor devuelto al código que llamó a la función. Si la palabra clave return se usa sin una expresión después de ella, la función devolverá undefined. Las funciones que no tienen ninguna instrucción return, como hacerRuido, también devuelven undefined.

- -

Los parámetros de una función se comportan como asociaciones habituales, pero sus valores iniciales son dados por el llamador de la función, no por el propio código de la función.

- -

Enlaces y ámbitos

- -

Cada asociación tiene un ámbito, que es la parte del programa en la que la asociación es visible. Para las asociaciones definidas fuera de cualquier función, bloque o módulo (ver Capítulo 10), el ámbito es todo el programa —puedes hacer referencia a esas asociaciones donde quieras. Estas asociaciones se llaman asociaciones globales.

- -

Las asociaciones que se crean en la lista de parámetros de una función o que se declaran dentro de ella solo pueden ser referenciadas dentro de esa función, por lo que se conocen como asociaciones locales. Cada vez que se llama a la función, se crean nuevas instancias de estas asociaciones. Esto proporciona cierto aislamiento entre funciones —cada llamada a función actúa en su pequeño mundo (su entorno local) y a menudo se puede entender sin saber mucho sobre lo que está sucediendo en el entorno global.

- -

Las asociaciones declaradas con let y const en realidad son locales al bloque en el que se declaran, por lo que si creas una de ellas dentro de un bucle, el código antes y después del bucle no puede “verla”. En el JavaScript de antes de 2015, solo las funciones creaban nuevos ámbitos, por lo que las asociaciones clásicas, creadas con la palabra clave var, son visibles en todas partes de la función en la que aparecen —o en todo el ámbito global, si no están dentro de una función.

- -

N. del T.: Lo más común al referirse a cualquiera de las entidades definidas mediante las palabras clave let, const y var es utilizar la palabra variable. Sin embargo, todas estas entidades se comportan de manera diferente, tal y como se explica en el texto. Además, el uso de la palabra variable para referirse a una entidad que se define mediante la palabra clave const —y que, por tanto, es constante— puede resultar confuso. Por este motivo, hemos elegido utilizar la palabra asociación para referirnos a estas entidades, aunque vínculo o enlace también serían alternativas adecuadas y podrían utilizarse en ocasiones. Esto se hace para —imitando lo que hace el autor en la obra original mediante el uso de la palabra bind— referirse a estas entidades de una manera unificada que no pueda llevar a confusión.

-
- -
let x = 10;   // global
-if (true) {
-  let y = 20; // local al bloque
-  var z = 30; // también global
-}
- -

Cada ámbito puede “mirar hacia afuera” al ámbito que lo rodea, por lo que x es visible dentro del bloque en el ejemplo. La excepción es cuando múltiples asociaciones tienen el mismo nombre —en ese caso, el código solo puede ver la más interna. Por ejemplo, cuando el código dentro de la función mitad hace referencia a n, está viendo su propia n, no la n global.

- -
const mitad = function(n) {
-  return n / 2;
-};
-
-let n = 10;
-console.log(mitad(100));
-// → 50
-console.log(n);
-// → 10
- -

Ámbito anidado

- -

JavaScript distingue no solo entre asociaciones globales y locales. Se pueden crear bloques y funciones dentro de otros bloques y funciones, produciendo múltiples grados de localidad.

- -

Por ejemplo, esta función —que muestra los ingredientes necesarios para hacer factor platos de hummus— tiene otra función dentro de ella:

- -
const hummus = function(factor) {
-  const ingrediente = function(cantidad, unidad, nombre) {
-    let cantidadDeIngrediente = cantidad * factor;
-    if (cantidadDeIngrediente != 1) {
-      unidad += "s de";
-    } else {
-      unidad += " de";
-    }
-    console.log(`${cantidadDeIngrediente} ${unidad} ${nombre}`);
-  };
-  ingrediente(1, "lata", "garbanzos");
-  ingrediente(0.25, "taza", "tahini");
-  ingrediente(0.25, "taza", "jugo de limón");
-  ingrediente(1, "diente", "ajo");
-  ingrediente(2, "cucharada", "aceite de oliva");
-  ingrediente(0.5, "cucharadita", "comino");
-};
- -

El código dentro de la función ingrediente puede ver la asociación factor de la función exterior, pero sus asociaciones locales, como unidad o cantidadDeIngrediente, no son visibles en la función exterior.

- -

El conjunto de asociaciones visibles dentro de un bloque está determinado por el lugar de ese bloque en el texto del programa. Cada ámbito local también puede ver todos los ámbitos locales que lo contienen, y todos los ámbitos pueden ver el ámbito global. Este enfoque de visibilidad de asociaciones se llama alcance léxico.

- -

Funciones como valores

- -

Generalmente una asociación de función simplemente actúa como un nombre para una parte específica del programa. Esta asociación se define una vez y nunca se cambia. Esto hace que sea fácil confundir la función y su nombre.

- -

Pero son cosas distintas. Un valor de función puede hacer todas las cosas que pueden hacer otros valores: puedes utilizarlo en expresiones arbitrarias, no solamente llamarlo. Es posible almacenar un valor de función en una nueva asociación, pasarlo como argumento a una función, etc. De manera similar, una asociación que contiene una función sigue siendo solo una asociación normal y, si no es constante, se le puede asignar un nuevo valor, así:

- -
let lanzarMisiles = function() {
-  sistemaDeMisil.lanzar("ahora");
-};
-if (modoSeguro) {
-  lanzarMisiles = function() {/* no hacer nada */};
-}
- -

En el Capítulo 5, discutiremos las cosas interesantes que podemos hacer al pasar valores de función a otras funciones.

- -

Notación de declaración

- -

Hay una manera ligeramente más corta de crear una asociación de función. Funciona de una manera un poco distinta cuando se utiliza la palabra clave function al inicio de una declaración:

- -
function cuadrado(x) {
-  return x * x;
-}
- -

Esto es una función declarativa. La declaración define la asociación cuadrado y la apunta a la función dada. Es un poco más fácil de escribir y no requiere un punto y coma después de la función.

- -

Hay una sutileza con esta forma de definir una función.

- -
console.log("El futuro dice:", futuro());
-
-function futuro() {
-  return "Nunca tendrás coches voladores";
-}
- -

El código anterior funciona, incluso aunque la función esté definida debajo del código que la usa. Las de funciones declarativas no forman parte del flujo de control regular de arriba hacia abajo. Conceptualmente se mueven al principio de su ámbito y pueden ser utilizadas por todo el código en ese ámbito. A veces esto es útil porque ofrece la libertad de ordenar el código de una manera que parezca más clara, sin tener que preocuparse por definir todas las funciones antes de que se utilicen.

- -

Funciones flecha

- -

Hay una tercera notación para funciones que tiene un aspecto muy diferente a las otras. En lugar de la palabra clave function, utiliza una flecha (=>) compuesta por un signo igual y un caracter mayor que (no confundir con el operador mayor o igual, que se escribe >=):

- -
const redondearA = (n, paso) => {
-  let resto = n % paso;
-  return n - resto + (resto < paso / 2 ? 0 : paso);
-};
- -

La flecha se escribe después de la lista de parámetros y va seguida por el cuerpo de la función. Expresa algo así como “esta entrada (los parámetros) produce este resultado (el cuerpo)”.

- -

Cuando solo hay un nombre de parámetro se pueden omitir los paréntesis alrededor de la lista de parámetros. Si el cuerpo es una sola expresión, en lugar de un bloque entre llaves, entonces la función devolverá esa expresión. Por ejemplo, estas dos definiciones de exponente hacen lo mismo:

- -
const exponente1 = (x) => { return x * x; };
-const exponente2 = x => x * x;
- -

Cuando una función flecha no tiene ningún parámetro, su lista de parámetros consiste simplemente en unos paréntesis vacíos.

- -
const cuerno = () => {
-  console.log("Honk");
-};
- -

No hay una razón esencial para tener tanto funciones flecha como expresiones function en el lenguaje. Aparte de un detalle menor, que discutiremos en el Capítulo 6, hacen lo mismo. Las funciones flecha se añadieron en 2015, principalmente para hacer posible escribir expresiones de función pequeñas de una manera menos verbosa. Las usaremos a menudo en el Capítulo 5 .

- -

La pila de llamadas

- -

La forma en que el control fluye a través de las funciones es un poco enrevesada. Echemos un vistazo más de cerca. Aquí hay un programa sencillo que realiza algunas llamadas de función:

- -
function saludar(quién) {
-  console.log("Hola " + quién);
-}
-saludar("Harry");
-console.log("Adiós");
- -

Una ejecución de este programa funciona más o menos así: la llamada a saludar hace que el control salte al inicio de esa función (línea 2). La función llama a console.log, que toma el control, hace su trabajo, y luego devuelve el control a la línea 2. Allí, llega al final de la función saludar, por lo que regresa al lugar desde el que se llamó, línea 4. La línea siguiente llama a console.log de nuevo. En cuanto vuelve de ahí, el programa llega a su fin.

- -

Podríamos mostrar el flujo de control esquemáticamente como sigue:

- -
fuera de función
-   en saludar
-        en console.log
-   en saludar
-fuera de función
-   en console.log
-fuera de función
- -

Dado que una función tiene que regresar al lugar desde donde se llamó cuando termina, la computadora debe recordar el contexto desde el cual se realizó la llamada. En un caso, console.log tiene que regresar a la función saludar cuando haya terminado. En el otro caso, regresa al final del programa.

- -

El lugar donde la computadora almacena este contexto es la pila de llamadas. Cada vez que se llama a una función, el contexto actual se apila encima de esta pila. Cuando una función termina, la computadora quita el contexto superior de la pila y lo usa para continuar la ejecución.

- -

Almacenar esta pila requiere espacio en la memoria de la computadora. Cuando la pila crece demasiado, la computadora fallará con un mensaje como “sin espacio en la pila” o “demasiada recursividad”. El siguiente código ilustra esto al hacerle a la computadora una pregunta realmente difícil que causa un vaivén infinito entre dos funciones. O, más bien, sería infinito, si la computadora tuviera una pila infinita. Como no es el caso, nos quedaremos sin espacio o “volaremos la pila”.

- -
function gallina() {
-  return huevo();
-}
-function huevo() {
-  return gallina();
-}
-console.log(gallina() + " salió primero.");
-// → ??
- -

Argumentos Opcionales

- -

El siguiente código está permitido y se ejecuta sin ningún problema:

- -
function cuadrado(x) { return x * x; }
-console.log(cuadrado(4, true, "erizo"));
-// → 16
- -

Hemos definido cuadrado con solo un parámetro. Sin embargo, cuando la llamamos con tres, el lenguaje no se queja. Ignora los argumentos adicionales y calcula el cuadrado del primero.

- -

JavaScript es extremadamente flexible en cuanto al número de argumentos que puedes pasarle a una función. Si pasas demasiados, los extras son ignorados. Si pasas muy pocos, a los parámetros faltantes se les asigna el valor undefined.

- -

El inconveniente de esto es que es posible —incluso probable— que pases accidentalmente una cantidad incorrecta de argumentos a funciones. Y nadie te dirá nada al respecto. La ventaja es que puedes utilizar este comportamiento para permitir que una función sea llamada con diferentes números de argumentos. Por ejemplo, esta función menos intenta imitar al operador - al actuar sobre uno o dos argumentos:

- -
function menos(a, b) {
-  if (b === undefined) return -a;
-  else return a - b;
-}
-
-console.log(menos(10));
-// → -10
-console.log(menos(10, 5));
-// → 5
- -

Si escribes un operador = después de un parámetro, seguido de una expresión, el valor de esa expresión sustituirá al argumento cuando este no se proporcione. Por ejemplo, esta versión de redondearA hace que su segundo argumento sea opcional. Si no lo proporcionas o pasas el valor undefined, por defecto será uno:

- -
function redondearA(n, paso = 1) {
-  let resto = n % paso;
-  return n - resto + (resto < paso / 2 ? 0 : paso);
-};
-
-console.log(redondearA(4.5));
-// → 5
-console.log(redondearA(4.5, 2));
-// → 4
- -

El próximo capítulo presentará una forma en que el cuerpo de una función puede acceder a toda la lista de argumentos que se le han pasado. Esto es útil porque le permite a una función aceptar cualquier número de argumentos. Por ejemplo, console.log lo hace, mostrando todos los valores que se le dan:

- -
console.log("C", "O", 2);
-// → C O 2
- -

Clausura

- -

La capacidad de tratar las funciones como valores, combinada con el hecho de que las asociaciones locales se recrean cada vez que se llama a una función, plantea una pregunta interesante: ¿qué sucede con las asociaciones locales cuando la llamada a la función que las creó ya no está activa? El siguiente código muestra un ejemplo de esto. Define una función, envuelveValor, que crea una asociación local. Luego, devuelve una función que accede a esta asociación local y la devuelve:

- -
function envuelveValor(n) {
-  let local = n;
-  return () => local;
-}
-
-let envoltura1 = envuelveValor(1);
-let envoltura2 = envuelveValor(2);
-console.log(envoltura1());
-// → 1
-console.log(envoltura2());
-// → 2
- -

Esto está permitido y funciona como esperarías: aún puede accederse a ambas instancias de la asociación. Esta situación es una buena demostración de que las asociaciones locales se crean nuevamente para cada llamada, y las diferentes llamadas no afectan las asociaciones locales de las demás llamadas.

- -

Esta característica —poder hacer referencia a una instancia específica de una asociación local en un ámbito superior— se llama clausura. Una función que hace referencia a asociaciones de ámbitos locales a su alrededor se llama una clausura. Este comportamiento no solo te libra de tener que preocuparte por la vida útil de las asociaciones, sino que también permite usar valores de función de formas creativas.

- -

Con un pequeño cambio, podemos convertir el ejemplo anterior en una forma de crear funciones que multiplican por una cantidad arbitraria:

- -
function multiplicador(factor) {
-  return número => número * factor;
-}
-
-let doble = multiplicador(2);
-console.log(doble(5));
-// → 10
- -

La asociación explícita local del ejemplo envuelveValor realmente no es necesaria, ya que un parámetro es en sí mismo una asociación local.

- -

Pensar en programas de esta manera requiere algo de práctica. Un buen modelo mental es pensar en los valores de función como conteniendo tanto el código en su cuerpo como el entorno en el que han sido creados. Cuando se llama, el cuerpo de la función ve el entorno en el que fue creada, no el entorno en el que se le llama.

- -

En el ejemplo anterior, se llama a multiplicador y esta crea un entorno en el que su parámetro factor se asocia a 2. El valor de función que devuelve, que se almacena en doble, recuerda este entorno para que, cuando se llame, multiplique su argumento por 2.

- -

Recursión

- -

Es perfectamente válido que una función se llame a sí misma, siempre y cuando no lo haga tan a menudo que desborde la pila de llamadas. Una función que se llama a sí misma se llama recursiva. La recursión permite escribir funciones con un estilo diferente. Considera, por ejemplo, esta función potencia, que hace lo mismo que el operador ** (potenciación):

- -
function potencia(base, exponente) {
-  if (exponente == 0) {
-    return 1;
-  } else {
-    return base * potencia(base, exponente - 1);
-  }
-}
-
-console.log(potencia(2, 3));
-// → 8
- -

Esto se parece más a la forma en que los matemáticos definen la potenciación y describe el concepto de manera más clara que el bucle que usamos en el Capítulo 2. La función se llama a sí misma varias veces con exponentes cada vez más pequeños para conseguir una multiplicación repetida como la deseada.

- -

Sin embargo, esta implementación tiene un problema: en implementaciones típicas en JavaScript, es aproximadamente tres veces más lenta que una versión que utilice un bucle for. Recorrer un simple bucle suele ser más económico que llamar a una función recursivamente.

- -

El dilema de velocidad versus elegancia es interesante. Se puede ver como una especie de continuo entre la compatibilidad con los humanos y las máquinas. Casi siempre se puede alargar y complicar un programa para hacerlo más rápido. Es cosa del programador encontrar el equilibrio apropiado.

- -

En el caso de la función potencia, una versión poco elegante (con bucles) sigue siendo bastante simple y fácil de leer. No tiene mucho sentido sustituirla por una función recursiva. Sin embargo, a menudo un programa trata con conceptos tan complejos que es útil renunciar a algo de eficiencia para hacer que el programa sea más sencillo.

- -

Preocuparse por la eficiencia puede ser una distracción. Es otro factor que complica el diseño del programa, y, cuando estás haciendo algo que ya es difícil, ese extra en lo que preocuparse puede llegar a ser paralizante.

- -

Por lo tanto, generalmente deberías comenzar escribiendo algo que sea correcto y fácil de entender. Si te preocupa que sea demasiado lento —lo cual suele ser raro, ya que la mayoría del código simplemente no se ejecuta suficientes veces como para consumir una cantidad significativa de tiempo—, puedes medir después y mejorarlo si es necesario.

- -

La recursión no siempre es simplemente una alternativa ineficiente a los bucles. Algunos problemas realmente son más fáciles de resolver con recursión que con bucles. Con frecuencia, se trata de problemas que requieren explorar o procesar varias “ramas”, cada una de las cuales podría ramificarse nuevamente en aún más ramas.

- -

Considera este problema: al comenzar desde el número 1 y repetidamente elegir entre sumar 5 o multiplicar por 3, se puede producir un conjunto infinito de números. ¿Cómo escribirías una función que, dado un número, intente encontrar una secuencia de tales sumas y multiplicaciones que produzcan ese número? Por ejemplo, el número 13 podría alcanzarse al multiplicar por 3 y luego sumar 5 dos veces, mientras que el número 15 no se puede alcanzar de esta manera.

- -

Aquí tienes una solución recursiva:

- -
function encontrarSolucion(objetivo) {
-  function encontrar(actual, historial) {
-    if (actual === objetivo) {
-      return historial;
-    } else if (actual > objetivo) {
-      return null;
-    } else {
-      return encontrar(actual + 5, `(${historial} + 5)`) ??
-             encontrar(actual * 3, `(${historial} * 3)`);
-    }
-  }
-  return encontrar(1, "1");
-}
-
-console.log(encontrarSolucion(24));
-// → (((1 * 3) + 5) * 3)
- -

Ten en cuenta que este programa no necesariamente encuentra la secuencia de operaciones más corta. Se conforma con encontrar cualquier secuencia.

- -

No te preocupes si no ves cómo funciona este código de inmediato. Vamos a inspeccionarlo bien, ya que es un gran ejercicio de pensamiento recursivo.

- -

La función interna encontrar es la que realiza la recursión real. Toma dos argumentos: el número actual y una cadena que registra cómo llegamos a este número. Si encuentra una solución, devuelve una cadena que muestra cómo llegar al objetivo. Si no puede encontrar una solución para este número, devuelve null.

- -

Para hacer esto, la función realiza una de entre tres acciones. Si el número actual es el número objetivo, el historial actual es una forma de alcanzar ese objetivo, por lo que este se devuelve y termina la función. Si el número actual es mayor que el objetivo, no tiene sentido explorar más esta rama porque tanto la suma como la multiplicación solo harán que el número sea más grande, por lo que la función devuelve null. Finalmente, si aún estamos por debajo del número objetivo, la función prueba ambas rutas posibles que parten del número actual llamándose a sí misma dos veces, una vez para la suma y otra vez para la multiplicación. Si la primera llamada devuelve algo que no es null, entonces este es el resultado que se devuelve. De lo contrario, se devuelve la segunda llamada, independientemente de si produce una cadena o null.

- -

Para entender mejor cómo esta función produce el efecto que estamos buscando, veamos todas las llamadas a encontrar que se hacen al buscar una solución para el número 13:

- -
encontrar(1, "1")
-  encontrar(6, "(1 + 5)")
-    encontrar(11, "((1 + 5) + 5)")
-      encontrar(16, "(((1 + 5) + 5) + 5)")
-        demasiado grande
-      encontrar(33, "(((1 + 5) + 5) * 3)")
-        demasiado grande
-    encontrar(18, "((1 + 5) * 3)")
-      demasiado grande
-  encontrar(3, "(1 * 3)")
-    encontrar(8, "((1 * 3) + 5)")
-      encontrar(13, "(((1 * 3) + 5) + 5)")
-        ¡encontrado!
- -

La sangría indica la profundidad de la pila de llamadas. La primera vez que se llama a encontrar, la función comienza llamándose a sí misma para explorar la solución que comienza con (1 + 5). Esa llamada seguirá recursivamente para explorar cada solución a continuación que produzca un número menor o igual al número objetivo. Como no encuentra uno que alcance el objetivo, devuelve null en la primera llamada que se hace dentro de encontrar. Allí, el operador ?? hace que ocurra la llamada que explora la opción (1 * 3). Esta búsqueda es más exitosa: su primera llamada recursiva, a través de otra llamada recursiva más, alcanza el número objetivo. Esa llamada más interna devuelve una cadena, y cada uno de los operadores ?? en las llamadas intermedias pasa esa cadena, devolviendo en última instancia la solución.

- -

Crecimiento de funciones

- -

Hay dos formas más o menos naturales de que las funciones aparezcan en los programas.

- -

La primera ocurre cuando te encuentras escribiendo código muy parecido varias veces. Preferirías no hacer eso, ya que tener más código significa más posibilidades de cometer errores y más material para leer para aquellas personas que intenten entender el programa. Por lo tanto, agarras esta funcionalidad repetida, encuentras un buen nombre para ella y la colocas dentro de una función.

- -

La segunda forma es que darte cuenta de que necesitas alguna funcionalidad que aún no has escrito y que suena como si mereciera su propia función. Primero nombras la función y luego escribes su cuerpo. Incluso podrías comenzar a escribir código que use la función antes de definir la función en sí.

- -

Cuán difícil es encontrar un buen nombre para una función es una buena indicación de lo claro que es el concepto que estás tratando de encapsular en ella. Vamos a ver de un ejemplo.

- -

Queremos escribir un programa que imprima dos números: el número de vacas y de pollos en una granja, con las palabras Vacas y Pollos después de ellos y ceros de rrelleno antes de ambos números para que siempre se trate de números con tres dígitos:

- -
007 Vacas
-011 Pollos
- -

Esto está pidiendo una función con dos argumentos: el número de vacas y el número de pollos. ¡Vamos a programar!

- -
function imprimirInventarioGranja(vacas, pollos) {
-  let cadenaVaca = String(vacas);
-  while (cadenaVaca.length < 3) {
-    cadenaVaca = "0" + cadenaVaca;
-  }
-  console.log(`${cadenaVaca} Vacas`);
-  let cadenaPollo = String(pollos);
-  while (cadenaPollo.length < 3) {
-    cadenaPollo = "0" + cadenaPollo;
-  }
-  console.log(`${cadenaPollo} Pollos`);
-}
-imprimirInventarioGranja(7, 11);
- -

Escribir .length después de una expresión de cadena nos dará la longitud de esa cadena. Por lo tanto, los bucles while siguen añadiendo ceros delante de las cadenas de números hasta que estas tengan al menos tres caracteres de longitud.

- -

¡Misión cumplida! Pero justo cuando estamos a punto de enviarle a la granjera el código (junto con una factura considerable), ella llama y nos dice que también ha comenzado a criar cerdos, ¿podríamos extender el software para imprimir también los cerdos?

- -

¡Claro que podemos! Pero justo cuando estamos en el proceso de copiar y pegar esas cuatro líneas una vez más, nos detenemos y reconsideramos. Tiene que haber una mejor manera. Aquí un primer intento:

- -
function imprimirConRellenoYEtiqueta(número, etiqueta) {
-  let cadenaNumero = String(número);
-  while (cadenaNumero.length < 3) {
-    cadenaNumero = "0" + cadenaNumero;
-  }
-  console.log(`${cadenaNumero} ${etiqueta}`);
-}
-
-function imprimirInventarioGranja(vacas, pollos, cerdos) {
-  imprimirConRellenoYEtiqueta(vacas, "Vacas");
-  imprimirConRellenoYEtiqueta(pollos, "Pollos");
-  imprimirConRellenoYEtiqueta(cerdos, "Cerdos");
-}
-
-imprimirInventarioGranja(7, 11, 3);
- -

¡Funciona! Pero ese nombre, imprimirConRellenoYEtiqueta, es un poco raro. Confluye tres cosas: imprimir, rellenar con ceros y añadir una etiqueta, en una sola función.

- -

En lugar de extraer completamente la parte repetida de nuestro programa, intentemos sacar un solo concepto:

- -
function rellenarConCeros(número, ancho) {
-  let cadena = String(número);
-  while (cadena.length < ancho) {
-    cadena = "0" + cadena;
-  }
-  return cadena;
-}
-
-function imprimirInventarioGranja(vacas, pollos, cerdos) {
-  console.log(`${rellenarConCeros(vacas, 3)} Vacas`);
-  console.log(`${rellenarConCeros(pollos, 3)} Pollos`);
-  console.log(`${rellenarConCeros(cerdos, 3)} Cerdos`);
-}
-
-imprimirInventarioGranja(7, 16, 3);
- -

Una función con un nombre claro y obvio como rellenarConCeros hace que sea más fácil para alguien que lee el código entender qué es lo que este hace. Además, una función así es útil en más situaciones aparte de este programa específico. Por ejemplo, podrías usarla para ayudar a imprimir tablas de números alineadas correctamente.

- -

¿Cómo de inteligente y versátil debería ser nuestra función? Podríamos escribir cualquier cosa, desde una función terriblemente simple que solo puede rellenar un número para que tenga tres caracteres de ancho hasta un sistema complejo de formato de números general que maneje números fraccionarios, números negativos, alineación de puntos decimales, relleno con diferentes caracteres y más.

- -

Un principio útil es abstenerse de agregar ingenio a menos que estés absolutamente seguro de que lo vas a necesitar. Puede ser tentador escribir “frameworks” generales para cada trozo de funcionalidad que te encuentres. No caigas en la tentación. Así no lograrás acabar ninguna tarea —estarás demasiado ocupado escribiendo código que nunca usarás.

- -

Funciones y efectos secundarios

- -

Las funciones pueden clasificarse más o menos en aquellas a las que se llama por sus efectos secundarios y aquellas a las que se llama por su valor de retorno (aunque también es posible tener efectos secundarios y devolver un valor).

- -

La primera función auxiliar en el ejemplo de la granja, imprimirConRellenoYEtiqueta, se llama por su efecto secundario: imprimir una línea. La segunda versión, rellenarConCeros, se llama por su valor de retorno. No es casualidad que la segunda sea útil en más situaciones que la primera. Las funciones que crean valores son más fáciles de combinar de nuevas formas que las funciones que realizan efectos secundarios directamente.

- -

Una función pura es un tipo específico de función productora de valores que no solo no tiene efectos secundarios, sino que tampoco depende de efectos secundarios de otro código —por ejemplo, no lee asociaciones globales cuyo valor podría cambiar. Una función pura tiene la agradable propiedad de que, al llamarla con los mismos argumentos, siempre produce el mismo valor (y no hace nada más). Una llamada a una tal función puede sustituirse por su valor de retorno sin cambiar el significado del código. Cuando no estás seguro de si una función pura está funcionando correctamente, puedes probarla llamándola y saber que, si funciona en ese contexto, funcionará en cualquier otro contexto. Las funciones no puras tienden a requerir más andamiaje para probarlas.

- -

Aún así, no hay que sentirse mal por escribir funciones que no son puras. Los efectos secundarios a menudo son útiles. No hay forma de escribir una versión pura de console.log, por ejemplo, y es bueno tener console.log. Algunas operaciones también son más fáciles de expresar de manera eficiente cuando usamos efectos secundarios.

- -

Resumen

- -

Este capítulo te enseña cómo escribir tus propias funciones. La palabra clave function, cuando se usa como expresión, puede crear un valor de función. Cuando se usa como una declaración, puede usarse para declarar un enlace y darle una función como su valor. Las funciones flecha son otra forma de crear funciones.

- -
// Definir f para contener un valor de función
-const f = function(a) {
-  console.log(a + 2);
-};
-
-// Declarar g como una función
-function g(a, b) {
-  return a * b * 3.5;
-}
-
-// Un valor de función menos verboso
-let h = a => a % 3;
- -

Una parte clave del estudio de las funciones es comprender los ámbitos (scopes). Cada bloque crea un nuevo ámbito. Los parámetros y las enlaces declarados en un ámbito dado son locales y no son visibles desde el exterior. Las asociaciones declaradas con var se comportan de manera diferente: terminan en el ámbito de la función más cercana o en el ámbito global.

- -

Separar las tareas que realiza tu programa en diferentes funciones es útil. No tendrás que repetirte tanto, y las funciones pueden ayudar a organizar un programa agrupando el código en trozos que hacen cosas específicas.

- -

Ejercicios

- -

Mínimo

- -

El capítulo previo presentó la función estándar Math.min que devuelve su menor argumento. Ahora podemos escribir una función como esa nosotros mismos. Define la función min que toma dos argumentos y devuelve su mínimo.

- -
// Tu código aquí.
-
-console.log(min(0, 10));
-// → 0
-console.log(min(0, -10));
-// → -10
- -
Mostrar pistas...
- -

Si tienes problemas para colocar las llaves y paréntesis en el lugar correcto para obtener una definición de función válida, comienza copiando uno de los ejemplos de este capítulo y modifícalo.

- -

Una función puede contener múltiples declaraciones return.

- -
- -

Recursión

- -

Hemos visto que podemos usar % (el operador de resto) para verificar si un número es par o impar al usar % 2 para ver si es divisible por dos. Aquí hay otra forma de definir si un número entero positivo es par o impar:

- -
    - -
  • - -

    El cero es par.

  • - -
  • - -

    El uno es impar.

  • - -
  • - -

    Para cualquier otro número N, su paridad es la misma que la de N - 2.

- -

Define una función recursiva esPar que corresponda a esta descripción. La función debe aceptar un solo parámetro (un número entero positivo) y devolver un booleano.

- -

Pruébalo con 50 y 75. Observa cómo se comporta con -1. ¿Por qué? ¿Puedes pensar en una forma de solucionarlo?

- -
// Tu código aquí.
-
-console.log(esPar(50));
-// → true
-console.log(esPar(75));
-// → false
-console.log(esPar(-1));
-// → ??
- -
Mostrar pistas...
- -

Es probable que tu función se parezca en cierta medida a la función interna encontrar en el ejemplo recursivo encontrarSolucion ejemplo de este capítulo, con una cadena if/else if/else que prueba cuál de los tres casos aplica. El else final, correspondiente al tercer caso, realiza la llamada recursiva. Cada una de las ramas debe contener una declaración return o de algún modo, asegurarse de que se devuelva un valor específico.

- -

Cuando se le da un número negativo, la función se llamará recursivamente una y otra vez, pasándose a sí misma un número cada vez más negativo, alejándose así más y más de devolver un resultado. Al final, el programa se quedará sin espacio en la pila cortará.

- -
- -

Contando minuciosamente

- -

Puedes obtener el N-ésimo caracter, o letra, de una cadena escribiendo [N] después de la cadena (por ejemplo, cadena[2]). El valor resultante será una cadena que contiene solo un carácter (por ejemplo, "b"). El primer carácter tiene la posición 0, lo que hace que el último se encuentre en la posición cadena.length - 1. En otras palabras, una cadena de dos caracteres tiene longitud 2, y sus caracteres tienen posiciones 0 y 1.

- -

Escribe una función contarBs que tome una cadena como único argumento y devuelva un número que indique cuántos caracteres B en mayúscula hay en la cadena.

- -

A continuación, escribe una función llamada contarCaracter que se comporte como contarBs, excepto que toma un segundo argumento que indica el carácter que se va a contar (en lugar de contar solo caracteres B en mayúscula). Reescribe contarBs para hacer uso de esta nueva función.

- -
// Tu código aquí.
-
-console.log(contarBs("BOB"));
-// → 2
-console.log(contarCaracter("kakkerlak", "k"));
-// → 4
- -
Mostrar pistas...
- -

Tu función necesita un bucle que mire cada carácter en la cadena. Puede recorrer un índice desde cero hasta uno menos que su longitud (< string.length). Si el caracter en la posición actual es el mismo que el que la función está buscando, agrega 1 a una variable contadora. Una vez que el bucle ha terminado, el contador puede ser devuelto.

- -

Ten cuidado de que todas las asociaciones utilizadas en la función sean locales a la función, declarándolas correctamente con la palabra clave let o const.

- -
-
- - From aad2c2bad1f36b6889c2b51a4cd6288d0642aff0 Mon Sep 17 00:00:00 2001 From: ckdvk Date: Mon, 10 Feb 2025 02:45:48 +0800 Subject: [PATCH 09/36] =?UTF-8?q?modificaciones=20en=20el=20c=C3=B3digo=20?= =?UTF-8?q?para=20funci=C3=B3n=20de=20notas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html/03_functions.html | 567 +++++++++++++++++++++++++++++++++++++++++ src/transform.mjs | 253 +++++++++--------- 2 files changed, 694 insertions(+), 126 deletions(-) diff --git a/html/03_functions.html b/html/03_functions.html index e69de29b..9e81844f 100644 --- a/html/03_functions.html +++ b/html/03_functions.html @@ -0,0 +1,567 @@ +<<<<<<< HEAD +======= + + + + + Funciones :: Eloquent JavaScript + + +
+ + +

Funciones

+ +
+ +

La gente piensa que la informática es el arte de los genios, cuando en realidad es al contrario, se trata simplemente de muchas personas construyendo cosas una encima de otra, como un muro de piedrecitas.

+ +
Donald Knuth
+ +
Ilustración de hojas de helecho con una forma fractal, abejas en el fondo
+ +

Las funciones son una de las herramientas más fundamentales en la programación en JavaScript. El concepto de envolver un fragmento de programa en un valor tiene mucha utilidad. Nos proporciona una manera de estructurar programas más grandes, de reducir la repetición, de asociar nombres con subprogramas y de aislar estos subprogramas entre sí.

+ +

La aplicación más evidente de las funciones es definir nuevo vocabulario. Crear palabras nuevas en el lenguaje usual no suele quedar bien, pero en programación es indispensable.

+ +

Un angloparlante adulto estándar tiene alrededor de 20000 palabras en su vocabulario. Pocos lenguajes de programación vienen con 20000 comandos ya incorporados, y el vocabulario que hay a disposición tiende a estar más precisamente definido —y por tanto a ser menos flexible— que en el caso del lenguaje natural humano. Así pues, tenemos que introducir palabras nuevas para evitar una verbosidad excesiva.

+ +

Definir una función

+ +

Una definición de función es una asociación cualquiera en la que el valor de la asociación es una función. Por ejemplo, este código define la asociación cuadrado para referirse a una función que produce el cuadrado de un número dado:

+ +
const cuadrado = function(x) {
+  return x * x;
+};
+
+console.log(cuadrado(12));
+// → 144
+ +

Una función se crea con una expresión que comienza con la palabra clave function. Las funciones tienen un conjunto de parámetros (en este ejemplo, solo x) y un cuerpo, que contiene las declaraciones que se ejecutarán cuando se llame a la función. El cuerpo de una función creada de esta manera siempre debe estar envuelto entre llaves, incluso aunque consista en una única declaración.

+ +

Una función puede tener varios parámetros o ninguno en absoluto. En el siguiente ejemplo, hacerRuido no enumera nombre de parámetro alguno, mientras que redondearA (que redondea n al múltiplo más cercano de paso) enumera dos:

+ +
const hacerRuido = function() {
+  console.log("¡Cling!");
+};
+
+hacerRuido();
+// → ¡Cling!
+
+const redondearA = function(n, paso) {
+  let resto = n % paso;
+  return n - resto + (resto < paso / 2 ? 0 : paso);
+};
+
+console.log(redondearA(23, 10));
+// → 20
+ +

Algunas funciones, como redondearA y cuadrado, producen un valor, y otras no, como hacerRuido, cuyo único resultado es un efecto secundario. Una instrucción return determina el valor que devuelve la función. Cuando el control llega a una instrucción de ese tipo, salta inmediatamente fuera de la función actual y le da el valor devuelto al código que llamó a la función. Si la palabra clave return se usa sin una expresión después de ella, la función devolverá undefined. Las funciones que no tienen ninguna instrucción return, como hacerRuido, también devuelven undefined.

+ +

Los parámetros de una función se comportan como asociaciones habituales, pero sus valores iniciales son dados por el llamador de la función, no por el propio código de la función.

+ +

Enlaces y ámbitos

+ +

Cada asociación tiene un ámbito, que es la parte del programa en la que la asociación es visible. Para las asociaciones definidas fuera de cualquier función, bloque o módulo (ver Capítulo 10), el ámbito es todo el programa —puedes hacer referencia a esas asociaciones donde quieras. Estas asociaciones se llaman asociaciones globales.

+ +

Las asociaciones que se crean en la lista de parámetros de una función o que se declaran dentro de ella solo pueden ser referenciadas dentro de esa función, por lo que se conocen como asociaciones locales. Cada vez que se llama a la función, se crean nuevas instancias de estas asociaciones. Esto proporciona cierto aislamiento entre funciones —cada llamada a función actúa en su pequeño mundo (su entorno local) y a menudo se puede entender sin saber mucho sobre lo que está sucediendo en el entorno global.

+ +

Las asociaciones declaradas con let y const en realidad son locales al bloque en el que se declaran, por lo que si creas una de ellas dentro de un bucle, el código antes y después del bucle no puede “verla”. En el JavaScript de antes de 2015, solo las funciones creaban nuevos ámbitos, por lo que las asociaciones clásicas, creadas con la palabra clave var, son visibles en todas partes de la función en la que aparecen —o en todo el ámbito global, si no están dentro de una función.

+ +

N. del T.: Lo más común al referirse a cualquiera de las entidades definidas mediante las palabras clave let, const y var es utilizar la palabra variable. Sin embargo, todas estas entidades se comportan de manera diferente, tal y como se explica en el texto. Además, el uso de la palabra variable para referirse a una entidad que se define mediante la palabra clave const —y que, por tanto, es constante— puede resultar confuso. Por este motivo, hemos elegido utilizar la palabra asociación para referirnos a estas entidades, aunque vínculo o enlace también serían alternativas adecuadas y podrían utilizarse en ocasiones. Esto se hace para —imitando lo que hace el autor en la obra original mediante el uso de la palabra bind— referirse a estas entidades de una manera unificada que no pueda llevar a confusión.

+
+ +
let x = 10;   // global
+if (true) {
+  let y = 20; // local al bloque
+  var z = 30; // también global
+}
+ +

Cada ámbito puede “mirar hacia afuera” al ámbito que lo rodea, por lo que x es visible dentro del bloque en el ejemplo. La excepción es cuando múltiples asociaciones tienen el mismo nombre —en ese caso, el código solo puede ver la más interna. Por ejemplo, cuando el código dentro de la función mitad hace referencia a n, está viendo su propia n, no la n global.

+ +
const mitad = function(n) {
+  return n / 2;
+};
+
+let n = 10;
+console.log(mitad(100));
+// → 50
+console.log(n);
+// → 10
+ +

Ámbito anidado

+ +

JavaScript distingue no solo entre asociaciones globales y locales. Se pueden crear bloques y funciones dentro de otros bloques y funciones, produciendo múltiples grados de localidad.

+ +

Por ejemplo, esta función —que muestra los ingredientes necesarios para hacer factor platos de hummus— tiene otra función dentro de ella:

+ +
const hummus = function(factor) {
+  const ingrediente = function(cantidad, unidad, nombre) {
+    let cantidadDeIngrediente = cantidad * factor;
+    if (cantidadDeIngrediente != 1) {
+      unidad += "s de";
+    } else {
+      unidad += " de";
+    }
+    console.log(`${cantidadDeIngrediente} ${unidad} ${nombre}`);
+  };
+  ingrediente(1, "lata", "garbanzos");
+  ingrediente(0.25, "taza", "tahini");
+  ingrediente(0.25, "taza", "jugo de limón");
+  ingrediente(1, "diente", "ajo");
+  ingrediente(2, "cucharada", "aceite de oliva");
+  ingrediente(0.5, "cucharadita", "comino");
+};
+ +

El código dentro de la función ingrediente puede ver la asociación factor de la función exterior, pero sus asociaciones locales, como unidad o cantidadDeIngrediente, no son visibles en la función exterior.

+ +

El conjunto de asociaciones visibles dentro de un bloque está determinado por el lugar de ese bloque en el texto del programa. Cada ámbito local también puede ver todos los ámbitos locales que lo contienen, y todos los ámbitos pueden ver el ámbito global. Este enfoque de visibilidad de asociaciones se llama alcance léxico.

+ +

Funciones como valores

+ +

Generalmente una asociación de función simplemente actúa como un nombre para una parte específica del programa. Esta asociación se define una vez y nunca se cambia. Esto hace que sea fácil confundir la función y su nombre.

+ +

Pero son cosas distintas. Un valor de función puede hacer todas las cosas que pueden hacer otros valores: puedes utilizarlo en expresiones arbitrarias, no solamente llamarlo. Es posible almacenar un valor de función en una nueva asociación, pasarlo como argumento a una función, etc. De manera similar, una asociación que contiene una función sigue siendo solo una asociación normal y, si no es constante, se le puede asignar un nuevo valor, así:

+ +
let lanzarMisiles = function() {
+  sistemaDeMisil.lanzar("ahora");
+};
+if (modoSeguro) {
+  lanzarMisiles = function() {/* no hacer nada */};
+}
+ +

En el Capítulo 5, discutiremos las cosas interesantes que podemos hacer al pasar valores de función a otras funciones.

+ +

Notación de declaración

+ +

Hay una manera ligeramente más corta de crear una asociación de función. Funciona de una manera un poco distinta cuando se utiliza la palabra clave function al inicio de una declaración:

+ +
function cuadrado(x) {
+  return x * x;
+}
+ +

Esto es una función declarativa. La declaración define la asociación cuadrado y la apunta a la función dada. Es un poco más fácil de escribir y no requiere un punto y coma después de la función.

+ +

Hay una sutileza con esta forma de definir una función.

+ +
console.log("El futuro dice:", futuro());
+
+function futuro() {
+  return "Nunca tendrás coches voladores";
+}
+ +

El código anterior funciona, incluso aunque la función esté definida debajo del código que la usa. Las de funciones declarativas no forman parte del flujo de control regular de arriba hacia abajo. Conceptualmente se mueven al principio de su ámbito y pueden ser utilizadas por todo el código en ese ámbito. A veces esto es útil porque ofrece la libertad de ordenar el código de una manera que parezca más clara, sin tener que preocuparse por definir todas las funciones antes de que se utilicen.

+ +

Funciones flecha

+ +

Hay una tercera notación para funciones que tiene un aspecto muy diferente a las otras. En lugar de la palabra clave function, utiliza una flecha (=>) compuesta por un signo igual y un caracter mayor que (no confundir con el operador mayor o igual, que se escribe >=):

+ +
const redondearA = (n, paso) => {
+  let resto = n % paso;
+  return n - resto + (resto < paso / 2 ? 0 : paso);
+};
+ +

La flecha se escribe después de la lista de parámetros y va seguida por el cuerpo de la función. Expresa algo así como “esta entrada (los parámetros) produce este resultado (el cuerpo)”.

+ +

Cuando solo hay un nombre de parámetro se pueden omitir los paréntesis alrededor de la lista de parámetros. Si el cuerpo es una sola expresión, en lugar de un bloque entre llaves, entonces la función devolverá esa expresión. Por ejemplo, estas dos definiciones de exponente hacen lo mismo:

+ +
const exponente1 = (x) => { return x * x; };
+const exponente2 = x => x * x;
+ +

Cuando una función flecha no tiene ningún parámetro, su lista de parámetros consiste simplemente en unos paréntesis vacíos.

+ +
const cuerno = () => {
+  console.log("Honk");
+};
+ +

No hay una razón esencial para tener tanto funciones flecha como expresiones function en el lenguaje. Aparte de un detalle menor, que discutiremos en el Capítulo 6, hacen lo mismo. Las funciones flecha se añadieron en 2015, principalmente para hacer posible escribir expresiones de función pequeñas de una manera menos verbosa. Las usaremos a menudo en el Capítulo 5 .

+ +

La pila de llamadas

+ +

La forma en que el control fluye a través de las funciones es un poco enrevesada. Echemos un vistazo más de cerca. Aquí hay un programa sencillo que realiza algunas llamadas de función:

+ +
function saludar(quién) {
+  console.log("Hola " + quién);
+}
+saludar("Harry");
+console.log("Adiós");
+ +

Una ejecución de este programa funciona más o menos así: la llamada a saludar hace que el control salte al inicio de esa función (línea 2). La función llama a console.log, que toma el control, hace su trabajo, y luego devuelve el control a la línea 2. Allí, llega al final de la función saludar, por lo que regresa al lugar desde el que se llamó, línea 4. La línea siguiente llama a console.log de nuevo. En cuanto vuelve de ahí, el programa llega a su fin.

+ +

Podríamos mostrar el flujo de control esquemáticamente como sigue:

+ +
fuera de función
+   en saludar
+        en console.log
+   en saludar
+fuera de función
+   en console.log
+fuera de función
+ +

Dado que una función tiene que regresar al lugar desde donde se llamó cuando termina, la computadora debe recordar el contexto desde el cual se realizó la llamada. En un caso, console.log tiene que regresar a la función saludar cuando haya terminado. En el otro caso, regresa al final del programa.

+ +

El lugar donde la computadora almacena este contexto es la pila de llamadas. Cada vez que se llama a una función, el contexto actual se apila encima de esta pila. Cuando una función termina, la computadora quita el contexto superior de la pila y lo usa para continuar la ejecución.

+ +

Almacenar esta pila requiere espacio en la memoria de la computadora. Cuando la pila crece demasiado, la computadora fallará con un mensaje como “sin espacio en la pila” o “demasiada recursividad”. El siguiente código ilustra esto al hacerle a la computadora una pregunta realmente difícil que causa un vaivén infinito entre dos funciones. O, más bien, sería infinito, si la computadora tuviera una pila infinita. Como no es el caso, nos quedaremos sin espacio o “volaremos la pila”.

+ +
function gallina() {
+  return huevo();
+}
+function huevo() {
+  return gallina();
+}
+console.log(gallina() + " salió primero.");
+// → ??
+ +

Argumentos Opcionales

+ +

El siguiente código está permitido y se ejecuta sin ningún problema:

+ +
function cuadrado(x) { return x * x; }
+console.log(cuadrado(4, true, "erizo"));
+// → 16
+ +

Hemos definido cuadrado con solo un parámetro. Sin embargo, cuando la llamamos con tres, el lenguaje no se queja. Ignora los argumentos adicionales y calcula el cuadrado del primero.

+ +

JavaScript es extremadamente flexible en cuanto al número de argumentos que puedes pasarle a una función. Si pasas demasiados, los extras son ignorados. Si pasas muy pocos, a los parámetros faltantes se les asigna el valor undefined.

+ +

El inconveniente de esto es que es posible —incluso probable— que pases accidentalmente una cantidad incorrecta de argumentos a funciones. Y nadie te dirá nada al respecto. La ventaja es que puedes utilizar este comportamiento para permitir que una función sea llamada con diferentes números de argumentos. Por ejemplo, esta función menos intenta imitar al operador - al actuar sobre uno o dos argumentos:

+ +
function menos(a, b) {
+  if (b === undefined) return -a;
+  else return a - b;
+}
+
+console.log(menos(10));
+// → -10
+console.log(menos(10, 5));
+// → 5
+ +

Si escribes un operador = después de un parámetro, seguido de una expresión, el valor de esa expresión sustituirá al argumento cuando este no se proporcione. Por ejemplo, esta versión de redondearA hace que su segundo argumento sea opcional. Si no lo proporcionas o pasas el valor undefined, por defecto será uno:

+ +
function redondearA(n, paso = 1) {
+  let resto = n % paso;
+  return n - resto + (resto < paso / 2 ? 0 : paso);
+};
+
+console.log(redondearA(4.5));
+// → 5
+console.log(redondearA(4.5, 2));
+// → 4
+ +

El próximo capítulo presentará una forma en que el cuerpo de una función puede acceder a toda la lista de argumentos que se le han pasado. Esto es útil porque le permite a una función aceptar cualquier número de argumentos. Por ejemplo, console.log lo hace, mostrando todos los valores que se le dan:

+ +
console.log("C", "O", 2);
+// → C O 2
+ +

Clausura

+ +

La capacidad de tratar las funciones como valores, combinada con el hecho de que las asociaciones locales se recrean cada vez que se llama a una función, plantea una pregunta interesante: ¿qué sucede con las asociaciones locales cuando la llamada a la función que las creó ya no está activa? El siguiente código muestra un ejemplo de esto. Define una función, envuelveValor, que crea una asociación local. Luego, devuelve una función que accede a esta asociación local y la devuelve:

+ +
function envuelveValor(n) {
+  let local = n;
+  return () => local;
+}
+
+let envoltura1 = envuelveValor(1);
+let envoltura2 = envuelveValor(2);
+console.log(envoltura1());
+// → 1
+console.log(envoltura2());
+// → 2
+ +

Esto está permitido y funciona como esperarías: aún puede accederse a ambas instancias de la asociación. Esta situación es una buena demostración de que las asociaciones locales se crean nuevamente para cada llamada, y las diferentes llamadas no afectan las asociaciones locales de las demás llamadas.

+ +

Esta característica —poder hacer referencia a una instancia específica de una asociación local en un ámbito superior— se llama clausura. Una función que hace referencia a asociaciones de ámbitos locales a su alrededor se llama una clausura. Este comportamiento no solo te libra de tener que preocuparte por la vida útil de las asociaciones, sino que también permite usar valores de función de formas creativas.

+ +

Con un pequeño cambio, podemos convertir el ejemplo anterior en una forma de crear funciones que multiplican por una cantidad arbitraria:

+ +
function multiplicador(factor) {
+  return número => número * factor;
+}
+
+let doble = multiplicador(2);
+console.log(doble(5));
+// → 10
+ +

La asociación explícita local del ejemplo envuelveValor realmente no es necesaria, ya que un parámetro es en sí mismo una asociación local.

+ +

Pensar en programas de esta manera requiere algo de práctica. Un buen modelo mental es pensar en los valores de función como conteniendo tanto el código en su cuerpo como el entorno en el que han sido creados. Cuando se llama, el cuerpo de la función ve el entorno en el que fue creada, no el entorno en el que se le llama.

+ +

En el ejemplo anterior, se llama a multiplicador y esta crea un entorno en el que su parámetro factor se asocia a 2. El valor de función que devuelve, que se almacena en doble, recuerda este entorno para que, cuando se llame, multiplique su argumento por 2.

+ +

Recursión

+ +

Es perfectamente válido que una función se llame a sí misma, siempre y cuando no lo haga tan a menudo que desborde la pila de llamadas. Una función que se llama a sí misma se llama recursiva. La recursión permite escribir funciones con un estilo diferente. Considera, por ejemplo, esta función potencia, que hace lo mismo que el operador ** (potenciación):

+ +
function potencia(base, exponente) {
+  if (exponente == 0) {
+    return 1;
+  } else {
+    return base * potencia(base, exponente - 1);
+  }
+}
+
+console.log(potencia(2, 3));
+// → 8
+ +

Esto se parece más a la forma en que los matemáticos definen la potenciación y describe el concepto de manera más clara que el bucle que usamos en el Capítulo 2. La función se llama a sí misma varias veces con exponentes cada vez más pequeños para conseguir una multiplicación repetida como la deseada.

+ +

Sin embargo, esta implementación tiene un problema: en implementaciones típicas en JavaScript, es aproximadamente tres veces más lenta que una versión que utilice un bucle for. Recorrer un simple bucle suele ser más económico que llamar a una función recursivamente.

+ +

El dilema de velocidad versus elegancia es interesante. Se puede ver como una especie de continuo entre la compatibilidad con los humanos y las máquinas. Casi siempre se puede alargar y complicar un programa para hacerlo más rápido. Es cosa del programador encontrar el equilibrio apropiado.

+ +

En el caso de la función potencia, una versión poco elegante (con bucles) sigue siendo bastante simple y fácil de leer. No tiene mucho sentido sustituirla por una función recursiva. Sin embargo, a menudo un programa trata con conceptos tan complejos que es útil renunciar a algo de eficiencia para hacer que el programa sea más sencillo.

+ +

Preocuparse por la eficiencia puede ser una distracción. Es otro factor que complica el diseño del programa, y, cuando estás haciendo algo que ya es difícil, ese extra en lo que preocuparse puede llegar a ser paralizante.

+ +

Por lo tanto, generalmente deberías comenzar escribiendo algo que sea correcto y fácil de entender. Si te preocupa que sea demasiado lento —lo cual suele ser raro, ya que la mayoría del código simplemente no se ejecuta suficientes veces como para consumir una cantidad significativa de tiempo—, puedes medir después y mejorarlo si es necesario.

+ +

La recursión no siempre es simplemente una alternativa ineficiente a los bucles. Algunos problemas realmente son más fáciles de resolver con recursión que con bucles. Con frecuencia, se trata de problemas que requieren explorar o procesar varias “ramas”, cada una de las cuales podría ramificarse nuevamente en aún más ramas.

+ +

Considera este problema: al comenzar desde el número 1 y repetidamente elegir entre sumar 5 o multiplicar por 3, se puede producir un conjunto infinito de números. ¿Cómo escribirías una función que, dado un número, intente encontrar una secuencia de tales sumas y multiplicaciones que produzcan ese número? Por ejemplo, el número 13 podría alcanzarse al multiplicar por 3 y luego sumar 5 dos veces, mientras que el número 15 no se puede alcanzar de esta manera.

+ +

Aquí tienes una solución recursiva:

+ +
function encontrarSolucion(objetivo) {
+  function encontrar(actual, historial) {
+    if (actual === objetivo) {
+      return historial;
+    } else if (actual > objetivo) {
+      return null;
+    } else {
+      return encontrar(actual + 5, `(${historial} + 5)`) ??
+             encontrar(actual * 3, `(${historial} * 3)`);
+    }
+  }
+  return encontrar(1, "1");
+}
+
+console.log(encontrarSolucion(24));
+// → (((1 * 3) + 5) * 3)
+ +

Ten en cuenta que este programa no necesariamente encuentra la secuencia de operaciones más corta. Se conforma con encontrar cualquier secuencia.

+ +

No te preocupes si no ves cómo funciona este código de inmediato. Vamos a inspeccionarlo bien, ya que es un gran ejercicio de pensamiento recursivo.

+ +

La función interna encontrar es la que realiza la recursión real. Toma dos argumentos: el número actual y una cadena que registra cómo llegamos a este número. Si encuentra una solución, devuelve una cadena que muestra cómo llegar al objetivo. Si no puede encontrar una solución para este número, devuelve null.

+ +

Para hacer esto, la función realiza una de entre tres acciones. Si el número actual es el número objetivo, el historial actual es una forma de alcanzar ese objetivo, por lo que este se devuelve y termina la función. Si el número actual es mayor que el objetivo, no tiene sentido explorar más esta rama porque tanto la suma como la multiplicación solo harán que el número sea más grande, por lo que la función devuelve null. Finalmente, si aún estamos por debajo del número objetivo, la función prueba ambas rutas posibles que parten del número actual llamándose a sí misma dos veces, una vez para la suma y otra vez para la multiplicación. Si la primera llamada devuelve algo que no es null, entonces este es el resultado que se devuelve. De lo contrario, se devuelve la segunda llamada, independientemente de si produce una cadena o null.

+ +

Para entender mejor cómo esta función produce el efecto que estamos buscando, veamos todas las llamadas a encontrar que se hacen al buscar una solución para el número 13:

+ +
encontrar(1, "1")
+  encontrar(6, "(1 + 5)")
+    encontrar(11, "((1 + 5) + 5)")
+      encontrar(16, "(((1 + 5) + 5) + 5)")
+        demasiado grande
+      encontrar(33, "(((1 + 5) + 5) * 3)")
+        demasiado grande
+    encontrar(18, "((1 + 5) * 3)")
+      demasiado grande
+  encontrar(3, "(1 * 3)")
+    encontrar(8, "((1 * 3) + 5)")
+      encontrar(13, "(((1 * 3) + 5) + 5)")
+        ¡encontrado!
+ +

La sangría indica la profundidad de la pila de llamadas. La primera vez que se llama a encontrar, la función comienza llamándose a sí misma para explorar la solución que comienza con (1 + 5). Esa llamada seguirá recursivamente para explorar cada solución a continuación que produzca un número menor o igual al número objetivo. Como no encuentra uno que alcance el objetivo, devuelve null en la primera llamada que se hace dentro de encontrar. Allí, el operador ?? hace que ocurra la llamada que explora la opción (1 * 3). Esta búsqueda es más exitosa: su primera llamada recursiva, a través de otra llamada recursiva más, alcanza el número objetivo. Esa llamada más interna devuelve una cadena, y cada uno de los operadores ?? en las llamadas intermedias pasa esa cadena, devolviendo en última instancia la solución.

+ +

Crecimiento de funciones

+ +

Hay dos formas más o menos naturales de que las funciones aparezcan en los programas.

+ +

La primera ocurre cuando te encuentras escribiendo código muy parecido varias veces. Preferirías no hacer eso, ya que tener más código significa más posibilidades de cometer errores y más material para leer para aquellas personas que intenten entender el programa. Por lo tanto, agarras esta funcionalidad repetida, encuentras un buen nombre para ella y la colocas dentro de una función.

+ +

La segunda forma es que darte cuenta de que necesitas alguna funcionalidad que aún no has escrito y que suena como si mereciera su propia función. Primero nombras la función y luego escribes su cuerpo. Incluso podrías comenzar a escribir código que use la función antes de definir la función en sí.

+ +

Cuán difícil es encontrar un buen nombre para una función es una buena indicación de lo claro que es el concepto que estás tratando de encapsular en ella. Vamos a ver de un ejemplo.

+ +

Queremos escribir un programa que imprima dos números: el número de vacas y de pollos en una granja, con las palabras Vacas y Pollos después de ellos y ceros de rrelleno antes de ambos números para que siempre se trate de números con tres dígitos:

+ +
007 Vacas
+011 Pollos
+ +

Esto está pidiendo una función con dos argumentos: el número de vacas y el número de pollos. ¡Vamos a programar!

+ +
function imprimirInventarioGranja(vacas, pollos) {
+  let cadenaVaca = String(vacas);
+  while (cadenaVaca.length < 3) {
+    cadenaVaca = "0" + cadenaVaca;
+  }
+  console.log(`${cadenaVaca} Vacas`);
+  let cadenaPollo = String(pollos);
+  while (cadenaPollo.length < 3) {
+    cadenaPollo = "0" + cadenaPollo;
+  }
+  console.log(`${cadenaPollo} Pollos`);
+}
+imprimirInventarioGranja(7, 11);
+ +

Escribir .length después de una expresión de cadena nos dará la longitud de esa cadena. Por lo tanto, los bucles while siguen añadiendo ceros delante de las cadenas de números hasta que estas tengan al menos tres caracteres de longitud.

+ +

¡Misión cumplida! Pero justo cuando estamos a punto de enviarle a la granjera el código (junto con una factura considerable), ella llama y nos dice que también ha comenzado a criar cerdos, ¿podríamos extender el software para imprimir también los cerdos?

+ +

¡Claro que podemos! Pero justo cuando estamos en el proceso de copiar y pegar esas cuatro líneas una vez más, nos detenemos y reconsideramos. Tiene que haber una mejor manera. Aquí un primer intento:

+ +
function imprimirConRellenoYEtiqueta(número, etiqueta) {
+  let cadenaNumero = String(número);
+  while (cadenaNumero.length < 3) {
+    cadenaNumero = "0" + cadenaNumero;
+  }
+  console.log(`${cadenaNumero} ${etiqueta}`);
+}
+
+function imprimirInventarioGranja(vacas, pollos, cerdos) {
+  imprimirConRellenoYEtiqueta(vacas, "Vacas");
+  imprimirConRellenoYEtiqueta(pollos, "Pollos");
+  imprimirConRellenoYEtiqueta(cerdos, "Cerdos");
+}
+
+imprimirInventarioGranja(7, 11, 3);
+ +

¡Funciona! Pero ese nombre, imprimirConRellenoYEtiqueta, es un poco raro. Confluye tres cosas: imprimir, rellenar con ceros y añadir una etiqueta, en una sola función.

+ +

En lugar de extraer completamente la parte repetida de nuestro programa, intentemos sacar un solo concepto:

+ +
function rellenarConCeros(número, ancho) {
+  let cadena = String(número);
+  while (cadena.length < ancho) {
+    cadena = "0" + cadena;
+  }
+  return cadena;
+}
+
+function imprimirInventarioGranja(vacas, pollos, cerdos) {
+  console.log(`${rellenarConCeros(vacas, 3)} Vacas`);
+  console.log(`${rellenarConCeros(pollos, 3)} Pollos`);
+  console.log(`${rellenarConCeros(cerdos, 3)} Cerdos`);
+}
+
+imprimirInventarioGranja(7, 16, 3);
+ +

Una función con un nombre claro y obvio como rellenarConCeros hace que sea más fácil para alguien que lee el código entender qué es lo que este hace. Además, una función así es útil en más situaciones aparte de este programa específico. Por ejemplo, podrías usarla para ayudar a imprimir tablas de números alineadas correctamente.

+ +

¿Cómo de inteligente y versátil debería ser nuestra función? Podríamos escribir cualquier cosa, desde una función terriblemente simple que solo puede rellenar un número para que tenga tres caracteres de ancho hasta un sistema complejo de formato de números general que maneje números fraccionarios, números negativos, alineación de puntos decimales, relleno con diferentes caracteres y más.

+ +

Un principio útil es abstenerse de agregar ingenio a menos que estés absolutamente seguro de que lo vas a necesitar. Puede ser tentador escribir “frameworks” generales para cada trozo de funcionalidad que te encuentres. No caigas en la tentación. Así no lograrás acabar ninguna tarea —estarás demasiado ocupado escribiendo código que nunca usarás.

+ +

Funciones y efectos secundarios

+ +

Las funciones pueden clasificarse más o menos en aquellas a las que se llama por sus efectos secundarios y aquellas a las que se llama por su valor de retorno (aunque también es posible tener efectos secundarios y devolver un valor).

+ +

La primera función auxiliar en el ejemplo de la granja, imprimirConRellenoYEtiqueta, se llama por su efecto secundario: imprimir una línea. La segunda versión, rellenarConCeros, se llama por su valor de retorno. No es casualidad que la segunda sea útil en más situaciones que la primera. Las funciones que crean valores son más fáciles de combinar de nuevas formas que las funciones que realizan efectos secundarios directamente.

+ +

Una función pura es un tipo específico de función productora de valores que no solo no tiene efectos secundarios, sino que tampoco depende de efectos secundarios de otro código —por ejemplo, no lee asociaciones globales cuyo valor podría cambiar. Una función pura tiene la agradable propiedad de que, al llamarla con los mismos argumentos, siempre produce el mismo valor (y no hace nada más). Una llamada a una tal función puede sustituirse por su valor de retorno sin cambiar el significado del código. Cuando no estás seguro de si una función pura está funcionando correctamente, puedes probarla llamándola y saber que, si funciona en ese contexto, funcionará en cualquier otro contexto. Las funciones no puras tienden a requerir más andamiaje para probarlas.

+ +

Aún así, no hay que sentirse mal por escribir funciones que no son puras. Los efectos secundarios a menudo son útiles. No hay forma de escribir una versión pura de console.log, por ejemplo, y es bueno tener console.log. Algunas operaciones también son más fáciles de expresar de manera eficiente cuando usamos efectos secundarios.

+ +

Resumen

+ +

Este capítulo te enseña cómo escribir tus propias funciones. La palabra clave function, cuando se usa como expresión, puede crear un valor de función. Cuando se usa como una declaración, puede usarse para declarar un enlace y darle una función como su valor. Las funciones flecha son otra forma de crear funciones.

+ +
// Definir f para contener un valor de función
+const f = function(a) {
+  console.log(a + 2);
+};
+
+// Declarar g como una función
+function g(a, b) {
+  return a * b * 3.5;
+}
+
+// Un valor de función menos verboso
+let h = a => a % 3;
+ +

Una parte clave del estudio de las funciones es comprender los ámbitos (scopes). Cada bloque crea un nuevo ámbito. Los parámetros y las enlaces declarados en un ámbito dado son locales y no son visibles desde el exterior. Las asociaciones declaradas con var se comportan de manera diferente: terminan en el ámbito de la función más cercana o en el ámbito global.

+ +

Separar las tareas que realiza tu programa en diferentes funciones es útil. No tendrás que repetirte tanto, y las funciones pueden ayudar a organizar un programa agrupando el código en trozos que hacen cosas específicas.

+ +

Ejercicios

+ +

Mínimo

+ +

El capítulo previo presentó la función estándar Math.min que devuelve su menor argumento. Ahora podemos escribir una función como esa nosotros mismos. Define la función min que toma dos argumentos y devuelve su mínimo.

+ +
// Tu código aquí.
+
+console.log(min(0, 10));
+// → 0
+console.log(min(0, -10));
+// → -10
+ +
Mostrar pistas...
+ +

Si tienes problemas para colocar las llaves y paréntesis en el lugar correcto para obtener una definición de función válida, comienza copiando uno de los ejemplos de este capítulo y modifícalo.

+ +

Una función puede contener múltiples declaraciones return.

+ +
+ +

Recursión

+ +

Hemos visto que podemos usar % (el operador de resto) para verificar si un número es par o impar al usar % 2 para ver si es divisible por dos. Aquí hay otra forma de definir si un número entero positivo es par o impar:

+ +
    + +
  • + +

    El cero es par.

  • + +
  • + +

    El uno es impar.

  • + +
  • + +

    Para cualquier otro número N, su paridad es la misma que la de N - 2.

+ +

Define una función recursiva esPar que corresponda a esta descripción. La función debe aceptar un solo parámetro (un número entero positivo) y devolver un booleano.

+ +

Pruébalo con 50 y 75. Observa cómo se comporta con -1. ¿Por qué? ¿Puedes pensar en una forma de solucionarlo?

+ +
// Tu código aquí.
+
+console.log(esPar(50));
+// → true
+console.log(esPar(75));
+// → false
+console.log(esPar(-1));
+// → ??
+ +
Mostrar pistas...
+ +

Es probable que tu función se parezca en cierta medida a la función interna encontrar en el ejemplo recursivo encontrarSolucion ejemplo de este capítulo, con una cadena if/else if/else que prueba cuál de los tres casos aplica. El else final, correspondiente al tercer caso, realiza la llamada recursiva. Cada una de las ramas debe contener una declaración return o de algún modo, asegurarse de que se devuelva un valor específico.

+ +

Cuando se le da un número negativo, la función se llamará recursivamente una y otra vez, pasándose a sí misma un número cada vez más negativo, alejándose así más y más de devolver un resultado. Al final, el programa se quedará sin espacio en la pila cortará.

+ +
+ +

Contando minuciosamente

+ +

Puedes obtener el N-ésimo caracter, o letra, de una cadena escribiendo [N] después de la cadena (por ejemplo, cadena[2]). El valor resultante será una cadena que contiene solo un carácter (por ejemplo, "b"). El primer carácter tiene la posición 0, lo que hace que el último se encuentre en la posición cadena.length - 1. En otras palabras, una cadena de dos caracteres tiene longitud 2, y sus caracteres tienen posiciones 0 y 1.

+ +

Escribe una función contarBs que tome una cadena como único argumento y devuelva un número que indique cuántos caracteres B en mayúscula hay en la cadena.

+ +

A continuación, escribe una función llamada contarCaracter que se comporte como contarBs, excepto que toma un segundo argumento que indica el carácter que se va a contar (en lugar de contar solo caracteres B en mayúscula). Reescribe contarBs para hacer uso de esta nueva función.

+ +
// Tu código aquí.
+
+console.log(contarBs("BOB"));
+// → 2
+console.log(contarCaracter("kakkerlak", "k"));
+// → 4
+ +
Mostrar pistas...
+ +

Tu función necesita un bucle que mire cada carácter en la cadena. Puede recorrer un índice desde cero hasta uno menos que su longitud (< string.length). Si el caracter en la posición actual es el mismo que el que la función está buscando, agrega 1 a una variable contadora. Una vez que el bucle ha terminado, el contador puede ser devuelto.

+ +

Ten cuidado de que todas las asociaciones utilizadas en la función sean locales a la función, declarándolas correctamente con la palabra clave let o const.

+ +
+
+ + +>>>>>>> a4f1e1e (Actualización del capítulo 2. Capítulo 3 revisado.) diff --git a/src/transform.mjs b/src/transform.mjs index 7687ef8e..6338f645 100644 --- a/src/transform.mjs +++ b/src/transform.mjs @@ -1,132 +1,133 @@ function childrenText(token) { - let text = "" - for (let i = 0; i < token.children.length; i++) { - let child = token.children[i]; - if (child.type == "text" || child.type == "code_inlin") text += child.content + let text = "" + for (let i = 0; i < token.children.length; i++) { + let child = token.children[i]; + if (child.type == "text" || child.type == "code_inlin") text += child.content + } + return text } - return text -} - -import {createHash} from "crypto" - -function hash(text) { - let sum = createHash("sha1") - sum.update(text) - return sum.digest("base64").slice(0, 10) -} - -function startAndEnd(text) { - var words = text.split(/\W+/); - if (!words[0]) words.shift(); - if (!words[words.length - 1]) words.pop(); - if (words.length <= 6) return words.join(" "); - return words.slice(0, 3).concat(words.slice(words.length - 3)).join(" "); -} - -function tokenText(token) { - if (token.type == "text" || token.type == "code_inline") return token.content - else if (token.type == "softbreak") return " " - else return "" -} - -function smartQuotes(tokens, i, tex, moveQuotes) { - let text = tokens[i].content, from = 0 - for (let j = i - 1, tt; j >= 0; j--) if (tt = tokenText(tokens[j])) { - text = tt + text - from = tt.length - break + + import {createHash} from "crypto" + + function hash(text) { + let sum = createHash("sha1") + sum.update(text) + return sum.digest("base64").slice(0, 10) } - let to = text.length - for (let j = i + 1, tt; j < tokens.length; j++) if (tt = tokenText(tokens[j])) { - text += tt - break + + function startAndEnd(text) { + var words = text.split(/\W+/); + if (!words[0]) words.shift(); + if (!words[words.length - 1]) words.pop(); + if (words.length <= 6) return words.join(" "); + return words.slice(0, 3).concat(words.slice(words.length - 3)).join(" "); } - - let quoted = text - .replace(/([\w\.,!?\)])'/g, "$1’") - .replace(/'(\w|\(\()/g, "‘$1") - .replace(/([\w\.,!?\)])"/g, "$1”") - .replace(/"(\w|\(\()/g, "“$1") - .slice(from, to) - if (moveQuotes) quoted = quoted.replace(/”([.!?:;,]+)/g, "$1”") - return tex ? quoted.replace(/‘/g, "`").replace(/’/g, "'").replace(/“/g, "``").replace(/”/g, "''") : quoted -} - -function handleIf(tokens, i, options) { - let tag = tokens[i].args[0] - if (options.defined.indexOf(tag) > -1) return i - for (let j = i + 1; j < tokens.length; j++) if (tokens[j].type == "meta_if_close" && tokens[j].args[0] == tag) - return j -} - -const titleCaseSmallWords = "a an the at by for in of on to up and as but with or nor if console.log".split(" "); - -function capitalizeTitle(text) { - return text.split(" ") - .map(word => titleCaseSmallWords.includes(word) ? word : word[0].toUpperCase() + word.slice(1)) - .join(" ") -} - -function transformInline(tokens, options, prevType) { - let capitalize = options.capitalizeTitles && prevType == "heading_open" - let result = [] - for (let i = 0; i < tokens.length; i++) { - let tok = tokens[i], type = tok.type - if (type == "meta_if_close" || (options.index === false && type == "meta_index")) { - // Drop - } else if (type == "meta_if_open") { - i = handleIf(tokens, i, options) - } else { - if (type == "text" && /[\'\"]/.test(tok.content)) tok.content = smartQuotes(tokens, i, options.texQuotes, options.moveQuotes) - if (capitalize) tok.content = capitalizeTitle(tok.content) - result.push(tok) - } + + function tokenText(token) { + if (token.type == "text" || token.type == "code_inline") return token.content + else if (token.type == "softbreak") return " " + else return "" } - return result -} - -function nextTag(tokens, i) { - for (let j = i + 1; j < tokens.length; j++) if (tokens[j].tag) return tokens[j]; -} - -export function transformTokens(tokens, options) { - let meta = {}, result = [] - for (let i = 0; i < tokens.length; i++) { - let tok = tokens[i], type = tok.type - if (type == "meta_meta") { - for (let prop in tok.args[0]) meta[prop] = tok.args[0][prop] - } else if (type == "meta_id") { - let next = nextTag(tokens, i) - ;(next.attrs || (next.attrs = [])).push(["id", tok.args[0]]) - } else if (type == "meta_table") { - nextTag(tokens, i).tableData = tok.args[0] - } else if (type == "meta_if_open") { - i = handleIf(tokens, i, options) - } else if (type == "meta_hint_open" && options.strip === "hints") { - do { i++ } while (tokens[i].type != "meta_hint_close") - } else if (type == "meta_if_close" || (options.index === false && (type == "meta_indexsee" || type == "meta_index"))) { - // Drop - } else if (tok.tag == "h1" && options.takeTitle) { - if (tokens[i + 1].children.length != 1) throw new Error("Complex H1 not supported") - meta.title = tokens[i + 1].children[0].content - i += 2 - } else { - if (type == "paragraph_open") - tok.hashID = "p-" + hash(startAndEnd(childrenText(tokens[i + 1]))) - else if (type == "heading_open") - tok.hashID = (tok.tag == "h2" ? "h-" : "i-") + hash(childrenText(tokens[i + 1])) - else if (type == "fence") - tok.hashID = "c-" + hash(tok.content) - - - if (tok.children) tok.children = transformInline(tok.children, options, tokens[i - 1].type) - // Mantener meta_note_open y meta_note_close - if (type == "meta_note_open" || type == "meta_note_close") { - result.push(tok) - continue - } - result.push(tok) - } + + function smartQuotes(tokens, i, tex, moveQuotes) { + let text = tokens[i].content, from = 0 + for (let j = i - 1, tt; j >= 0; j--) if (tt = tokenText(tokens[j])) { + text = tt + text + from = tt.length + break + } + let to = text.length + for (let j = i + 1, tt; j < tokens.length; j++) if (tt = tokenText(tokens[j])) { + text += tt + break + } + + let quoted = text + .replace(/([\w\.,!?\)])'/g, "$1’") + .replace(/'(\w|\(\()/g, "‘$1") + .replace(/([\w\.,!?\)])"/g, "$1”") + .replace(/"(\w|\(\()/g, "“$1") + .slice(from, to) + if (moveQuotes) quoted = quoted.replace(/”([.!?:;,]+)/g, "$1”") + return tex ? quoted.replace(/‘/g, "`").replace(/’/g, "'").replace(/“/g, "``").replace(/”/g, "''") : quoted } - return {tokens: result, metadata: meta} -} + + function handleIf(tokens, i, options) { + let tag = tokens[i].args[0] + if (options.defined.indexOf(tag) > -1) return i + for (let j = i + 1; j < tokens.length; j++) if (tokens[j].type == "meta_if_close" && tokens[j].args[0] == tag) + return j + } + + const titleCaseSmallWords = "a an the at by for in of on to up and as but with or nor if console.log".split(" "); + + function capitalizeTitle(text) { + return text.split(" ") + .map(word => titleCaseSmallWords.includes(word) ? word : word[0].toUpperCase() + word.slice(1)) + .join(" ") + } + + function transformInline(tokens, options, prevType) { + let capitalize = options.capitalizeTitles && prevType == "heading_open" + let result = [] + for (let i = 0; i < tokens.length; i++) { + let tok = tokens[i], type = tok.type + if (type == "meta_if_close" || (options.index === false && type == "meta_index")) { + // Drop + } else if (type == "meta_if_open") { + i = handleIf(tokens, i, options) + } else { + if (type == "text" && /[\'\"]/.test(tok.content)) tok.content = smartQuotes(tokens, i, options.texQuotes, options.moveQuotes) + if (capitalize) tok.content = capitalizeTitle(tok.content) + result.push(tok) + } + } + return result + } + + function nextTag(tokens, i) { + for (let j = i + 1; j < tokens.length; j++) if (tokens[j].tag) return tokens[j]; + } + + export function transformTokens(tokens, options) { + let meta = {}, result = [] + for (let i = 0; i < tokens.length; i++) { + let tok = tokens[i], type = tok.type + if (type == "meta_meta") { + for (let prop in tok.args[0]) meta[prop] = tok.args[0][prop] + } else if (type == "meta_id") { + let next = nextTag(tokens, i) + ;(next.attrs || (next.attrs = [])).push(["id", tok.args[0]]) + } else if (type == "meta_table") { + nextTag(tokens, i).tableData = tok.args[0] + } else if (type == "meta_if_open") { + i = handleIf(tokens, i, options) + } else if (type == "meta_hint_open" && options.strip === "hints") { + do { i++ } while (tokens[i].type != "meta_hint_close") + } else if (type == "meta_if_close" || (options.index === false && (type == "meta_indexsee" || type == "meta_index"))) { + // Drop + } else if (tok.tag == "h1" && options.takeTitle) { + if (tokens[i + 1].children.length != 1) throw new Error("Complex H1 not supported") + meta.title = tokens[i + 1].children[0].content + i += 2 + } else { + if (type == "paragraph_open") + tok.hashID = "p-" + hash(startAndEnd(childrenText(tokens[i + 1]))) + else if (type == "heading_open") + tok.hashID = (tok.tag == "h2" ? "h-" : "i-") + hash(childrenText(tokens[i + 1])) + else if (type == "fence") + tok.hashID = "c-" + hash(tok.content) + + + if (tok.children) tok.children = transformInline(tok.children, options, tokens[i - 1].type) + // Mantener meta_note_open y meta_note_close + if (type == "meta_note_open" || type == "meta_note_close") { + result.push(tok) + continue + } + result.push(tok) + } + } + return {tokens: result, metadata: meta} + } + \ No newline at end of file From 14ccca77f8bb4f5a011d1db4c1b614e4777a222c Mon Sep 17 00:00:00 2001 From: ckdvk Date: Mon, 10 Feb 2025 03:15:40 +0800 Subject: [PATCH 10/36] otro fix para las notas --- src/render_html.mjs | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/render_html.mjs b/src/render_html.mjs index 5309806b..44f96ea3 100644 --- a/src/render_html.mjs +++ b/src/render_html.mjs @@ -24,6 +24,22 @@ for (const arg of process.argv.slice(2)) { if (!file) throw new Error('No input file') const chapter = /^\d{2}_([^\.]+)/.exec(file) || [null, 'hints'] + + + + + + + + + + + + + + + + const { tokens, metadata } = transformTokens(markdown.parse(fs.readFileSync(file, 'utf8'), {}), { defined: epub ? ['book', 'html'] : ['interactive', 'html'], strip: epub ? 'hints' : '', @@ -31,6 +47,11 @@ const { tokens, metadata } = transformTokens(markdown.parse(fs.readFileSync(file index: false }) + + + + + const close = epub ? '/' : '' const dir = dirname(fileURLToPath(import.meta.url)) @@ -149,10 +170,6 @@ const renderer = { sup_open () { return '' }, sup_close () { return '' }, -// blockquote_open () { return '\n\n
'; }, -// blockquote_close () { return '
'; }, - - link_open (token) { const alt = token.attrGet('alt'); let href = token.attrGet('href') const maybeChapter = /^(\w+)(#.*)?$/.exec(href) @@ -176,6 +193,11 @@ const renderer = { return `${escape(alt)}` }, + meta_note(token) { + return `\n\n
${markdown.render(token.args[0])}
`; + }, + + meta_quote_open () { return '\n\n
' }, meta_quote_close (token) { const { author, title } = token.args[0] || {} @@ -188,12 +210,6 @@ const renderer = { meta_hint_open () { return '\n\n
Mostrar pistas...
' }, meta_hint_close () { return '\n\n
' }, - meta_note_open (token) { - return '\n\n

' + token.content + '

'; - }, - meta_note_close () { - return '
'; - } } function renderArray (tokens) { From cb8d4a33be9829bf37dd54ddd54754bc8bf68a49 Mon Sep 17 00:00:00 2001 From: ckdvk Date: Mon, 10 Feb 2025 03:17:40 +0800 Subject: [PATCH 11/36] =?UTF-8?q?=C3=BAltimo=20fix.=20Hab=C3=ADa=20fallos?= =?UTF-8?q?=20en=20los=20commits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- html/03_functions.html | 7 ++----- src/chapter_info.mjs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/html/03_functions.html b/html/03_functions.html index 9e81844f..455d5c3e 100644 --- a/html/03_functions.html +++ b/html/03_functions.html @@ -1,5 +1,3 @@ -<<<<<<< HEAD -======= @@ -62,7 +60,7 @@

Los parámetros de una función se comportan como asociaciones habituales, pero sus valores iniciales son dados por el llamador de la función, no por el propio código de la función.

-

Enlaces y ámbitos

+

Asociaciones y ámbitos

Cada asociación tiene un ámbito, que es la parte del programa en la que la asociación es visible. Para las asociaciones definidas fuera de cualquier función, bloque o módulo (ver Capítulo 10), el ámbito es todo el programa —puedes hacer referencia a esas asociaciones donde quieras. Estas asociaciones se llaman asociaciones globales.

@@ -482,7 +480,7 @@

Mínimo

-

El capítulo previo presentó la función estándar Math.min que devuelve su menor argumento. Ahora podemos escribir una función como esa nosotros mismos. Define la función min que toma dos argumentos y devuelve su mínimo.

+

En el capítulo previo se introdujo la función estándar Math.min que devuelve el menor de sus argumentos. Ahora podemos escribir una función como esa nosotros mismos. Define la función min que toma dos argumentos y devuelve su mínimo.

// Tu código aquí.
 
@@ -564,4 +562,3 @@ 

->>>>>>> a4f1e1e (Actualización del capítulo 2. Capítulo 3 revisado.) diff --git a/src/chapter_info.mjs b/src/chapter_info.mjs index a92fd1af..e858cb31 100644 --- a/src/chapter_info.mjs +++ b/src/chapter_info.mjs @@ -12,7 +12,7 @@ const TRANSLATIONS_MAP = { '02_3_tablero_de_ajedrez.js': '02_3_chessboard.js', '03_1_mnimo.js': '03_1_minimum.js', '03_2_recursin.js': '03_2_recursion.js', - '03_3_contando_frijoles.js': '03_3_bean_counting.js', + '03_3_contando_minuciosamente.js': '03_3_bean_counting.js', '04_1_la_suma_de_un_rango.js': '04_1_the_sum_of_a_range.js', '04_2_reversin_de_un_array.js': '04_2_reversing_an_array.js', '04_3_lista.js': '04_3_a_list.js', From 6ed4bb58287689fd7c49b562cc64e45b1056e0bb Mon Sep 17 00:00:00 2001 From: ckdvk Date: Mon, 10 Feb 2025 03:30:00 +0800 Subject: [PATCH 12/36] =?UTF-8?q?peque=C3=B1a=20revisi=C3=B3n=20del=20cap?= =?UTF-8?q?=C3=ADtulo=202,=20sobre=20el=20uso=20de=20enlace,=20asociaci?= =?UTF-8?q?=C3=B3n=20o=20variable.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 02_program_structure.md | 36 +++++++++++++++++---------------- html/02_program_structure.html | 37 ++++++++++++++++++---------------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/02_program_structure.md b/02_program_structure.md index bf46253e..c7fd7272 100644 --- a/02_program_structure.md +++ b/02_program_structure.md @@ -51,7 +51,9 @@ A veces, JavaScript te permite omitir el punto y coma al final de una declaraci {{index [syntax, statement], [binding, definition], "side effect", ["memoría", "organización"], [estado, in binding]}} -¿Cómo mantiene un programa un estado interno? ¿Cómo recuerda las cosas? Hemos visto cómo producir nuevos valores a partir de valores antiguos, pero esto no modifica los valores originales. Además, el nuevo valor debe utilizarse inmediatamente o desaparecerá tan pronto aparezca. Para atrapar y retener valores, JavaScript nos da algo llamado _enlace_, o _variable_: +¿Cómo mantiene un programa un estado interno? ¿Cómo recuerda las cosas? Hemos visto cómo producir nuevos valores a partir de valores antiguos, pero esto no modifica los valores originales. Además, el nuevo valor debe utilizarse inmediatamente o desaparecerá tan pronto aparezca. Para atrapar y retener valores, JavaScript nos da algo llamado _asociación_, o _enlace_, o _variable_: + +{{note "**N. del T.:** El uso de la palabra **variable** para denotar este concepto es muy común, aunque puede llegar a resultar confusa. En la versión original de este libro, el autor elige usar la palabra **bind** en lugar de **variable** para referirse a estas entidades. Nosotros haremos lo mismo utilizando la palabra **asociación**, aunque, en ocasiones, también se usará la palabra **enlace**. Rara vez usaremos la palabra **variable**, que se reservará en general para un tipo concreto de enlace. Veremos más sobre las diferencias entre cada tipo de asociación en este y el [siguiente capítulo](functions)"}} ``` let caught = 5 * 5; @@ -59,11 +61,11 @@ let caught = 5 * 5; {{index "let keyword"}} -Eso nos da un segundo tipo de ((declaración)). La palabra clave (_((keyword))_) `let` indica que esta frase va a definir una variable (o enlace). Está seguida por el nombre de la variable y, si queremos darle inmediatamente un valor, por un operador `=` y una expresión. +Eso nos da un segundo tipo de ((declaración)). La palabra clave (_((keyword))_) `let` indica que esta frase va a definir una variable (o asociación). Está seguida por el nombre de la variable y, si queremos darle inmediatamente un valor, por un operador `=` y una expresión. -En el ejemplo se crea un enlace llamado `caught` y se utiliza para capturar el número que se produce al multiplicar 5 por 5. +En el ejemplo se crea una asociación llamada `caught` y se utiliza para capturar el número que se produce al multiplicar 5 por 5. -Después de que se haya definido un enlace, su nombre se puede usar como una ((expresión)). El valor de esa expresión es el valor que el enlace guarda actualmente. Aquí tienes un ejemplo: +Después de que se haya definido una asociación, su nombre se puede usar como una ((expresión)). El valor de esa expresión es el valor que la asociación guarda actualmente. Aquí tienes un ejemplo: ``` let ten = 10; @@ -73,7 +75,7 @@ console.log(ten * ten); {{index "= operator", "asignación", [binding, "asignación"]}} -Cuando un enlace apunta a un valor, eso no significa que esté atado a ese valor para siempre. El operador `=` se puede usar en cualquier momento en enlaces existentes para desconectarlos de su valor actual y hacer que apunten a uno nuevo: +Cuando una asociación apunta a un valor, eso no significa que esté atada a ese valor para siempre. El operador `=` se puede usar en cualquier momento en asociaciones existentes para desconectarlas de su valor actual y hacer que apunten a uno nuevo: ``` let mood = "light"; @@ -86,9 +88,9 @@ console.log(mood); {{index [binding, "modelo de"], "tentáculo (analogía)"}} -Debes imaginarte los enlaces como tentáculos más que como cajas. No _contienen_ valores; los _agarran_—dos enlaces pueden hacer referencia al mismo valor. Un programa solo puede acceder a los valores a los que todavía tiene una referencia. Cuando necesitas recordar algo, o bien haces crecer un nuevo tentáculo para agarrarlo o lo reconectas con uno de tus tentáculos existentes. +Debes imaginarte las asociaciones como tentáculos más que como cajas. No _contienen_ valores; los _agarran_ —dos asociaciones pueden hacer referencia al mismo valor. Un programa solo puede acceder a los valores a los que todavía tiene una referencia. Cuando necesitas recordar algo, o bien haces crecer un nuevo tentáculo para agarrarlo o lo reconectas con uno de tus tentáculos existentes. -Veamos otro ejemplo. Para recordar la cantidad de dólares que Luigi todavía te debe, creas un enlace. Cuando te paga $35, le das a este enlace un nuevo valor: +Veamos otro ejemplo. Para recordar la cantidad de dólares que Luigi todavía te debe, creas una asociación. Cuando te paga $35, le das a esta asociación un nuevo valor: ``` let luigisDebt = 140; @@ -99,11 +101,11 @@ console.log(luigisDebt); {{index undefined}} -Cuando defines un enlace sin darle un valor, el tentáculo no tiene nada que agarrar, por lo que termina en el aire. Si solicitas el valor de un enlace vacío, obtendrás el valor `undefined`. +Cuando defines una asociación sin darle un valor, el tentáculo no tiene nada que agarrar, por lo que termina en el aire. Si solicitas el valor de un enlace vacío, obtendrás el valor `undefined`. {{index "let keyword"}} -Una sola instrucción `let` puede definir múltiples enlaces. Las definiciones deben estar separadas por comas: +Una sola instrucción `let` puede definir múltiples asociaciones. Las definiciones deben estar separadas por comas: ``` let one = 1, two = 2; @@ -111,7 +113,7 @@ console.log(one + two); // → 3 ``` -Las palabras `var` y `const` también se pueden usar para crear enlaces de manera similar a como lo hace `let`: +Las palabras `var` y `const` también se pueden usar para crear asociaciones de manera similar a como lo hace `let`: ``` var name = "Ayda"; @@ -122,21 +124,21 @@ console.log(greeting + name); {{index "var keyword"}} -La primera de estas, `var` (abreviatura de "variable"), es la forma en que se declaraban los enlaces en JavaScript anterior a 2015, cuando aún no existía `let`. Veremos la forma precisa en que difiere de `let` en el [próximo capítulo](functions). Por ahora, recuerda que en su mayoría hace lo mismo, pero rara vez lo usaremos en este libro porque se comporta de manera extraña en algunas situaciones. +La primera de estas, `var` (abreviatura de "variable"), es la forma en que se declaraban las asociaciones en JavaScript anterior a 2015, cuando aún no existía `let`. Veremos la forma precisa en que difiere de `let` en el [próximo capítulo](functions). Por ahora, recuerda que en su mayoría hace lo mismo, pero rara vez la usaremos en este libro porque se comporta de manera extraña en algunas situaciones. {{index "palabra clave const", "nomenclatura"}} -La palabra `const` significa _((constante))_. Define un enlace constante, que apunta al mismo valor mientras exista. Esto es útil para enlaces que solo dan un nombre a un valor de manera que más tarde puedas referirte fácilmente a él. +La palabra `const` significa _((constante))_. Define una asociación constante, que apunta al mismo valor mientras exista. Esto es útil para asociaciones que solo dan un nombre a un valor de manera que más tarde puedas referirte fácilmente a él. ## Nombres de enlaces {{index "carácter de subrayado", "signo de dólar", [enlace, nomenclatura]}} -Los nombres de enlaces pueden ser cualquier secuencia de una o más letras. Podemos incluir dígitos como parte del nombre de un enlace —`catch22` es un nombre válido, por ejemplo—, siempre y cuando el nombre no empiece por uno de ellos. Un nombre de enlace puede incluir signos de dólar (`$`) o subrayados (`_`), pero ningún otro caracter especial o signo de puntuación. +Los nombres de asociaciones o enlaces pueden ser cualquier secuencia de una o más letras. Podemos incluir dígitos como parte del nombre de un enlace —`catch22` es un nombre válido, por ejemplo—, siempre y cuando el nombre no empiece por uno de ellos. Un nombre de enlace puede incluir signos de dólar (`$`) o subrayados (`_`), pero ningún otro caracter especial o signo de puntuación. {{index [sintaxis, identificador], "implements (palabra reservada)", "interface (palabra reservada)", "package (palabra reservada)", "private (palabra reservada)", "protected (palabra reservada)", "public (palabra reservada)", "static (palabra reservada)", "operador void", "yield (palabra reservada)", "enum (palabra reservada)", "palabra reservada", [enlace, nomenclatura]}} -Cualquier palabra con un significado especial, como `let`, es una _((palabra clave))_, y no puede ser usada como nombre de un enlace. También hay una serie de palabras que están "reservadas para su uso" en ((futuras)) versiones de JavaScript, las cuales tampoco se pueden usar como nombres de enlaces. La lista completa de palabras clave y palabras reservadas es bastante larga: +Cualquier palabra con un significado especial, como `let`, es una _((palabra clave))_, y no puede ser usada como nombre de una asociación. También hay una serie de palabras que están "reservadas para su uso" en ((futuras)) versiones de JavaScript, las cuales tampoco se pueden usar como nombres de asociaciones. La lista completa de palabras clave y palabras reservadas es bastante larga: ```{lang: "null"} break case catch class const continue debugger default @@ -148,7 +150,7 @@ switch this throw true try typeof var void while with yield {{index [sintaxis, error]}} -No te entretengas en memorizar esta lista. Simplemente, cuando al crear un enlace se produzca un error de sintaxis inesperado, comprueba si estás intentando definir una palabra reservada. +No te entretengas en memorizar esta lista. Simplemente, cuando al crear una asociación se produzca un error de sintaxis inesperado, comprueba si estás intentando definir una palabra reservada. ## El entorno @@ -198,7 +200,7 @@ if}} {{index [objeto, propiedad], [acceso, propiedad]}} -Aunque los nombres de enlaces no pueden contener ((puntos)), `console.log` tiene uno. Esto se debe a que `console.log` no es un simple enlace, sino una expresión que recupera la propiedad `log` del valor contenido por el enlace `console`. Descubriremos exactamente lo que esto significa en el [Capítulo ?](data#properties). +Aunque los nombres de asociaciones no pueden contener ((puntos)), `console.log` tiene uno. Esto se debe a que `console.log` no es un simple enlace, sino una expresión que recupera la propiedad `log` del valor contenido por el enlace `console`. Descubriremos exactamente lo que esto significa en el [Capítulo ?](data#properties). {{id valores_retorno}} ## Valores de retorno @@ -565,7 +567,7 @@ Puedes colocar cualquier cantidad de etiquetas `case` dentro del cuerpo de `swit {{index "capitalización", [binding, nombrar], [espacios en blanco, sintaxis]}} -Los nombres de los enlaces no pueden contener espacios, aunque a menudo es útil usar varias palabras para describir claramente lo que representa el enlace. Estas son básicamente tus opciones para escribir un nombre de enlace con varias palabras: +Los nombres de los asociaciones no pueden contener espacios, aunque a menudo es útil usar varias palabras para describir claramente lo que representa la asociación. Estas son básicamente tus opciones para escribir un nombre de asociación con varias palabras: ```{lang: null} fuzzylittleturtle diff --git a/html/02_program_structure.html b/html/02_program_structure.html index bc70a3da..70b4ad62 100644 --- a/html/02_program_structure.html +++ b/html/02_program_structure.html @@ -43,21 +43,24 @@

Enlaces

-

¿Cómo mantiene un programa un estado interno? ¿Cómo recuerda las cosas? Hemos visto cómo producir nuevos valores a partir de valores antiguos, pero esto no modifica los valores originales. Además, el nuevo valor debe utilizarse inmediatamente o desaparecerá tan pronto aparezca. Para atrapar y retener valores, JavaScript nos da algo llamado enlace, o variable:

+

¿Cómo mantiene un programa un estado interno? ¿Cómo recuerda las cosas? Hemos visto cómo producir nuevos valores a partir de valores antiguos, pero esto no modifica los valores originales. Además, el nuevo valor debe utilizarse inmediatamente o desaparecerá tan pronto aparezca. Para atrapar y retener valores, JavaScript nos da algo llamado asociación, o enlace, o variable:

+ +

N. del T.: El uso de la palabra variable para denotar este concepto es muy común, aunque puede llegar a resultar confusa. En la versión original de este libro, el autor elige usar la palabra bind en lugar de variable para referirse a estas entidades. Nosotros haremos lo mismo utilizando la palabra asociación, aunque, en ocasiones, también se usará la palabra enlace. Rara vez usaremos la palabra variable, que se reservará en general para un tipo concreto de enlace. Veremos más sobre las diferencias entre cada tipo de asociación en este y el siguiente capítulo

+
let caught = 5 * 5;
-

Eso nos da un segundo tipo de declaración. La palabra clave (keyword) let indica que esta frase va a definir una variable (o enlace). Está seguida por el nombre de la variable y, si queremos darle inmediatamente un valor, por un operador = y una expresión.

+

Eso nos da un segundo tipo de declaración. La palabra clave (keyword) let indica que esta frase va a definir una variable (o asociación). Está seguida por el nombre de la variable y, si queremos darle inmediatamente un valor, por un operador = y una expresión.

-

En el ejemplo se crea un enlace llamado caught y se utiliza para capturar el número que se produce al multiplicar 5 por 5.

+

En el ejemplo se crea una asociación llamada caught y se utiliza para capturar el número que se produce al multiplicar 5 por 5.

-

Después de que se haya definido un enlace, su nombre se puede usar como una expresión. El valor de esa expresión es el valor que el enlace guarda actualmente. Aquí tienes un ejemplo:

+

Después de que se haya definido una asociación, su nombre se puede usar como una expresión. El valor de esa expresión es el valor que la asociación guarda actualmente. Aquí tienes un ejemplo:

let ten = 10;
 console.log(ten * ten);
 // → 100
-

Cuando un enlace apunta a un valor, eso no significa que esté atado a ese valor para siempre. El operador = se puede usar en cualquier momento en enlaces existentes para desconectarlos de su valor actual y hacer que apunten a uno nuevo:

+

Cuando una asociación apunta a un valor, eso no significa que esté atada a ese valor para siempre. El operador = se puede usar en cualquier momento en asociaciones existentes para desconectarlas de su valor actual y hacer que apunten a uno nuevo:

let mood = "light";
 console.log(mood);
@@ -66,39 +69,39 @@ 

// → dark

-

Debes imaginarte los enlaces como tentáculos más que como cajas. No contienen valores; los agarran—dos enlaces pueden hacer referencia al mismo valor. Un programa solo puede acceder a los valores a los que todavía tiene una referencia. Cuando necesitas recordar algo, o bien haces crecer un nuevo tentáculo para agarrarlo o lo reconectas con uno de tus tentáculos existentes.

+

Debes imaginarte las asociaciones como tentáculos más que como cajas. No contienen valores; los agarran —dos asociaciones pueden hacer referencia al mismo valor. Un programa solo puede acceder a los valores a los que todavía tiene una referencia. Cuando necesitas recordar algo, o bien haces crecer un nuevo tentáculo para agarrarlo o lo reconectas con uno de tus tentáculos existentes.

-

Veamos otro ejemplo. Para recordar la cantidad de dólares que Luigi todavía te debe, creas un enlace. Cuando te paga $35, le das a este enlace un nuevo valor:

+

Veamos otro ejemplo. Para recordar la cantidad de dólares que Luigi todavía te debe, creas una asociación. Cuando te paga $35, le das a esta asociación un nuevo valor:

let luigisDebt = 140;
 luigisDebt = luigisDebt - 35;
 console.log(luigisDebt);
 // → 105
-

Cuando defines un enlace sin darle un valor, el tentáculo no tiene nada que agarrar, por lo que termina en el aire. Si solicitas el valor de un enlace vacío, obtendrás el valor undefined.

+

Cuando defines una asociación sin darle un valor, el tentáculo no tiene nada que agarrar, por lo que termina en el aire. Si solicitas el valor de un enlace vacío, obtendrás el valor undefined.

-

Una sola instrucción let puede definir múltiples enlaces. Las definiciones deben estar separadas por comas:

+

Una sola instrucción let puede definir múltiples asociaciones. Las definiciones deben estar separadas por comas:

let one = 1, two = 2;
 console.log(one + two);
 // → 3
-

Las palabras var y const también se pueden usar para crear enlaces de manera similar a como lo hace let:

+

Las palabras var y const también se pueden usar para crear asociaciones de manera similar a como lo hace let:

var name = "Ayda";
 const greeting = "Hola ";
 console.log(greeting + name);
 // → Hola Ayda
-

La primera de estas, var (abreviatura de “variable”), es la forma en que se declaraban los enlaces en JavaScript anterior a 2015, cuando aún no existía let. Veremos la forma precisa en que difiere de let en el próximo capítulo. Por ahora, recuerda que en su mayoría hace lo mismo, pero rara vez lo usaremos en este libro porque se comporta de manera extraña en algunas situaciones.

+

La primera de estas, var (abreviatura de “variable”), es la forma en que se declaraban las asociaciones en JavaScript anterior a 2015, cuando aún no existía let. Veremos la forma precisa en que difiere de let en el próximo capítulo. Por ahora, recuerda que en su mayoría hace lo mismo, pero rara vez la usaremos en este libro porque se comporta de manera extraña en algunas situaciones.

-

La palabra const significa constante. Define un enlace constante, que apunta al mismo valor mientras exista. Esto es útil para enlaces que solo dan un nombre a un valor de manera que más tarde puedas referirte fácilmente a él.

+

La palabra const significa constante. Define una asociación constante, que apunta al mismo valor mientras exista. Esto es útil para asociaciones que solo dan un nombre a un valor de manera que más tarde puedas referirte fácilmente a él.

Nombres de enlaces

-

Los nombres de enlaces pueden ser cualquier secuencia de una o más letras. Podemos incluir dígitos como parte del nombre de un enlace —catch22 es un nombre válido, por ejemplo—, siempre y cuando el nombre no empiece por uno de ellos. Un nombre de enlace puede incluir signos de dólar ($) o subrayados (_), pero ningún otro caracter especial o signo de puntuación.

+

Los nombres de asociaciones o enlaces pueden ser cualquier secuencia de una o más letras. Podemos incluir dígitos como parte del nombre de un enlace —catch22 es un nombre válido, por ejemplo—, siempre y cuando el nombre no empiece por uno de ellos. Un nombre de enlace puede incluir signos de dólar ($) o subrayados (_), pero ningún otro caracter especial o signo de puntuación.

-

Cualquier palabra con un significado especial, como let, es una palabra clave, y no puede ser usada como nombre de un enlace. También hay una serie de palabras que están “reservadas para su uso” en futuras versiones de JavaScript, las cuales tampoco se pueden usar como nombres de enlaces. La lista completa de palabras clave y palabras reservadas es bastante larga:

+

Cualquier palabra con un significado especial, como let, es una palabra clave, y no puede ser usada como nombre de una asociación. También hay una serie de palabras que están “reservadas para su uso” en futuras versiones de JavaScript, las cuales tampoco se pueden usar como nombres de asociaciones. La lista completa de palabras clave y palabras reservadas es bastante larga:

break case catch class const continue debugger default
 delete do else enum export extends false finally for
@@ -106,7 +109,7 @@ 

-

No te entretengas en memorizar esta lista. Simplemente, cuando al crear un enlace se produzca un error de sintaxis inesperado, comprueba si estás intentando definir una palabra reservada.

+

No te entretengas en memorizar esta lista. Simplemente, cuando al crear una asociación se produzca un error de sintaxis inesperado, comprueba si estás intentando definir una palabra reservada.

El entorno

@@ -132,7 +135,7 @@

"el valor de x es", x); // → el valor de x es 30

-

Aunque los nombres de enlaces no pueden contener puntos, console.log tiene uno. Esto se debe a que console.log no es un simple enlace, sino una expresión que recupera la propiedad log del valor contenido por el enlace console. Descubriremos exactamente lo que esto significa en el Capítulo 4.

+

Aunque los nombres de asociaciones no pueden contener puntos, console.log tiene uno. Esto se debe a que console.log no es un simple enlace, sino una expresión que recupera la propiedad log del valor contenido por el enlace console. Descubriremos exactamente lo que esto significa en el Capítulo 4.

Valores de retorno

@@ -375,7 +378,7 @@

Uso de mayúsculas

-

Los nombres de los enlaces no pueden contener espacios, aunque a menudo es útil usar varias palabras para describir claramente lo que representa el enlace. Estas son básicamente tus opciones para escribir un nombre de enlace con varias palabras:

+

Los nombres de los asociaciones no pueden contener espacios, aunque a menudo es útil usar varias palabras para describir claramente lo que representa la asociación. Estas son básicamente tus opciones para escribir un nombre de asociación con varias palabras:

fuzzylittleturtle
 fuzzy_little_turtle

From 563a0741c9103701fff3cda5e5bba1ed89003692 Mon Sep 17 00:00:00 2001
From: ckdvk 
Date: Wed, 12 Feb 2025 01:58:33 +0800
Subject: [PATCH 13/36] =?UTF-8?q?revisado=20cap=C3=ADtulo=204.=20Alg=C3=BA?=
 =?UTF-8?q?n=20ajuste=20en=20cap=C3=ADtulo=202.=20TODO:=20Revisar=20ejerci?=
 =?UTF-8?q?cios?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 02_program_structure.md        |   2 +-
 04_data.md                     | 490 ++++++++++++++++----------------
 html/02_program_structure.html |   2 +-
 html/04_data.html              | 494 +++++++++++++++++----------------
 4 files changed, 505 insertions(+), 483 deletions(-)

diff --git a/02_program_structure.md b/02_program_structure.md
index c7fd7272..cad7a73c 100644
--- a/02_program_structure.md
+++ b/02_program_structure.md
@@ -53,7 +53,7 @@ A veces, JavaScript te permite omitir el punto y coma al final de una declaraci
 
 ¿Cómo mantiene un programa un estado interno? ¿Cómo recuerda las cosas? Hemos visto cómo producir nuevos valores a partir de valores antiguos, pero esto no modifica los valores originales. Además, el nuevo valor debe utilizarse inmediatamente o desaparecerá tan pronto aparezca. Para atrapar y retener valores, JavaScript nos da algo llamado _asociación_, o _enlace_, o _variable_:
 
-{{note "**N. del T.:** El uso de la palabra **variable** para denotar este concepto es muy común, aunque puede llegar a resultar confusa. En la versión original de este libro, el autor elige usar la palabra **bind** en lugar de **variable** para referirse a estas entidades. Nosotros haremos lo mismo utilizando la palabra **asociación**, aunque, en ocasiones, también se usará la palabra **enlace**. Rara vez usaremos la palabra **variable**, que se reservará en general para un tipo concreto de enlace. Veremos más sobre las diferencias entre cada tipo de asociación en este y el [siguiente capítulo](functions)"}}
+{{note "**N. del T.:** El uso de la palabra **variable** para denotar este concepto es muy común, aunque puede llegar a resultar confusa. En la versión original de este libro, el autor elige usar la palabra **bind** en lugar de **variable** para referirse a estas entidades. Nosotros haremos lo mismo utilizando la palabra **asociación**, aunque, en ocasiones, también se usará la palabra **enlace** o **asignación**. Rara vez usaremos la palabra **variable**, que se reservará en general para un tipo concreto de enlace. Veremos más sobre las diferencias entre cada tipo de asociación en este y el [siguiente capítulo](functions)"}}
 
 ```
 let caught = 5 * 5;
diff --git a/04_data.md b/04_data.md
index 5d40014a..f2f2d8e0 100644
--- a/04_data.md
+++ b/04_data.md
@@ -4,7 +4,7 @@
 
 {{quote {author: "Charles Babbage", title: "Passages from the Life of a Philosopher (1864)", chapter: true}
 
-En dos ocasiones me han preguntado: 'Dígame, Sr. Babbage, si introduce en la máquina cifras erróneas, ¿saldrán respuestas correctas?' [...] No soy capaz de entender correctamente el tipo de confusión de ideas que podría provocar tal pregunta.
+En dos ocasiones me han preguntado: 'Dígame, Sr. Babbage, si introduce en la máquina cifras incorrectas, ¿saldrán las respuestas correctas?' [...] No soy capaz de comprender correctamente el tipo de confusión de ideas que podría provocar tal pregunta.
 
 quote}}
 
@@ -14,11 +14,11 @@ quote}}
 
 {{index objeto, "estructura de datos"}}
 
-Números, booleanos y cadenas de texto son los átomos a partir de los cuales se construyen las estructuras de ((datos)). Sin embargo, muchos tipos de información requieren más de un átomo. Los _objetos_ nos permiten agrupar valores, incluyendo otros objetos, para construir estructuras más complejas.
+Números, booleanos y cadenas de texto son los átomos con los que se construyen las estructuras de ((datos)). Sin embargo, muchos tipos de información requieren más de un átomo. Los _objetos_ nos permiten agrupar valores —incluyendo otros objetos— para construir estructuras más complejas.
 
-Hasta ahora, los programas que hemos creado han estado limitados por el hecho de que operaban solo en tipos de datos simples. Después de aprender los conceptos básicos de estructuras de datos en este capítulo, sabrás lo suficiente como para comenzar a escribir programas útiles.
+Hasta ahora, los programas que hemos escrito han estado limitados por el hecho de que operaban solo en tipos de datos simples. Después de aprender los conceptos básicos sobre estructuras de datos en este capítulo, sabrás suficiente como para comenzar a escribir programas útiles.
 
-El capítulo trabajará a través de un ejemplo de programación más o menos realista, introduciendo conceptos a medida que se aplican al problema en cuestión. El código de ejemplo a menudo se basará en funciones y variables introducidas anteriormente en el libro.
+En este capítulo trabajaremos con un ejemplo de programación más o menos realista, introduciendo conceptos a medida que se aplican al problema en cuestión. El código de ejemplo a menudo se basará en funciones y asociaciones introducidas anteriormente en el libro.
 
 {{if libro
 
@@ -26,41 +26,43 @@ El ((sandbox)) en línea para el libro ([_https://eloquentjavascript.net/code_](
 
 if}}
 
-## El hombreardilla
+## El hombre ardilla
 
-{{index "ejemplo hombreardilla", "licantropía"}}
+{{index "ejemplo hombre ardilla", "licantropía"}}
 
-De vez en cuando, usualmente entre las 8 p. m. y las 10 p. m., ((Jacques)) se transforma en un pequeño roedor peludo con una cola espesa.
+De vez en cuando, normalmente entre las 8 p. m. y las 10 p. m., ((Jacques)) se transforma en un pequeño roedor peludo con espesa cola.
 
-Por un lado, Jacques está bastante contento de no tener licantropía clásica. Convertirse en una ardilla causa menos problemas que convertirse en un lobo. En lugar de preocuparse por comer accidentalmente al vecino (_eso_ sería incómodo), se preocupa por ser comido por el gato del vecino. Después de dos ocasiones de despertar en una rama peligrosamente delgada en la copa de un roble, desnudo y desorientado, ha optado por cerrar con llave las puertas y ventanas de su habitación por la noche y poner unas cuantas nueces en el suelo para mantenerse ocupado.
+Por un lado, Jacques está bastante contento de no tener una licantropía convencional. Convertirse en una ardilla da menos problemas que convertirse en un lobo. En lugar de tenerse que preocupar por comerse accidentalmente al vecino (_eso_ sería incómodo), se preocupa por que no se lo coma el gato del vecino. Tras despertar en dos ocasiones sobre una rama peligrosamente fina en la copa de un roble, desnudo y desorientado, ha optado por cerrar con llave las puertas y ventanas de su habitación por las noches y poner unas cuantas nueces en el suelo para mantenerse ocupado.
 
-Pero Jacques preferiría deshacerse por completo de su condición. Las ocurrencias irregulares de la transformación hacen que sospeche que podrían ser desencadenadas por algo. Durante un tiempo, creyó que sucedía solo en días en los que había estado cerca de robles. Sin embargo, evitar los robles no resolvió el problema.
+Pero Jacques preferiría deshacerse por completo de su condición. Las irregularidad con que suceden sus transformaciones hacen que sospeche que podrían ser desencadenadas por algo. Durante un tiempo, creyó que solo sucedía en días en los que había estado cerca de robles, pero evitar los robles no resolvió el problema.
 
 {{index diario}}
 
-Cambió a un enfoque más científico, Jacques ha comenzado a llevar un registro diario de todo lo que hace en un día dado y si cambió de forma. Con estos datos, espera estrechar las condiciones que desencadenan las transformaciones. Lo primero que necesita es una estructura de datos para almacenar esta información.
+Cambiando a un enfoque más científico, Jacques ha comenzado a llevar un registro diario de todo lo que hace en el día y si cambió de forma ese día. Con estos datos, espera poder deliminar las condiciones que desencadenan sus transformaciones.
+
+Lo primero que necesita es una estructura de datos para almacenar esta información.
 
 ## Conjuntos de datos
 
 {{index ["estructura de datos", "colección"], [memoria, "organización"]}}
 
-Para trabajar con un conjunto de datos digitales, primero tenemos que encontrar una forma de representarlo en la memoria de nuestra máquina. Digamos, por ejemplo, que queremos representar una ((colección)) de los números 2, 3, 5, 7 y 11.
+Para trabajar con un conjunto de datos digitales, primero tenemos que encontrar una manera de representarlo en la memoria de nuestra máquina. Digamos, por ejemplo, que queremos representar una ((colección)) de los números 2, 3, 5, 7 y 11.
 
 {{index string}}
 
-Podríamos ser creativos con las cadenas, después de todo, las cadenas pueden tener cualquier longitud, por lo que podemos poner muchos datos en ellas, y usar `"2 3 5 7 11"` como nuestra representación. Pero esto es incómodo. Tendríamos que extraer de alguna manera los dígitos y convertirlos de vuelta a números para acceder a ellos.
+Podríamos ponernos creativos con las cadenas —después de todo, las cadenas pueden tener cualquier longitud, por lo que podemos poner muchos datos en ellas— y usar `"2 3 5 7 11"` como representación de esta colección. Pero esto quizá no sea lo más apropiado. Tendríamos que extraer de alguna manera los dígitos y convertirlos de vuelta a números para acceder a ellos.
 
 {{index [array, "creación"], "[] (arreglo)"}}
 
-Afortunadamente, JavaScript proporciona un tipo de dato específicamente para almacenar secuencias de valores. Se llama un _array_ y se escribe como una lista de valores entre ((corchetes)), separados por comas:
+Afortunadamente, JavaScript proporciona un tipo de dato específicamente para almacenar secuencias de valores. Se llama _array_ (o arreglo) y se escribe como una lista de valores entre ((corchetes)), separados por comas:
 
 ```
-let listaDeNumeros = [2, 3, 5, 7, 11];
-console.log(listaDeNumeros[2]);
+let listaDeNúmeros = [2, 3, 5, 7, 11];
+console.log(listaDeNúmeros[2]);
 // → 5
-console.log(listaDeNumeros[0]);
+console.log(listaDeNúmeros[0]);
 // → 2
-console.log(listaDeNumeros[2 - 1]);
+console.log(listaDeNúmeros[2 - 1]);
 // → 3
 ```
 
@@ -70,9 +72,9 @@ La notación para acceder a los elementos dentro de un array también utiliza ((
 
 {{id array_indexing}}
 
-{{index "conteo basado en cero"}}
+{{index "numeración basada en cero"}}
 
-El primer índice de un array es cero, no uno, por lo que el primer elemento se recupera con `listaDeNumeros[0]`. El conteo basado en cero tiene una larga tradición en tecnología y, de cierta manera, tiene mucho sentido, pero se necesita un poco de tiempo para acostumbrarse. Piensa en el índice como el número de elementos a omitir, contando desde el inicio del array.
+El primer índice de un array es cero, no uno, por lo que el primer elemento se recupera con `listaDeNúmeros[0]`. La numeración basada en cero tiene una larga tradición en tecnología y, en cierto modo, tiene mucho sentido, pero se necesita un poco de tiempo para hacerse a ella. Piensa en el índice como el número de elementos del array que hay que saltarse hasta llegar al elemento requerido, contando desde el inicio del array.
 
 {{id propiedades}}
 
@@ -80,11 +82,11 @@ El primer índice de un array es cero, no uno, por lo que el primer elemento se
 
 {{index "objeto Math", "función Math.max", ["propiedad longitud", "para cadenas"], [objeto, propiedad], "carácter punto", [acceso de propiedad]}}
 
-Hemos visto algunas expresiones como `miCadena.length` (para obtener la longitud de una cadena) y `Math.max` (la función máxima) en capítulos anteriores. Estas expresiones acceden a una _propiedad_ de algún valor. En el primer caso, accedemos a la propiedad `length` del valor en `miCadena`. En el segundo, accedemos a la propiedad llamada `max` en el objeto `Math` (que es una colección de constantes y funciones relacionadas con matemáticas).
+Hemos visto algunas expresiones como `miCadena.length` (para obtener la longitud de una cadena) y `Math.max` (la función máximo) en capítulos anteriores. Estas expresiones acceden a una _propiedad_ de un valor. En el primer caso, accedemos a la propiedad `length` del valor en `miCadena`. En el segundo, accedemos a la propiedad llamada `max` en el objeto `Math` (que es una colección de constantes y funciones relacionadas con matemáticas).
 
 {{index ["acceso de propiedad"], null, undefined}}
 
-Casi todos los valores de JavaScript tienen propiedades. Las excepciones son `null` y `undefined`. Si intentas acceder a una propiedad en uno de estos valores no definidos, obtendrás un error:
+Casi todos los valores de JavaScript tienen propiedades. Las excepciones son `null` y `undefined`. Si intentas acceder a una propiedad de uno de estos valores no definidos, obtendrás un error:
 
 ```{test: no}
 null.length;
@@ -94,9 +96,11 @@ null.length;
 {{indexsee "carácter punto", "carácter punto"}}
 {{index "[] (subíndice)", "carácter punto", "corchetes", "propiedad calculada", ["acceso de propiedad"]}}
 
-Las dos formas principales de acceder a propiedades en JavaScript son con un punto y con corchetes. Tanto `valor.x` como `valor[x]` acceden a una propiedad en `valor`, pero no necesariamente a la misma propiedad. La diferencia radica en cómo se interpreta `x`. Al usar un punto, la palabra después del punto es el nombre literal de la propiedad. Al usar corchetes, la expresión entre los corchetes es _evaluada_ para obtener el nombre de la propiedad. Mientras que `valor.x` obtiene la propiedad de `valor` llamada "x", `valor[x]` toma el valor de la variable llamada `x` y lo utiliza, convertido a cadena, como nombre de propiedad. Si sabes que la propiedad en la que estás interesado se llama _color_, dices `valor.color`. Si quieres extraer la propiedad nombrada por el valor almacenado en la vinculación `i`, dices `valor[i]`. Los nombres de las propiedades son cadenas de texto. Pueden ser cualquier cadena, pero la notación de punto solo funciona con nombres que parecen nombres de vinculaciones válidos, comenzando con una letra o guion bajo, y conteniendo solo letras, números y guiones bajos. Si deseas acceder a una propiedad llamada _2_ o _John Doe_, debes utilizar corchetes: `valor[2]` o `valor["John Doe"]`.
+Las dos formas principales de acceder a propiedades en JavaScript son con un punto y con corchetes. Tanto `valor.x` como `valor[x]` acceden a una propiedad en `valor`, pero no necesariamente a la misma propiedad. La diferencia radica en cómo se interpreta `x`. Al usar un punto, la palabra después del punto es el nombre literal de la propiedad. Al usar corchetes, la expresión entre los corchetes es _evaluada_ para obtener el nombre de la propiedad. Mientras que `valor.x` obtiene la propiedad de `valor` llamada "x", `valor[x]` toma el valor de la variable llamada `x` y lo utiliza, convertido a cadena, como nombre de propiedad.
+
+Si sabes que la propiedad en la que estás interesado se llama _color_, dices `valor.color`. Si quieres extraer la propiedad nombrada por el valor almacenado en la asociación `i`, dices `valor[i]`. Los nombres de las propiedades son cadenas de texto. Pueden ser cualquier cadena, pero la notación de punto solo funciona cuando se usan nombres que encajan en las reglas válidas para definir nombres de asociaciones —comenzando con una letra o guion bajo, y conteniendo solo letras, números y guiones bajos. Si quieres acceder a una propiedad llamada _2_ o _John Doe_, tendrás que usar corchetes: `valor[2]` o `valor["John Doe"]`.
 
-Los elementos en un ((array)) se almacenan como propiedades del array, utilizando números como nombres de propiedades. Dado que no puedes usar la notación de punto con números y generalmente quieres usar una vinculación que contenga el índice de todos modos, debes utilizar la notación de corchetes para acceder a ellos.
+Los elementos en un ((array)) se almacenan como propiedades del array, utilizando números como nombres de estas propiedades. Dado que se puede usar la notación de punto con números y aún así querríamos usar una asociación que contenga el índice, tendremos que usar la notación de corchetes para acceder a ellos.
 
 {{index ["propiedad longitud", "para array"], [array, "longitud de"]}}
 
@@ -120,17 +124,17 @@ console.log(doh.toUpperCase());
 
 {{index "conversión de mayúsculas y minúsculas", "método toUpperCase", "método toLowerCase"}}
 
-Cada cadena de texto tiene una propiedad `toUpperCase`. Cuando se llama, devolverá una copia de la cadena en la que todas las letras se han convertido a mayúsculas. También existe `toLowerCase`, que hace lo contrario.
+Toda cadena de texto tiene una propiedad `toUpperCase`. Cuando se llama, devolverá una copia de la cadena en la que todas las letras se han convertido a mayúsculas. También existe `toLowerCase`, que hace lo contrario.
 
 {{index "vinculación de this"}}
 
 Curiosamente, aunque la llamada a `toUpperCase` no pasa argumentos, de alguna manera la función tiene acceso a la cadena `"Doh"`, el valor cuya propiedad llamamos. Descubrirás cómo funciona esto en el [Capítulo ?](object#obj_methods).
 
-Las propiedades que contienen funciones generalmente se llaman _métodos_ del valor al que pertenecen, como en "`toUpperCase` es un método de una cadena".
+Las propiedades que contienen funciones generalmente se llaman _métodos_ del valor al que pertenecen. Por ejemplo, `toUpperCase` es un método de una cadena.
 
 {{id "métodos_de_array"}}
 
-Este ejemplo demuestra dos métodos que puedes utilizar para manipular arrays:
+Este ejemplo muestra dos métodos que puedes utilizar para manipular arrays:
 
 ```
 let secuencia = [1, 2, 3];
@@ -150,31 +154,31 @@ El método `push` agrega valores al final de un array. El método `pop` hace lo
 
 {{index ["estructura de datos", pila]}}
 
-Estos nombres un tanto tontos son términos tradicionales para operaciones en una _((pila))_. Una pila, en programación, es una estructura de datos que te permite agregar valores a ella y sacarlos en el orden opuesto para que lo que se agregó último se elimine primero. Las pilas son comunes en programación; es posible que recuerdes la función ((call stack)) del [capítulo anterior](functions#stack), que es una instancia de la misma idea.
+Estos nombres medio tontos son términos tradicionales (en inglés) para operaciones en una _((pila))_. Una pila, en programación, es una estructura de datos que te permite agregar valores a ella y sacarlos en el orden inverso para que lo que se agregó último se elimine primero. Las pilas son algo común en programación —puede que recuerdes la pila de llamadas ((call stack)) de una función que vimos en el [capítulo anterior](functions#stack), que es un ejemplo de esta idea.
 
 ## Objetos
 
 {{index diario, "ejemplo weresquirrel", array, registro}}
 
-De vuelta al hombre-ardilla. Un conjunto de entradas de registro diario se puede representar como un array, pero las entradas no consisten solo en un número o una cadena, cada entrada necesita almacenar una lista de actividades y un valor booleano que indique si Jacques se convirtió en ardilla o no. Idealmente, nos gustaría agrupar estos elementos en un único valor y luego poner esos valores agrupados en un array de entradas de registro.
+De vuelta al hombre ardilla. Un conjunto de entradas del registro diario se puede representar como un array, pero las entradas no solo consisten en un número o una cadena —cada entrada necesita almacenar una lista de actividades y un valor booleano que indique si Jacques se convirtió en ardilla o no ese día. Idealmente, nos gustaría agrupar todo esto en un único valor y luego poner esos valores agrupados en un array de entradas de registro.
 
-Los valores del tipo ((object)) son colecciones arbitrarias de propiedades. Una forma de crear un objeto es usando llaves como una expresión:
+Los valores del tipo ((object)) son colecciones arbitrarias de propiedades. Una forma de crear un objeto es usando llaves como expresión.
 
 ```
-let dia1 = {
+let día1 = {
   hombreArdilla: false,
   eventos: ["trabajo", "tocó árbol", "pizza", "correr"]
 };
-console.log(dia1.hombreArdilla);
+console.log(día1.hombreArdilla);
 // → false
-console.log(dia1.lobo);
+console.log(día1.lobo);
 // → undefined
-dia1.lobo = false;
-console.log(dia1.lobo);
+día1.lobo = false;
+console.log(día1.lobo);
 // → false
 ```
 
-Dentro de las llaves, se escribe una lista de propiedades separadas por comas. Cada propiedad tiene un nombre seguido por dos puntos y un valor. Cuando un objeto se escribe en varias líneas, indentarlo como se muestra en este ejemplo ayuda a la legibilidad. Las propiedades cuyos nombres no son nombres de enlace válidos o números válidos deben ir entre comillas:
+Dentro de las llaves, se escribe una lista de propiedades separadas por comas. Cada propiedad tiene su nombre, seguido por dos puntos y un valor. Cuando un objeto se escribe en varias líneas, indentarlo como se muestra en este ejemplo ayuda a la legibilidad. Las propiedades cuyos nombres no son nombres de asociación válidos o números válidos deben ir entre comillas:
 
 ```
 let descripciones = {
@@ -183,15 +187,15 @@ let descripciones = {
 };
 ```
 
-Esto significa que las llaves tienen _dos_ significados en JavaScript. Al principio de una ((sentencia)), comienzan un ((bloque)) de sentencias. En cualquier otra posición, describen un objeto. Afortunadamente, rara vez es útil comenzar una sentencia con un objeto entre llaves, por lo que la ambigüedad entre estos dos casos no es gran problema. El único caso en el que esto surge es cuando quiere devolver un objeto desde una función flecha abreviada: no se puede escribir `n => {prop: n}`, ya que las llaves se interpretarán como el cuerpo de una función. En cambio, se debe poner un conjunto de paréntesis alrededor del objeto para dejar claro que es una expresión.
+Esto significa que las llaves tienen _dos_ significados en JavaScript. Al principio de una ((sentencia)), comienzan un ((bloque)) de sentencias. En cualquier otra posición, describen un objeto. Por suerte, rara vez es útil comenzar una sentencia con un objeto entre llaves, por lo que la ambigüedad entre estos dos casos no es un problema como tal. El único caso en el que se da algo así es cuando quierea devolver un objeto desde una función flecha abreviada: no se puede escribir `n => {prop: n}`, ya que las llaves se interpretarán como el cuerpo de una función. En cambio, se debe poner un conjunto de paréntesis alrededor del objeto para dejar claro que es una expresión.
 
-Al leer una propiedad que no existe, obtendrás el valor `undefined`.
+Leer una propiedad que no existe dará como resultado el valor `undefined`.
 
 Es posible asignar un valor a una expresión de propiedad con el operador `=`. Esto reemplazará el valor de la propiedad si ya existía o creará una nueva propiedad en el objeto si no existía.
 
-Para volver brevemente a nuestro modelo de tentáculos de ((enlace))s, los enlaces de propiedad son similares. _Agarran_ valores, pero otros enlaces y propiedades podrían estar aferrándose a esos mismos valores. Puedes pensar en los objetos como pulpos con cualquier cantidad de tentáculos, cada uno con un nombre escrito en él.
+Por volver un momento a nuestro modelo de tentáculos para las ((asociacion))es —las asociaciones de propiedad son parecidas. Estas _agarran_ valores, pero otras asociaciones y propiedades podrían estar aferrándose a esos mismos valores. Puedes pensar en los objetos como pulpos con una cantidad cualquiera de tentáculos, cada uno con un nombre escrito en él.
 
-El operador `delete` corta un tentáculo de dicho pulpo. Es un operador unario que, cuando se aplica a una propiedad de un objeto, eliminará la propiedad nombrada del objeto. Esto no es algo común de hacer, pero es posible.
+El operador `delete` corta un tentáculo de dicho pulpo. Es un operador unario que, cuando se aplica a una propiedad de un objeto, eliminará la propiedad del objeto que se ha nombrado. No es que se trate de algo común, pero se puede hacer.
 
 ```
 let unObjeto = {izquierda: 1, derecha: 2};
@@ -208,11 +212,11 @@ console.log("derecha" in unObjeto);
 
 {{index "operador in", [propiedad, "prueba de"], objeto}}
 
-El operador binario `in`, cuando se aplica a una cadena y a un objeto, te dice si ese objeto tiene una propiedad con ese nombre. La diferencia entre establecer una propiedad como `undefined` y realmente borrarla es que, en el primer caso, el objeto todavía _tiene_ la propiedad (simplemente no tiene un valor muy interesante), mientras que en el segundo caso la propiedad ya no está presente y `in` devolverá `false`.
+El operador binario `in`, cuando se aplica a una cadena y a un objeto, te dice si ese objeto tiene una propiedad con ese nombre. La diferencia entre establecer una propiedad como `undefined` y realmente borrarla es que, en el primer caso, el objeto todavía _tiene_ la propiedad (simplemente no tiene un valor muy interesante), mientras que en el segundo caso la propiedad ya no está presente e `in` devolverá `false`.
 
 {{index "función Object.keys"}}
 
-Para averiguar qué propiedades tiene un objeto, puedes utilizar la función `Object.keys`. Al darle la función un objeto, devolverá un array de cadenas: los nombres de las propiedades del objeto:
+Para averiguar qué propiedades tiene un objeto, puedes utilizar la función `Object.keys`. Dale a la función un objeto, y te devolverá un array de cadenas: los nombres de las propiedades del objeto.
 
 ```
 console.log(Object.keys({x: 0, y: 0, z: 2}));
@@ -230,11 +234,11 @@ console.log(objetoA);
 
 {{index array, "colección"}}
 
-Los arrays, entonces, son solo un tipo de objeto especializado para almacenar secuencias de cosas. Si evalúas `typeof []`, producirá `"object"`. Puedes visualizar los arrays como pulpos largos y planos con todos sus tentáculos en una fila ordenada, etiquetados con números.
+Los arrays, por tanto, no son más que un tipo de objeto especializado para almacenar secuencias de cosas. Si evalúas `typeof []`, producirá `"object"`. Puedes visualizar los arrays como pulpos largos y planos con todos sus tentáculos en una fila ordenada, etiquetados con números.
 
 {{index diario, "ejemplo de ardilla"}}
 
-Jacques representará el diario que lleva como un array de objetos:
+Jacques va a representar el diario que lleva como un array de objetos:
 
 ```{test: wrap}
 let diario = [
@@ -253,42 +257,42 @@ let diario = [
 
 ## Mutabilidad
 
-Pronto llegaremos a la programación real, pero primero, hay una pieza más de teoría para entender.
+Pronto llegaremos a la programación real, pero primero, hay una cosa más de la teoría que hay que saber.
 
 {{index mutabilidad, "efecto secundario", "número", cadena, booleano, [objeto, mutabilidad]}}
 
-Vimos que los valores de objetos pueden modificarse. Los tipos de valores discutidos en capítulos anteriores, como números, cadenas y booleanos, son todos _((inmutables))_—es imposible cambiar valores de esos tipos. Puedes combinarlos y derivar nuevos valores de ellos, pero al tomar un valor específico de cadena, ese valor siempre permanecerá igual. El texto dentro de él no puede ser cambiado. Si tienes una cadena que contiene `"gato"`, no es posible que otro código cambie un carácter en tu cadena para que diga `"rata"`.
+Hemos visto que los valores de objetos pueden modificarse. Los tipos de valores de los que hemos hablado en capítulos anteriores, como números, cadenas y booleanos, son todos _((inmutables))_ —es imposible cambiar valores de esos tipos. Puedes combinarlos y obtener nuevos valores a partir de ellos, pero cuando consideras un valor específico de cadena, ese valor siempre va a ser el mismo. El texto dentro de él no se puede cambiar. Si tienes una cadena que contiene `"gato"`, no es posible que otro código cambie un carácter en tu cadena para que diga `"rata"`.
 
-Los objetos funcionan de manera diferente. _Puedes_ cambiar sus propiedades, lo que hace que un valor de objeto tenga un contenido diferente en momentos diferentes.
+Los objetos funcionan de manera distinta. _Puedes_ cambiar sus propiedades, lo que hace que un valor de objeto vaya cambiando su contenido con el tiempo.
 
 {{index [objeto, identidad], identidad, ["organización", memoria], mutabilidad}}
 
-Cuando tenemos dos números, 120 y 120, podemos considerarlos precisamente el mismo número, tanto si se refieren a los mismos bits físicos como si no. Con los objetos, hay una diferencia entre tener dos referencias al mismo objeto y tener dos objetos diferentes que contienen las mismas propiedades. Considera el siguiente código:
+Cuando tenemos dos números, 120 y 120, podemos considerarlos precisamente el mismo número, tanto si se refieren físicamente a los mismos bits como si no. Con los objetos, hay una diferencia entre tener dos referencias al mismo objeto y tener dos objetos diferentes que contienen las mismas propiedades. Considera el siguiente código:
 
 ```
-let object1 = {value: 10};
-let object2 = object1;
-let object3 = {value: 10};
+let objeto1 = {valor: 10};
+let objeto2 = objeto1;
+let objeto3 = {valor: 10};
 
-console.log(object1 == object2);
+console.log(objeto1 == objeto2);
 // → true
-console.log(object1 == object3);
+console.log(objeto1 == objeto3);
 // → false
 
-object1.value = 15;
-console.log(object2.value);
+objeto1.valor = 15;
+console.log(objeto2.valor);
 // → 15
-console.log(object3.value);
+console.log(objeto3.valor);
 // → 10
 ```
 
 {{index "tentacle (analogy)", [binding, "model of"]}}
 
-Las asignaciones `object1` y `object2` contienen la _misma_ referencia al objeto, por lo que al cambiar `object1` también se cambia el valor de `object2`. Se dice que tienen la misma _identidad_. La asignación `object3` apunta a un objeto diferente, que inicialmente contiene las mismas propiedades que `object1` pero vive una vida separada.
+Las asignaciones `object1` y `object2` referencian al _mismo_ objeto, por lo que al cambiar `object1` también se cambia el valor de `object2`. Se dice que tienen la misma _identidad_. La asignación `object3` apunta a un objeto diferente, que inicialmente contiene las mismas propiedades que `object1` pero tiene su vida por separado.
 
 {{index "const keyword", "let keyword", [binding, "as state"]}}
 
-Las asignaciones pueden ser modificables o constantes, pero esto es independiente de cómo se comportan sus valores. Aunque los valores numéricos no cambian, puedes utilizar una asignación `let` para hacer un seguimiento de un número que cambia al cambiar el valor al que apunta la asignación. De manera similar, aunque una asignación `const` a un objeto en sí no puede cambiarse y seguirá apuntando al mismo objeto, los _contenidos_ de ese objeto pueden cambiar.
+Las asociaciones pueden ser modificables o constantes, pero esto es independiente de cómo se comportan sus valores. Por mucho que los valores numéricos no cambien, siempre puedes utilizar una asignación usando `let` para modelar el seguimiento de un número que cambia simplemente cambiando el valor al que apunta la asignación. Del mismo modo, aunque una asignación a un objeto con `const` en sí no puede cambiarse y seguirá apuntando siempre al mismo objeto, los _contenidos_ de ese objeto sí que pueden cambiar.
 
 ```{test: no}
 const score = {visitors: 0, home: 0};
@@ -300,7 +304,7 @@ score = {visitors: 1, home: 1};
 
 {{index "== operator", [comparison, "of objects"], "deep comparison"}}
 
-Cuando se comparan objetos con el operador `==` de JavaScript, se compara por identidad: producirá `true` solo si ambos objetos son exactamente el mismo valor. Comparar objetos diferentes devolverá `false`, incluso si tienen propiedades idénticas. No hay una operación de comparación "profunda" incorporada en JavaScript que compare objetos por contenido, pero es posible escribirla tú mismo (lo cual es uno de los [ejercicios](data#exercise_deep_compare) al final de este capítulo).
+Cuando se comparan objetos con el operador `==` de JavaScript, se compara por identidad: obtendremos `true` solo si ambos objetos son exactamente el mismo valor. Comparar objetos diferentes devolverá `false`, incluso aunque tengan propiedades idénticas. No hay una operación de comparación "profunda" incorporada en JavaScript que compare objetos por contenido, pero podrías escribirla tú mismo (lo cual es uno de los [ejercicios](data#exercise_deep_compare) al final de este capítulo).
 
 ## El diario del licántropo
 
@@ -309,43 +313,47 @@ Cuando se comparan objetos con el operador `==` de JavaScript, se compara por id
 Jacques inicia su intérprete de JavaScript y configura el entorno que necesita para mantener su ((diario)):
 
 ```{includeCode: true}
-let journal = [];
+let diario = [];
 
-function addEntry(events, squirrel) {
-  journal.push({events, squirrel});
+function añadirEntrada(eventos, ardilla) {
+  diario.push({eventos, ardilla});
 }
 ```
 
 {{index [braces, object], "{} (object)", [property, definition]}}
 
-Observa que el objeto agregado al diario luce un poco extraño. En lugar de declarar propiedades como `events: events`, simplemente se da un nombre de propiedad: `events`. Esta es una forma abreviada que significa lo mismo: si un nombre de propiedad en notación de llaves no va seguido de un valor, su valor se toma del enlace con el mismo nombre.
+Fíjate en que el objeto agregado al diario tiene una pinta un poco rara. En vez de declarar propiedades como `eventos: eventos`, simplemente se da un nombre de propiedad: `eventos`. Esta es una forma abreviada que significa lo mismo: si un nombre de propiedad en notación de llaves no va seguido de un valor, su valor se saca del enlace con el mismo nombre.
 
-Cada noche a las 10 p.m., o a veces a la mañana siguiente después de bajar de la repisa superior de su estantería, Jacques registra el día:
+Cada noche a las 10 p.m. —o a veces a la mañana siguiente después de bajar de la repisa superior de su estantería—, Jacques registra el día:
 
 ```
-addEntry(["work", "touched tree", "pizza", "running",
-          "television"], false);
-addEntry(["work", "ice cream", "cauliflower", "lasagna",
-          "touched tree", "brushed teeth"], false);
-addEntry(["weekend", "cycling", "break", "peanuts",
-          "beer"], true);
+añadirEntrada(["trabajo", "tocó árbol", "pizza", 
+          "correr", "televisión"], false);
+añadirEntrada(["trabajo", "helado", "coliflor", "lasaña",
+          "tocó árbol", "se cepilló los dientes"], false);
+añadirEntrada(["fin de semana", "ciclismo", "descanso", "cacahuetes",
+          "cerveza"], true);
 ```
 
-Una vez que tiene suficientes puntos de datos, tiene la intención de utilizar estadísticas para descubrir qué eventos pueden estar relacionados con las transformaciones en ardilla.
+Una vez que tenga suficientes datos, tiene la intención de hacer estadística para descubrir cuáles de esos eventos pueden estar relacionados con las ardillificaciones.
 
 {{index "correlación"}}
 
-La _correlación_ es una medida de la ((dependencia)) entre variables estadísticas. Una variable estadística no es exactamente igual a una variable de programación. En estadística, típicamente tienes un conjunto de _mediciones_, y cada variable se mide para cada medición. La correlación entre variables suele expresarse como un valor que va de -1 a 1. Una correlación de cero significa que las variables no están relacionadas. Una correlación de 1 indica que las dos están perfectamente relacionadas: si conoces una, también conoces la otra. Un -1 también significa que las variables están perfectamente relacionadas pero son opuestas: cuando una es verdadera, la otra es falsa.
+La _correlación_ es una medida de la ((dependencia)) entre variables estadísticas. Una variable estadística no es exactamente lo mismo que una variable de programación. En estadística, normalmente tienes un conjunto de _mediciones_. En cada medición se miden todas las variables. La correlación entre variables suele expresarse como un valor entre -1 y 1. Una correlación de cero significa que las variables no tienen relación. Una correlación de 1 indica que las dos están perfectamente relacionadas: si conoces una, también conoces la otra. Un -1 también significa que las variables están perfectamente relacionadas pero son opuestas: cuando una es verdadera, la otra es falsa.
 
 {{index del "coeficiente phi"}}
 
-Para calcular la medida de correlación entre dos variables booleanas, podemos utilizar el _coeficiente phi_ (_ϕ_). Esta es una fórmula cuya entrada es una ((tabla de frecuencias)) que contiene la cantidad de veces que se observaron las diferentes combinaciones de las variables. La salida de la fórmula es un número entre -1 y 1 que describe la correlación.
+Para calcular la correlación entre dos variables booleanas, podemos utilizar el _coeficiente phi_ (_ϕ_). Este es una función cuya entrada es una ((tabla de frecuencias)) que contiene la cantidad de veces que se han observado las diferentes combinaciones de las variables. La salida de la fórmula es un número entre -1 y 1 que describe la correlación.
+
+{{note "**N. del T.:** Aquí estamos usando la palabra **función** en el sentido matemático, evitando así el uso de la palabra fórmula para referirse a esta. Por supuesto, dicha función se calcula a través de una **fórmula**, o un cálculo matemático."}}
 
-Podríamos tomar el evento de comer ((pizza)) y ponerlo en una tabla de frecuencias como esta, donde cada número indica la cantidad de veces que ocurrió esa combinación en nuestras mediciones.
+Podríamos considerar el evento de comer ((pizza)) y apuntarlo en una tabla de frecuencias como esta, donde cada número indica la cantidad de veces que ocurrido esa combinación en nuestras mediciones.
 
-{{figure {url: "img/pizza-squirrel.svg", alt: "Una tabla de dos por dos que muestra la variable pizza en el eje horizontal y la variable ardilla en el eje vertical. Cada celda muestra cuántas veces ocurrió esa combinación. En 76 casos, ninguna ocurrió. En 9 casos, solo la pizza era verdadera. En 4 casos, solo la ardilla era verdadera. Y en un caso ambas ocurrieron.", width: "7cm"}}}
+{{figure {url: "img/pizza-squirrel.svg", alt: "Una tabla de tamaño dos por dos que muestra la variable pizza en el eje horizontal y la variable ardilla en el eje vertical. Cada celda muestra cuántas veces ocurrió esa combinación. En 76 casos, no ha ocurrido ninguna. En 9 casos, solo se dio el suceso de comer pizza. En 4 casos, solo se dio el suceso de transformarse en ardilla. Y en un caso ambas cosas sucedieron a la vez.", width: "7cm"}}}
 
-Si llamamos a esa tabla _n_, podemos calcular _ϕ_ utilizando la siguiente fórmula:
+{{note "**N. del T.:** squirrel significa ardilla. Las filas representan los posibles valores para el suceso de transformarse en ardilla, mientras que las columnas representan si se comió pizza ese día o no."}}
+
+Si llamamos _n_ a esta tabla, podemos calcular el _ϕ_ asociado utilizando la siguiente fórmula:
 
 {{if html
 
@@ -359,19 +367,21 @@ if}}
 
 if}}
 
-(Si en este punto estás dejando el libro para concentrarte en un terrible flashback a la clase de matemáticas de décimo grado, ¡espera! No pretendo torturarte con interminables páginas de notación críptica, solo es esta fórmula por ahora. Y incluso con esta, todo lo que haremos es convertirla en JavaScript).
+—Si llegados a este punto estás tirando el libro mientras te concentras en un terrible flashback de la clase de matemáticas de 4º de ESO, ¡espera! No busco torturarte con interminables páginas de notación críptica. Por ahora será solo esta fórmula. E incluso con esta, lo único que vamos a hacer es convertirla en JavaScript.
+
+La notación [_n_~01~]{if html}[[$n_{01}$]{latex}]{if tex} indica la cantidad de mediciones donde la primera variable (ardilla) es falsa (0) y la segunda variable (pizza) es verdadera (1). En nuestra tabla de pizza, [_n_~01~]{if html}[[$n_{01}$]{latex}]{if tex} es 9.
 
-La notación [_n_~01~]{if html}[[$n_{01}$]{latex}]{if tex} indica la cantidad de mediciones donde la primera variable (ardilla) es falsa (0) y la segunda variable (pizza) es verdadera (1). En la tabla de pizza, [_n_~01~]{if html}[[$n_{01}$]{latex}]{if tex} es 9.El valor [_n_~1•~]{if html}[[$n_{1\bullet}$]{latex}]{if tex} se refiere a la suma de todas las mediciones donde la primera variable es verdadera, que es 5 en el ejemplo de la tabla. De manera similar, [_n_~•0~]{if html}[[$n_{\bullet0}$]{latex}]{if tex} se refiere a la suma de las mediciones donde la segunda variable es falsa.
+El valor [_n_~1•~]{if html}[[$n_{1\bullet}$]{latex}]{if tex} se refiere a la suma de todas las mediciones donde la primera variable es verdadera, que,  en el ejemplo de la tabla, es 5. De manera similar, [_n_~•0~]{if html}[[$n_{\bullet0}$]{latex}]{if tex} se refiere a la suma de las mediciones donde la segunda variable es falsa.
 
 {{index "correlación", "coeficiente phi"}}
 
-Entonces para la tabla de pizza, la parte encima de la línea de división (el dividendo) sería 1×76−4×9 = 40, y la parte debajo de ella (el divisor) sería la raíz cuadrada de 5×85×10×80, o [√340,000]{if html}[[$\sqrt{340,000}$]{latex}]{if tex}. Esto da un valor de _ϕ_ ≈ 0.069, que es muy pequeño. Comer ((pizza)) no parece tener influencia en las transformaciones.
+Así que para la tabla de pizza, la parte de arriba de la fracción (el dividendo) sería 1×76−4×9 = 40, y la parte de abajo (el divisor) sería la raíz cuadrada de 5×85×10×80, o [√340,000]{if html}[[$\sqrt{340,000}$]{latex}]{if tex}. Así que _ϕ_ ≈ 0.069, que es un valor muy pequeño. Comer ((pizza)) no parece influir en las transformaciones.
 
 ## Calculando la correlación
 
 {{index [array, "como tabla"], ["anidación", "de arrays"]}}
 
-Podemos representar una tabla dos por dos en JavaScript con un array de cuatro elementos (`[76, 9, 4, 1]`). También podríamos usar otras representaciones, como un array que contiene dos arrays de dos elementos cada uno (`[[76, 9], [4, 1]]`) o un objeto con nombres de propiedades como `"11"` y `"01"`, pero el array plano es simple y hace que las expresiones que acceden a la tabla sean agradabemente cortas. Interpretaremos los índices del array como números binarios de dos bits, donde el dígito más a la izquierda (más significativo) se refiere a la variable ardilla y el dígito más a la derecha (menos significativo) se refiere a la variable de evento. Por ejemplo, el número binario `10` se refiere al caso donde Jacques se transformó en ardilla, pero el evento (digamos, "pizza") no ocurrió. Esto sucedió cuatro veces. Y como `10` en binario es 2 en notación decimal, almacenaremos este número en el índice 2 del array.
+Podemos representar una tabla dos por dos en JavaScript con un array de cuatro elementos (`[76, 9, 4, 1]`). También podríamos usar otras representaciones, como un array que contiene dos arrays de dos elementos cada uno (`[[76, 9], [4, 1]]`) o un objeto con nombres de propiedades como `"11"` y `"01"`, pero el primer array que hemos propuesto es simple y hace que las expresiones que acceden a la tabla sean agradablemente cortas. Vamos a interpretar los índices del array como números de dos bits en binario, donde el dígito más a la izquierda (el más significativo) se refiere a la variable ardilla y el dígito más a la derecha (el menos significativo) se refiere a la variable de evento. Por ejemplo, el número binario `10` se refiere al caso donde Jacques se transforma en ardilla, pero el evento (digamos, "pizza") no ocurre. Esto sucede cuatro veces. Como el `10` es la representación en binario del número 2, almacenaremos este número en el índice 2 del array.
 
 {{index "coeficiente phi", "función phi"}}
 
@@ -380,12 +390,12 @@ Podemos representar una tabla dos por dos en JavaScript con un array de cuatro e
 Esta es la función que calcula el coeficiente _ϕ_ a partir de dicho array:
 
 ```{includeCode: strip_log, test: clip}
-function phi(table) {
-  return (table[3] * table[0] - table[2] * table[1]) /
-    Math.sqrt((table[2] + table[3]) *
-              (table[0] + table[1]) *
-              (table[1] + table[3]) *
-              (table[0] + table[2]));
+function phi(tabla) {
+  return (tabla[3] * tabla[0] - tabla[2] * tabla[1]) /
+    Math.sqrt((tabla[2] + tabla[3]) *
+              (tabla[0] + tabla[1]) *
+              (tabla[1] + tabla[3]) *
+              (tabla[0] + tabla[2]));
 }
 
 console.log(phi([76, 9, 4, 1]));
@@ -394,41 +404,41 @@ console.log(phi([76, 9, 4, 1]));
 
 {{index "raíz cuadrada", "función Math.sqrt"}}
 
-Esta es una traducción directa de la fórmula de _ϕ_ a JavaScript. `Math.sqrt` es la función de raíz cuadrada, como se provee en el objeto `Math` en un entorno estándar de JavaScript. Debemos agregar dos campos de la tabla para obtener campos como [n~1•~]{if html}[[$n_{1\bullet}$]{latex}]{if tex} porque las sumas de filas o columnas no se almacenan directamente en nuestra estructura de datos.
+Esta es una traducción directa de la fórmula de _ϕ_ a JavaScript. `Math.sqrt` es la función de raíz cuadrada, que viene incluida en el objeto `Math` en un entorno estándar de JavaScript. Para obtener campos de la forma [n~1•~]{if html}[[$n_{1\bullet}$]{latex}]{if tex} tenemos que sumar los correspondientes pares de la tabla, ya que las sumas de filas o columnas no se almacenan directamente en nuestra estructura de datos.
 
 {{index "conjunto de datos JOURNAL"}}
 
-Jacques mantiene su diario por tres meses. El ((conjunto de datos)) resultante está disponible en el [sandbox de código](https://eloquentjavascript.net/code#4) para este capítulo[ ([_https://eloquentjavascript.net/code#4_](https://eloquentjavascript.net/code#4))]{if book}, donde se almacena en el vínculo `JOURNAL`, y en un archivo descargable [aquí](https://eloquentjavascript.net/code/journal.js).
+Jacques escribe en su diario durante tres meses. El ((conjunto de datos)) resultante está disponible en el [sandbox de código](https://eloquentjavascript.net/code#4) para este capítulo[ ([_https://eloquentjavascript.net/code#4_](https://eloquentjavascript.net/code#4))]{if book} —donde se almacena en la variable `JOURNAL`— y en un [archivo descargable](https://eloquentjavascript.net/code/journal.js).
 
 {{index "función tableFor"}}
 
-Para extraer una tabla dos por dos para un evento específico del diario, debemos recorrer todas las entradas y contar cuántas veces ocurre el evento en relación con las transformaciones a ardilla:
+Para montar una tabla dos por dos sobre un evento específico del diario, tenemos que recorrer todas las entradas y contar cuántas veces ocurre el evento en relación con las transformaciones en ardilla:
 
 ```{includeCode: strip_log}
-function tableFor(event, journal) {
-  let table = [0, 0, 0, 0];
-  for (let i = 0; i < journal.length; i++) {
-    let entry = journal[i], index = 0;
-    if (entry.events.includes(event)) index += 1;
-    if (entry.squirrel) index += 2;
-    table[index] += 1;
+function tablaPara(evento, diario) {
+  let tabla = [0, 0, 0, 0];
+  for (let i = 0; i < diario.length; i++) {
+    let entrada = diario[i], índice = 0;
+    if (entrada.eventos.includes(evento)) índice += 1;
+    if (entrada.ardilla) índice += 2;
+    tabla[índice] += 1;
   }
-  return table;
+  return tabla;
 }
 
-console.log(tableFor("pizza", JOURNAL));
+console.log(tablaPara("pizza", JOURNAL));
 // → [76, 9, 4, 1]
 ```
 
 {{index [array, searching], "includes method"}}
 
-Los arrays tienen un método `includes` que comprueba si un valor dado existe en el array. La función utiliza esto para determinar si el nombre del evento en el que está interesado forma parte de la lista de eventos de un día dado.
+Los arrays tienen un método `includes` que comprueba si un valor dado existe en el array. La función utiliza esto para determinar si el nombre del evento en el que está interesada forma parte de la lista de eventos de un día dado.
 
 {{index [array, indexing]}}
 
-El cuerpo del bucle en `tableFor` determina en qué caja de la tabla cae cada entrada del diario, verificando si la entrada contiene el evento específico en el que está interesado y si el evento ocurre junto con un incidente de ardilla. Luego, el bucle suma uno a la caja correcta de la tabla.
+El cuerpo del bucle en la función `tablaPara` determina en qué parte de la tabla cae cada entrada del diario, verificando si la entrada contiene el evento específico en el que está interesada y si el evento ocurre un día en el que Jacques se transforma en ardilla. Luego, el bucle suma uno a la caja correcta de la tabla.
 
-Ahora tenemos las herramientas necesarias para calcular correlaciones individuales. El único paso restante es encontrar una correlación para cada tipo de evento que se registró y ver si algo destaca.
+Ahora tenemos las herramientas necesarias para calcular correlaciones individuales. El único paso restante es encontrar una correlación para cada tipo de evento que se registró y ver si hay aglo que destaque.
 
 {{id for_of_loop}}
 
@@ -436,22 +446,22 @@ Ahora tenemos las herramientas necesarias para calcular correlaciones individual
 
 {{index "for loop", loop, [array, iteration]}}
 
-En la función `tableFor`, hay un bucle como este:
+En la función `tablaPara`, hay un bucle como este:
 
 ```
 for (let i = 0; i < JOURNAL.length; i++) {
-  let entry = JOURNAL[i];
-  // Hacer algo con entry
+  let entrada = JOURNAL[i];
+  // Hacer algo con entrada
 }
 ```
 
-Este tipo de bucle es común en el JavaScript clásico; recorrer arrays elemento por elemento es algo que se hace con frecuencia, y para hacerlo se recorre un contador sobre la longitud del array y se selecciona cada elemento por turno.
+Este tipo de bucle es común en el JavaScript clásico —recorrer arrays elemento a elemento es algo que se hace con frecuencia, y para hacerlo se recorre un contador sobre la longitud del array y se selecciona el elemento de turno.
 
-Hay una forma más sencilla de escribir tales bucles en JavaScript moderno:
+En JavaScript moderno hay una forma más sencilla de escribir tales bucles:
 
 ```
-for (let entry of JOURNAL) {
-  console.log(`${entry.events.length} eventos.`);
+for (let entrada of JOURNAL) {
+  console.log(`${entrada.eventos.length} eventos.`);
 }
 ```
 
@@ -465,34 +475,34 @@ Cuando un bucle `for` usa la palabra `of` después de la definición de su varia
 
 {{index journal, "ejemplo de ardilla", "función journalEvents"}}
 
-Necesitamos calcular una correlación para cada tipo de evento que ocurre en el conjunto de datos. Para hacerlo, primero necesitamos _encontrar_ cada tipo de evento.
+Necesitamos calcular una correlación para cada tipo de evento que aparece en el conjunto de datos. Para hacerlo, primero necesitamos _encontrar_ todos los tipos de evento.
 
 {{index "includes method", "push method"}}
 
 ```{includeCode: "strip_log"}
-function journalEvents(journal) {
-  let events = [];
-  for (let entry of journal) {
-    for (let event of entry.events) {
-      if (!events.includes(event)) {
-        events.push(event);
+function eventosDiario(diario) {
+  let eventos = [];
+  for (let entrada of diario) {
+    for (let evento of entrada.eventos) {
+      if (!eventos.includes(evento)) {
+        eventos.push(evento);
       }
     }
   }
-  return events;
+  return eventos;
 }
 
-console.log(journalEvents(JOURNAL));
+console.log(eventosDiario(JOURNAL));
 // → ["zanahoria", "ejercicio", "fin de semana", "pan", …]
 ```
 
-Agregando los nombres de cualquier evento que no estén en él al array `events`, la función recopila todos los tipos de eventos.
+La función recopila todos los tipos de evento añadiendo los nombres de cualquier evento que no esté ya en el array `events`.
 
 Usando esa función, podemos ver todas las correlaciones:
 
 ```{test: no}
-for (let event of journalEvents(JOURNAL)) {
-  console.log(event + ":", phi(tableFor(event, JOURNAL)));
+for (let evento of eventosDiario(JOURNAL)) {
+  console.log(evento + ":", phi(tablaPara(evento, JOURNAL)));
 }
 // → zanahoria:   0.0140970969
 // → ejercicio: 0.0685994341
@@ -502,13 +512,13 @@ for (let event of journalEvents(JOURNAL)) {
 // y así sucesivamente...
 ```
 
-La mayoría de las correlaciones parecen estar cerca de cero. Comer zanahorias, pan o pudín aparentemente no desencadena la licantropía de las ardillas. Las transformaciones parecen ocurrir un poco más a menudo los fines de semana. Filtraremos los resultados para mostrar solo correlaciones mayores que 0.1 o menores que -0.1:
+La mayoría de las correlaciones parecen estar cerca de cero. Comer zanahorias, pan o pudín aparentemente no desencadenan la _ardillolicantropía_. Las transformaciones parecen ocurrir un poco más a menudo en fines de semana. Filtraremos los resultados para mostrar solo correlaciones mayores que 0.1 o menores que -0.1:
 
 ```{test: no, startCode: true}
-for (let event of journalEvents(JOURNAL)) {
-  let correlation = phi(tableFor(event, JOURNAL));
-  if (correlation > 0.1 || correlation < -0.1) {
-    console.log(event + ":", correlation);
+for (let evento of eventosDiario(JOURNAL)) {
+  let correlación = phi(tablaPara(evento, JOURNAL));
+  if (correlación > 0.1 || correlación < -0.1) {
+    console.log(evento + ":", correlación);
   }
 }
 // → fin de semana:        0.1371988681
@@ -520,38 +530,38 @@ for (let event of journalEvents(JOURNAL)) {
 // → cacahuetes:        0.5902679812
 ```
 
-¡Ajá! Hay dos factores con una correlación claramente más fuerte que los demás. Comer cacahuetes tiene un fuerte efecto positivo en la posibilidad de convertirse en una ardilla, mientras que cepillarse los dientes tiene un efecto negativo significativo.
+¡Ajá! Hay dos factores con una correlación claramente más fuerte que los demás. Comer cacahuetes tiene un fuerte efecto positivo en la posibilidad de convertirse en ardilla, mientras que cepillarse los dientes tiene un significante efecto negativo.
 
 Interesante. Intentemos algo:
 
 ```
-for (let entry of JOURNAL) {
-  if (entry.events.includes("cacahuetes") &&
-     !entry.events.includes("cepillarse los dientes")) {
-    entry.events.push("dientes de cacahuate");
+for (let entrada of JOURNAL) {
+  if (entrada.eventos.includes("cacahuetes") &&
+     !entrada.eventos.includes("cepillarse los dientes")) {
+    entrada.eventos.push("cacahuate en los dientes");
   }
 }
-console.log(phi(tableFor("dientes de cacahuate", JOURNAL)));
+console.log(phi(tablaPara("cacahuate en los dientes", JOURNAL)));
 // → 1
 ```
 
-Ese es un resultado sólido. El fenómeno ocurre precisamente cuando Jacques come cacahuetes y no se cepilla los dientes. Si tan solo no fuera tan descuidado con la higiene dental, ni siquiera se habría dado cuenta de su aflicción.
+Ese es un resultado sólido. El fenómeno ocurre precisamente cuando Jacques come cacahuetes y no se cepilla los dientes. Si no fuera tan descuidado con la higiene dental, ni siquiera se habría dado cuenta de su trastorno.
 
 Sabiendo esto, Jacques deja de comer cacahuetes por completo y descubre que sus transformaciones se detienen.
 
 {{index "ejemplo de hombre ardilla"}}
 
-Pero solo pasan unos pocos meses antes de que se dé cuenta de que algo falta en esta forma de vivir completamente humana. Sin sus aventuras salvajes, Jacques apenas se siente vivo. Decide que prefiere ser un animal salvaje a tiempo completo. Después de construir una hermosa casita en un árbol en el bosque y equiparla con un dispensador de mantequilla de cacahuate y un suministro de diez años de mantequilla de cacahuate, cambia de forma por última vez y vive la corta y enérgica vida de una ardilla.
+Pero solo pasan unos pocos meses antes de que se dé cuenta de que le falta algo en esta forma de vivir completamente humana. Sin sus aventuras salvajes, Jacques apenas se siente vivo. Decide que prefiere ser un animal salvaje a tiempo completo. Después de construir una hermosa casita en un árbol del bosque y equiparla con un dispensador de mantequilla de cacahuate con diez años de suministro, cambia de forma por última vez y vive la corta y enérgica vida de una ardilla.
 
-## Más arreología
+## Más arreglología
 
 {{index [array, "métodos"], ["método", array]}}
 
-Antes de terminar el capítulo, quiero presentarte algunos conceptos más relacionados con objetos. Comenzaré presentando algunos métodos de array generalmente útiles.
+Antes de terminar el capítulo, quiero presentarte algunos conceptos más relacionados con objetos. Comenzaré presentando algunos métodos generalmente útiles de los arrays.
 
 {{index "método push", "método pop", "método shift", "método unshift"}}
 
-Vimos `push` y `pop`, que agregan y eliminan elementos al final de un array, [anteriormente](data#array_methods) en este capítulo. Los métodos correspondientes para agregar y eliminar cosas al principio de un array se llaman `unshift` y `shift`.
+[Anteriormente](data#array_methods) en este capítulo, vimos `push` y `pop`, que agregan y eliminan elementos al final de un array. Los métodos correspondientes para agregar y eliminar cosas al principio de un array se llaman `unshift` y `shift`.
 
 ```
 let listaDeTareas = [];
@@ -568,11 +578,11 @@ function recordarUrgente(tarea) {
 
 {{index "ejemplo de gestión de tareas"}}
 
-Este programa gestiona una cola de tareas. Agregas tareas al final de la cola llamando a `recordar("comestibles")`, y cuando estás listo para hacer algo, llamas a `obtenerTarea()` para obtener (y eliminar) el primer elemento de la cola. La función `recordarUrgente` también agrega una tarea pero la agrega al principio en lugar de al final de la cola.
+Este programa gestiona una cola de tareas. Agregas tareas al final de la cola llamando a `recordar("compras")`, y cuando estás listo para hacer algo, llamas a `obtenerTarea()` para obtener (y eliminar) el primer elemento de la cola. La función `recordarUrgente` también agrega una tarea pero la agrega al principio en lugar de al final de la cola.
 
 {{index [array, searching], "indexOf method", "lastIndexOf method"}}
 
-Para buscar un valor específico, los arrays proporcionan un método `indexOf`. Este método busca a través del array desde el principio hasta el final y devuelve el índice en el que se encontró el valor solicitado, o -1 si no se encontró. Para buscar desde el final en lugar de desde el principio, existe un método similar llamado `lastIndexOf`:
+Para buscar un valor específico, los arrays proporcionan un método `indexOf`. Este método busca a través del array desde el principio hasta el final y devuelve el primer índice en el que se encontró el valor solicitado, o -1 si no se encontró. Para buscar desde el final en lugar de desde el principio, existe un método similar llamado `lastIndexOf`:
 
 ```
 console.log([1, 2, 3, 2, 1].indexOf(2));
@@ -585,7 +595,7 @@ Tanto `indexOf` como `lastIndexOf` admiten un segundo argumento opcional que ind
 
 {{index "slice method", [array, indexing]}}
 
-Otro método fundamental de los arrays es `slice`, que toma índices de inicio y fin y devuelve un array que solo contiene los elementos entre ellos. El índice de inicio es inclusivo, mientras que el índice de fin es exclusivo.
+Otro método fundamental de los arrays es `slice`, que recibe un índice inicial y otro final y devuelve un array que solo contiene los elementos entre ellos. El índice de inicio es inclusivo, mientras que el índice de fin es exclusivo.
 
 ```
 console.log([0, 1, 2, 3, 4].slice(2, 4));
@@ -602,24 +612,24 @@ Cuando no se proporciona el índice de fin, `slice` tomará todos los elementos
 
 El método `concat` se puede usar para concatenar arrays y crear un nuevo array, similar a lo que el operador `+` hace para las strings.
 
-El siguiente ejemplo muestra tanto `concat` como `slice` en acción. Toma un array y un índice y devuelve un nuevo array que es una copia del array original con el elemento en el índice dado eliminado:
+El siguiente ejemplo muestra tanto `concat` como `slice` en acción. Toma un array y un índice y devuelve un nuevo array que es una copia del array original sin el elemento correspondiente al índice dado:
 
 ```
-function remove(array, index) {
-  return array.slice(0, index)
-    .concat(array.slice(index + 1));
+function eliminar(array, índice) {
+  return array.slice(0, índice)
+    .concat(array.slice(índice + 1));
 }
-console.log(remove(["a", "b", "c", "d", "e"], 2));
+console.log(eliminar(["a", "b", "c", "d", "e"], 2));
 // → ["a", "b", "d", "e"]
 ```
 
-Si le pasas a `concat` un argumento que no es un array, ese valor se agregará al nuevo array como si fuera un array de un solo elemento.
+Si le pasas a `concat` un argumento que no es un array, ese valor se agregará al nuevo array creado por `concat` como si fuera un array de un solo elemento.
 
 ## Strings y sus propiedades
 
 {{index [string, properties]}}
 
-Podemos acceder a propiedades como `length` y `toUpperCase` en valores de tipo string. Pero si intentamos añadir una nueva propiedad, esta no se conserva.
+Podemos acceder a propiedades como `length` y `toUpperCase` en valores de tipo cadena (string). Pero si intentamos añadir una nueva propiedad, esta no se conserva.
 
 ```
 let kim = "Kim";
@@ -628,38 +638,38 @@ console.log(kim.age);
 // → undefined
 ```
 
-Los valores de tipo string, number y Boolean no son objetos, y aunque el lenguaje no se queja si intentas establecer nuevas propiedades en ellos, en realidad no almacena esas propiedades. Como se mencionó anteriormente, dichos valores son inmutables y no pueden ser modificados.
+Los valores de tipo string, number y Boolean no son objetos y, aunque el lenguaje no se queja si intentas establecer nuevas propiedades en ellos, en realidad no almacena esas propiedades. Como se dijo antes, dichos valores son inmutables y no pueden ser modificados.
 
 {{index [string, methods], "slice method", "indexOf method", [string, searching]}}
 
-Pero estos tipos tienen propiedades integradas. Cada valor string tiene varios métodos. Algunos muy útiles son `slice` e `indexOf`, que se parecen a los métodos de arrays del mismo nombre:
+Pero estos tipos tienen propiedades integradas. Cada valor de tipo string tiene varios métodos. Algunos muy útiles son `slice` e `indexOf`, que se parecen a los métodos de arrays del mismo nombre:
 
 ```
-console.log("coconuts".slice(4, 7));
-// → nut
-console.log("coconut".indexOf("u"));
-// → 5
+console.log("cocos".slice(2, 4));
+// → co
+console.log("coco".indexOf("o"));
+// → 1
 ```
 
 Una diferencia es que el `indexOf` de un string puede buscar un string que contenga más de un carácter, mientras que el método correspondiente de arrays busca solo un elemento:
 
 ```
-console.log("one two three".indexOf("ee"));
-// → 11
+console.log("me gusta leer".indexOf("ee"));
+// → 10
 ```
 
 {{index [whitespace, trimming], "trim method"}}
 
-El método `trim` elimina los espacios en blanco (espacios, saltos de línea, tabulaciones y caracteres similares) del principio y final de una cadena:
+El método `trim` elimina los espacios en blanco (espacios, saltos de línea, tabulaciones y caracteres similares) del principio y el final de una cadena:
 
 ```
-console.log("  okay \n ".trim());
-// → okay
+console.log("  de acuerdo \n ".trim());
+// → de acuerdo
 ```
 
 {{id padStart}}
 
-La función `zeroPad` del [capítulo anterior](functions) también existe como un método. Se llama `padStart` y recibe la longitud deseada y el carácter de relleno como argumentos:
+La función `rellenarConCeros` del [capítulo anterior](functions) también existe como un método. Se llama `padStart` y recibe la longitud deseada y el caracter de relleno como argumentos:
 
 ```
 console.log(String(6).padStart(3, "0"));
@@ -673,12 +683,12 @@ console.log(String(6).padStart(3, "0"));
 Puedes dividir una cadena en cada ocurrencia de otra cadena con `split` y unirla nuevamente con `join`:
 
 ```
-let sentence = "Secretarybirds specialize in stomping";
-let words = sentence.split(" ");
-console.log(words);
-// → ["Secretarybirds", "specialize", "in", "stomping"]
-console.log(words.join(". "));
-// → Secretarybirds. specialize. in. stomping
+let frase = "El pájaro secretario se especializa en pisotear";
+let palabras = frase.split(" ");
+console.log(palabras);
+// → ["El", "pájaro", "secretario", "se", "especializa", "en", "pisotear"]
+console.log(palabras.join(". "));
+// → El. pájaro. secretario. se. especializa. en. pisotear
 ```
 
 {{index "repeat method"}}
@@ -695,34 +705,34 @@ console.log("LA".repeat(3));
 Ya hemos visto la propiedad `length` del tipo string. Acceder a los caracteres individuales en una cadena se parece a acceder a los elementos de un array (con una complicación que discutiremos en el [Capítulo ?](higher_order#code_units)).
 
 ```
-let string = "abc";
-console.log(string.length);
+let cadena = "abc";
+console.log(cadena.length);
 // → 3
-console.log(string[1]);
+console.log(cadena[1]);
 // → b
 ```
 
 {{id rest_parameters}}
 
-## Parámetros restantes
+## Parámetros Rest
 
 {{index "Math.max function", "period character", "max example", spread, [array, "of rest arguments"]}}
 
-Puede ser útil para una función aceptar cualquier cantidad de ((argumento)s). Por ejemplo, `Math.max` calcula el máximo de _todos_ los argumentos que se le pasan. Para escribir una función así, colocas tres puntos antes del último ((parámetro)) de la función, de esta manera:
+Puede ser útil para una función aceptar una cantidad cualquiera de ((argumento))s. Por ejemplo, `Math.max` calcula el máximo de entre _todos_ los argumentos que se le pasan. Para escribir una función así, colocas tres puntos antes del último ((parámetro)) de la función, de esta manera:
 
 ```{includeCode: strip_log}
-function max(...numbers) {
-  let result = -Infinity;
-  for (let number of numbers) {
-    if (number > result) result = number;
+function max(...números) {
+  let resultado = -Infinity;
+  for (let número of números) {
+    if (número > resultado) resultado = número;
   }
-  return result;
+  return resultado;
 }
 console.log(max(4, 1, 9, -2));
 // → 9
 ```
 
-Cuando se llama a una función así, el _((parámetro restante))_ se vincula a un array que contiene todos los argumentos restantes. Si hay otros parámetros antes de él, sus valores no forman parte de ese array. Cuando, como en `max`, es el único parámetro, contendrá todos los argumentos.
+Cuando se llama a una función así, el _((parámetro rest))_ se vincula a un array que contiene todos los argumentos restantes. Si hay otros parámetros antes de él, sus valores no forman parte de ese array. Cuando, como en `max`, es el único parámetro, contendrá todos los argumentos.
 
 {{index [function, application]}}
 
@@ -730,21 +740,21 @@ Puedes usar una notación similar de tres puntos para _llamar_ a una función co
 
 ```
 let numbers = [5, 1, 7];
-console.log(max(...numbers));
+console.log(max(...números));
 // → 7
 ```
 
-Esto "((expande))" el array en la llamada de la función, pasando sus elementos como argumentos separados. Es posible incluir un array de esa manera junto con otros argumentos, como en `max(9, ...numbers, 2)`.
+Esto "((expande))" el array en la llamada de la función, pasando sus elementos como argumentos separados. Es posible incluir un array de esa manera junto con otros argumentos, como en `max(9, ...números, 2)`.
 
 {{index "[] (array)"}}
 
 La notación de array entre corchetes cuadrados permite al operador de triple punto expandir otro array en el nuevo array:
 
 ```
-let words = ["never", "fully"];
+let palabras = ["lo", "entenderé"];
 
-console.log(["will", ...words, "understand"]);
-// → ["will", "never", "fully", "understand"]
+console.log(["nunca", ...palabras, "del todo"]);
+// → ["nunca", "lo", "entenderé", "del todo"]
 ```
 
 {{index "{} (object)"}}
@@ -761,25 +771,25 @@ console.log({...coordenadas, y: 5, z: 1});
 
 {{index "Objeto Math", "Función Math.min", "Función Math.max", "Función Math.sqrt", "mínimo", "máximo", "raíz cuadrada"}}
 
-Como hemos visto, `Math` es una bolsa de funciones de utilidad relacionadas con números, tales como `Math.max` (máximo), `Math.min` (mínimo) y `Math.sqrt` (raíz cuadrada).
+Como hemos visto, `Math` es un saco de funciones tales como `Math.max` (máximo), `Math.min` (mínimo) y `Math.sqrt` (raíz cuadrada), para hacer cosas con números.
 
 {{index espacio de nombres, [objeto, propiedad]}}
 
 {{id "contaminación de espacio de nombres"}}
 
-El objeto `Math` se utiliza como un contenedor para agrupar un conjunto de funcionalidades relacionadas. Solo hay un objeto `Math` y casi nunca es útil como un valor. Más bien, proporciona un _espacio de nombres_ para que todas estas funciones y valores no tengan que ser enlaces globales.
+El objeto `Math` se utiliza como un contenedor para agrupar un conjunto de funcionalidades relacionadas. Solo hay un objeto `Math` y casi nunca es útil como un valor. Más bien, proporciona un _espacio de nombres_ para que todas estas funciones y valores no tengan que ser variables globales.
 
 {{index [enlace, nombrar]}}
 
-Tener demasiados enlaces globales "contamina" el espacio de nombres. Cuantos más nombres se hayan tomado, más probable es que sobrescribas accidentalmente el valor de algún enlace existente. Por ejemplo, es probable que quieras nombrar algo `max` en uno de tus programas. Dado que la función `max` integrada de JavaScript está protegida de forma segura dentro del objeto `Math`, no tienes que preocuparte por sobrescribirla.
+Tener demasiados enlaces globales "contamina" el espacio de nombres. Cuantos más nombres se hayan tomado, más probable es que sobrescribas accidentalmente el valor de alguna asociación existente. Por ejemplo, es probable que quieras nombrar algo con `max` en uno de tus programas. Dado que la función `max` integrada de JavaScript está protegida de forma segura dentro del objeto `Math`, no tienes que preocuparte por sobrescribirla.
 
 {{index "palabra clave let", "palabra clave const"}}
 
-Muchos lenguajes te detendrán, o al menos te advertirán, cuando estés definiendo un enlace con un nombre que ya está tomado. JavaScript hace esto para enlaces que declaraste con `let` o `const`, pero —perversamente— no para enlaces estándar ni para enlaces declarados con `var` o `function`.
+Muchos lenguajes te detendrán, o al menos te advertirán, cuando estés definiendo un enlace con un nombre que ya está tomado. JavaScript hace esto para enlaces que declaraste con `let` o `const`, pero —perversamente— no para asociaciones estándar ni para enlaces declarados con `var` o `function`.
 
 {{index "Función Math.cos", "Función Math.sin", "Función Math.tan", "Función Math.acos", "Función Math.asin", "Función Math.atan", "Constante Math.PI", coseno, seno, tangente, "constante PI", pi}}
 
-Volviendo al objeto `Math`. Si necesitas hacer ((trigonometría)), `Math` puede ayudarte. Contiene `cos` (coseno), `sin` (seno) y `tan` (tangente), así como sus funciones inversas, `acos`, `asin` y `atan`, respectivamente. El número π (pi) —o al menos la aproximación más cercana que cabe en un número de JavaScript— está disponible como `Math.PI`. Existe una antigua tradición de programación que consiste en escribir los nombres de ((valores constantes)) en mayúsculas:
+Volviendo al objeto `Math`. Si necesitas hacer ((trigonometría)), `Math` puede ayudarte. Contiene las funciones `cos` (coseno), `sin` (seno) y `tan` (tangente), así como sus funciones inversas, `acos`, `asin` y `atan`, respectivamente. El número π (pi) —o al menos la aproximación más cercana que cabe en un número de JavaScript— está disponible como `Math.PI`. Existe una antigua tradición de programación que consiste en escribir los nombres de ((valores constantes)) en mayúsculas:
 
 ```{test: no}
 function puntoAleatorioEnCirculo(radio) {
@@ -795,7 +805,7 @@ Si no estás familiarizado con senos y cosenos, no te preocupes. Los explicaré
 
 {{index "Función Math.random", "número aleatorio"}}
 
-El ejemplo anterior utilizó `Math.random`. Esta es una función que devuelve un nuevo número pseudoaleatorio entre cero (inclusive) y uno (exclusivo) cada vez que la llamas:
+En el ejemplo anterior se ha usado `Math.random`. Esta es una función que devuelve un nuevo número pseudoaleatorio entre cero (inclusive) y uno (exclusivo) cada vez que la llamas:
 
 ```{test: no}
 console.log(Math.random());
@@ -808,7 +818,7 @@ console.log(Math.random());
 
 {{index "número seudorandom", "número aleatorio"}}
 
-Aunque las computadoras son máquinas deterministas —siempre reaccionan de la misma manera si se les da la misma entrada— es posible hacer que produzcan números que parezcan aleatorios. Para lograrlo, la máquina mantiene algún valor oculto y, cada vez que solicitas un nuevo número aleatorio, realiza cálculos complicados en este valor oculto para crear un valor nuevo. Almacena un nuevo valor y devuelve algún número derivado de este. De esta manera, puede producir números nuevos y difíciles de predecir que se _aparentan_ aleatorios.
+Aunque las computadoras son máquinas deterministas —siempre reaccionan de la misma manera si se les da la misma entrada—, es posible hacer que produzcan números que parezcan aleatorios. Para lograrlo, la máquina mantiene algún valor oculto y, cada vez que solicitas un nuevo número aleatorio, realiza cálculos complicados en este valor oculto para crear un valor nuevo. Almacena un nuevo valor y devuelve algún número derivado de este. De esta manera, puede producir números nuevos y difíciles de predecir que _parecen_ aleatorios.
 
 {{index redondeo, "función Math.floor"}}
 
@@ -823,7 +833,7 @@ Al multiplicar el número aleatorio por 10, obtenemos un número mayor o igual a
 
 {{index "función Math.ceil", "función Math.round", "función Math.abs", "valor absoluto"}}
 
-También existen las funciones `Math.ceil` (para "techo", que redondea hacia arriba al número entero más cercano), `Math.round` (al número entero más cercano) y `Math.abs`, que toma el valor absoluto de un número, es decir, niega los valores negativos pero deja los positivos tal como están.
+También existen las funciones `Math.ceil` (de "techo", que redondea hacia arriba al número entero más cercano), `Math.round` (al número entero más cercano) y `Math.abs`, que proporciona el valor absoluto de un número, es decir, niega los valores negativos pero deja los positivos tal y como están.
 
 ## Desestructuración
 
@@ -832,18 +842,18 @@ También existen las funciones `Math.ceil` (para "techo", que redondea hacia arr
 Volviendo por un momento a la función `phi`.
 
 ```{test: wrap}
-function phi(table) {
-  return (table[3] * table[0] - table[2] * table[1]) /
-    Math.sqrt((table[2] + table[3]) *
-              (table[0] + table[1]) *
-              (table[1] + table[3]) *
-              (table[0] + table[2]));
+function phi(tabla) {
+  return (tabla[3] * tabla[0] - tabla[2] * tabla[1]) /
+    Math.sqrt((tabla[2] + tabla[3]) *
+              (tabla[0] + tabla[1]) *
+              (tabla[1] + tabla[3]) *
+              (tabla[0] + tabla[2]));
 }
 ```
 
 {{index "desestructuración de asignaciones", "parámetro"}}
 
-Una razón por la que esta función es difícil de leer es que tenemos una asignación apuntando a nuestro array, pero preferiríamos tener asignaciones para los _elementos_ del array, es decir, `let n00 = table[0]` y así sucesivamente. Afortunadamente, hay una forma concisa de hacer esto en JavaScript:
+Una razón por la que esta función es difícil de leer es que tenemos una asociación apuntando a nuestro array, pero preferiríamos tener asociaciones para los _elementos_ del array, es decir, `let n00 = table[0]` y así sucesivamente. Por suerte, hay una forma concisa de hacer esto en JavaScript:
 
 ```
 function phi([n00, n01, n10, n11]) {
@@ -855,15 +865,15 @@ function phi([n00, n01, n10, n11]) {
 
 {{index "palabra clave let", "palabra clave var", "palabra clave const", ["asignación", "desestructuración"]}}
 
-Esto también funciona para asignaciones creadas con `let`, `var` o `const`. Si sabes que el valor que estás asignando es un array, puedes usar ((corchetes)) para "mirar dentro" del valor y asignar sus contenidos.
+Esto también funciona para asignaciones creadas con `let`, `var` o `const`. Si sabes que el valor que estás asignando es un array, puedes usar ((corchetes)) para "mirar dentro" de cada valor y crear una asociación a su contenido.
 
 {{index [objeto, propiedad], [llaves, objeto]}}
 
 Un truco similar funciona para objetos, usando llaves en lugar de corchetes:
 
 ```
-let {name} = {name: "Faraji", age: 23};
-console.log(name);
+let {nombre} = {nombre: "Faraji", edad: 23};
+console.log(nombre);
 // → Faraji
 ```
 
@@ -878,12 +888,12 @@ Ten en cuenta que si intentas desestructurar `null` o `undefined`, obtendrás un
 Cuando no estás seguro de si un valor dado produce un objeto pero aún deseas leer una propiedad de él cuando lo hace, puedes usar una variante de la notación de punto: `objeto?.propiedad`.
 
 ```
-function city(objeto) {
-  return objeto.address?.city;
+function ciudad(objeto) {
+  return objeto.dirección?.ciudad;
 }
-console.log(city({address: {city: "Toronto"}}));
+console.log(ciudad({dirección: {ciudad: "Toronto"}}));
 // → Toronto
-console.log(city({name: "Vera"}));
+console.log(ciudad({nombre: "Vera"}));
 // → undefined
 ```
 
@@ -892,9 +902,9 @@ La expresión `a?.b` significa lo mismo que `a.b` cuando `a` no es nulo o indefi
 Una notación similar se puede utilizar con el acceso a corchetes cuadrados, e incluso con llamadas de funciones, colocando `?.` delante de los paréntesis o corchetes:
 
 ```
-console.log("string".notAMethod?.());
+console.log("cadena".metodoNoExistente?.());
 // → undefined
-console.log({}.arrayProp?.[0]);
+console.log({}.propiedadArray?.[0]);
 // → undefined
 ```
 
@@ -902,26 +912,26 @@ console.log({}.arrayProp?.[0]);
 
 {{index [array, representation], [object, representation], "data format", [memory, organization]}}
 
-Debido a que las propiedades capturan su valor en lugar de contenerlo, los objetos y arrays se almacenan en la memoria de la computadora como secuencias de bits que contienen las _((direcciones))_—el lugar en la memoria—de sus contenidos. Un array con otro array dentro de él consiste en (al menos) una región de memoria para el array interno y otra para el array externo, que contiene (entre otras cosas) un número que representa la dirección del array interno.
+Dado que las propiedades capturan su valor en lugar de contenerlo, los objetos y arrays se almacenan en la memoria de la computadora como secuencias de bits que contienen las _((direcciones))_ —el lugar en la memoria— de sus contenidos. Un array con otro array dentro de él consiste en (al menos) una región de memoria para el array interno y otra para el array externo, que contiene (entre otras cosas) un número que representa la dirección del array interno.
 
-Si deseas guardar datos en un archivo para más tarde o enviarlos a otra computadora a través de la red, debes convertir de alguna manera estas marañas de direcciones de memoria en una descripción que se pueda almacenar o enviar. Podrías enviar toda la memoria de tu computadora junto con la dirección del valor que te interesa, supongo, pero eso no parece ser el mejor enfoque.
+Si deseas guardar datos en un archivo para más tarde o enviarlos a otra computadora a través de la red, debes convertir de alguna manera estas marañas de direcciones de memoria en una descripción que se pueda almacenar o enviar. Podrías enviar toda la memoria de tu computadora junto con la dirección del valor que te interesa, supongo, pero esa no parece ser la mejor estrategia.
 
 {{indexsee "JavaScript Object Notation", JSON}}
 
 {{index [serialization, "World Wide Web"]}}
 
-Lo que podemos hacer es _serializar_ los datos. Eso significa que se convierten en una descripción plana. Un formato de serialización popular se llama _((JSON))_ (pronunciado "Jason"), que significa JavaScript Object Notacion. Se utiliza ampliamente como formato de almacenamiento y comunicación de datos en la Web, incluso en lenguajes que no son JavaScript.
+Lo que podemos hacer es _serializar_ los datos. Es decir, convertirlos en una descripción plana. Un formato de serialización popular se llama _((JSON))_ (pronunciado como el nombre "Jason"), que viene de JavaScript Object Notacion. Se utiliza ampliamente como formato de almacenamiento y comunicación de datos en la Web, incluso en lenguajes que no son JavaScript.
 
 {{index [array, notation], [object, creation], [quoting, "in JSON"], comment}}
 
-JSON se parece al formato de escritura de arrays y objetos de JavaScript, con algunas restricciones. Todos los nombres de propiedades deben estar rodeados de comillas dobles y solo se permiten expresiones de datos simples—no llamadas a funciones, enlaces, o cualquier cosa que implique cálculos reales. Los comentarios no están permitidos en JSON.
+JSON se parece al formato de escritura de arrays y objetos de JavaScript, con algunas restricciones. Todos los nombres de propiedades deben estar rodeados de comillas dobles y solo se permiten expresiones de datos simples —no llamadas a funciones, variables (asociaciones, en general), o cualquier cosa que implique cálculos reales. Los comentarios no están permitidos en JSON.
 
-Una entrada de diario podría verse así cuando se representa como datos JSON:
+Una entrada de diario podría verse así cuando se representa como datos en formato JSON:
 
 ```{lang: "json"}
 {
-  "squirrel": false,
-  "events": ["work", "touched tree", "pizza", "running"]
+  "ardilla": false,
+  "eventos": ["trabajo", "tocar árbol", "pizza", "correr"]
 }
 ```
 
@@ -930,21 +940,21 @@ Una entrada de diario podría verse así cuando se representa como datos JSON:
 JavaScript nos proporciona las funciones `JSON.stringify` y `JSON.parse` para convertir datos a este formato y desde este formato. La primera toma un valor de JavaScript y devuelve una cadena codificada en JSON. La segunda toma dicha cadena y la convierte en el valor que codifica:
 
 ```
-let string = JSON.stringify({squirrel: false,
-                             events: ["weekend"]});
-console.log(string);
-// → {"squirrel":false,"events":["weekend"]}
-console.log(JSON.parse(string).events);
-// → ["weekend"]
+let cadena = JSON.stringify({ardilla: false,
+                             eventos: ["finde"]});
+console.log(cadena);
+// → {"ardilla":false,"eventos":["finde"]}
+console.log(JSON.parse(cadena).eventos);
+// → ["finde"]
 ```
 
 ## Resumen
 
-Los objetos y arrays proporcionan formas de agrupar varios valores en un único valor. Esto nos permite poner un montón de cosas relacionadas en una bolsa y correr con la bolsa en lugar de envolver nuestros brazos alrededor de cada una de las cosas individuales e intentar sostenerlas por separado.
+Los objetos y arrays proporcionan formas de agrupar varios valores en un único valor. Esto nos permite poner un montón de cosas relacionadas en una bolsa y correr con la bolsa en lugar de envolver con nuestros brazos cada una de las cosas individuales e intentar sostenerlas todas por separado.
 
-La mayoría de los valores en JavaScript tienen propiedades, con las excepciones de `null` y `undefined`. Las propiedades se acceden usando `valor.prop` o `valor["prop"]`. Los objetos tienden a usar nombres para sus propiedades y almacenan más o menos un conjunto fijo de ellas. Los arrays, por otro lado, suelen contener cantidades variables de valores conceptualmente idénticos y usan números (comenzando desde 0) como los nombres de sus propiedades.
+La mayoría de los valores en JavaScript tienen propiedades, con las excepciones de `null` y `undefined`. Las propiedades se acceden usando `valor.prop` o `valor["prop"]`. Los objetos tienden a usar nombres para sus propiedades y almacenan más o menos un conjunto fijo de ellas. Los arrays, por otro lado, suelen contener cantidades variables de valores conceptualmente idénticos y usan números (comenzando desde 0) como nombres para sus propiedades.
 
-Sí _hay_ algunas propiedades nombradas en arrays, como `length` y varios métodos. Los métodos son funciones que viven en propiedades y (usualmente) actúan sobre el valor del cual son una propiedad.
+Sí _hay_ algunas propiedades con nombre en arrays, como `length` y varios métodos más. Los métodos son funciones que viven en propiedades y (usualmente) actúan sobre el valor del cual son una propiedad.
 
 Puedes iterar sobre arrays usando un tipo especial de bucle `for`: `for (let elemento of array)`.
 
@@ -957,29 +967,29 @@ Puedes iterar sobre arrays usando un tipo especial de bucle `for`: `for (let ele
 La [introducción](intro) de este libro insinuó lo siguiente como una forma agradable de calcular la suma de un rango de números:
 
 ```{test: no}
-console.log(sum(range(1, 10)));
+console.log(suma(rango(1, 10)));
 ```
 
 {{index "range function", "sum function"}}
 
-Escribe una función `range` que tome dos argumentos, `inicio` y `fin`, y devuelva un array que contenga todos los números desde `inicio` hasta `fin`, incluyendo `fin`.
+Escribe una función `rango` que tome dos argumentos, `inicio` y `fin`, y devuelva un array que contenga todos los números desde `inicio` hasta `fin`, incluyendo `fin`.
 
-Luego, escribe una función `sum` que tome un array de números y devuelva la suma de estos números. Ejecuta el programa de ejemplo y verifica si realmente devuelve 55.
+Luego, escribe una función `suma` que tome un array de números y devuelva la suma de estos números. Ejecuta el programa de ejemplo y verifica si realmente devuelve 55.
 
 {{index "optional argument"}}
 
-Como asignación adicional, modifica tu función `range` para que tome un tercer argumento opcional que indique el valor de "paso" utilizado al construir el array. Si no se proporciona un paso, los elementos deberían aumentar en incrementos de uno, correspondiendo al comportamiento anterior. La llamada a la función `range(1, 10, 2)` debería devolver `[1, 3, 5, 7, 9]`. Asegúrate de que esto también funcione con valores de paso negativos, de modo que `range(5, 2, -1)` produzca `[5, 4, 3, 2]`.
+Como bonus, modifica tu función `rango` para que tome un tercer argumento opcional que indique el valor de "paso" utilizado al construir el array. Si no se proporciona un paso, los elementos deberían aumentar en incrementos de uno, correspondiendo al comportamiento anterior. La llamada a la función `range(1, 10, 2)` debería devolver `[1, 3, 5, 7, 9]`. Asegúrate de que esto también funcione con valores de paso negativos, de modo que `range(5, 2, -1)` produzca `[5, 4, 3, 2]`.
 
 {{if interactive
 
 ```{test: no}
 // Tu código aquí.
 
-console.log(range(1, 10));
+console.log(rango(1, 10));
 // → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
-console.log(range(5, 2, -1));
+console.log(rango(5, 2, -1));
 // → [5, 4, 3, 2]
-console.log(sum(range(1, 10)));
+console.log(suma(rango(1, 10)));
 // → 55
 ```
 
@@ -997,13 +1007,13 @@ Dado que el límite final es inclusivo, necesitarás usar el operador `<=` en lu
 
 {{index "arguments object"}}
 
-El parámetro de paso puede ser un parámetro opcional que por defecto (usando el operador `=`) es 1.
+El parámetro de paso puede ser un parámetro opcional que por defecto (usando el operador `=`) sea 1.
 
 {{index "range function", "for loop"}}
 
-Hacer que `range` comprenda valores negativos de paso probablemente sea mejor haciendo escribiendo dos bucles separados: uno para contar hacia arriba y otro para contar hacia abajo, porque la comparación que verifica si el bucle ha terminado necesita ser `>=` en lugar de `<=` al contar hacia abajo.
+Hacer que `rango` comprenda valores negativos de paso probablemente sea mejor haciendo escribiendo dos bucles separados: uno para contar hacia arriba y otro para contar hacia abajo, porque la comparación que verifica si el bucle ha terminado necesita ser `>=` en lugar de `<=` al contar hacia abajo.
 
-También puede valer la pena usar un paso predeterminado diferente, es decir, -1, cuando el final del rango es menor que el principio. De esa manera, `range(5, 2)` devuelve algo significativo, en lugar de quedarse atascado en un ((bucle infinito)). Es posible hacer referencia a parámetros anteriores en el valor predeterminado de un parámetro.
+También puede valer la pena usar un paso predeterminado diferente, es decir, -1, cuando el final del rango es menor que el principio. De esa manera, `rango(5, 2)` devuelve algo con sentido, en lugar de quedarse atascado en un ((bucle infinito)). Es posible hacer referencia a parámetros anteriores en el valor predeterminado de un parámetro.
 
 hint}}
 
@@ -1011,11 +1021,11 @@ hint}}
 
 {{index "reversing (exercise)", "método reverse", [array, "métodos"]}}
 
-Los arrays tienen un método `reverse` que cambia el array invirtiendo el orden en el que aparecen sus elementos. Para este ejercicio, escribe dos funciones, `reverseArray` y `reverseArrayInPlace`. La primera, `reverseArray`, debería tomar un array como argumento y producir un _nuevo_ array que tenga los mismos elementos en orden inverso. La segunda, `reverseArrayInPlace`, debería hacer lo que hace el método `reverse`: _modificar_ el array dado como argumento invirtiendo sus elementos. Ninguna de las funciones puede utilizar el método `reverse` estándar.
+Los arrays tienen un método `reverse` que modifica el array invirtiendo el orden en el que aparecen sus elementos. Para este ejercicio, escribe dos funciones, `reverseArray` y `reverseArrayInPlace`. La primera, `reverseArray`, debería tomar un array como argumento y producir un _nuevo_ array que tenga los mismos elementos en orden inverso. La segunda, `reverseArrayInPlace`, debería hacer lo que hace el método `reverse`: _modificar_ el array dado como argumento invirtiendo sus elementos. Ninguna de las funciones puede utilizar el método `reverse` estándar.
 
 {{index eficiencia, "función pura", "efecto secundario"}}
 
-Recordando las notas sobre efectos secundarios y funciones puras en el [capítulo anterior](functions#pure), ¿qué variante esperas que sea útil en más situaciones? ¿Cuál se ejecuta más rápido?
+Recordando las notas sobre efectos secundarios y funciones puras del [capítulo anterior](functions#pure), ¿qué variante esperas que sea útil en más situaciones? ¿Cuál se ejecuta más rápido?
 
 {{if interactive
 
@@ -1039,13 +1049,13 @@ if}}
 
 {{index "reversing (exercise)"}}
 
-Hay dos formas obvias de implementar `reverseArray`. La primera es simplemente recorrer el array de entrada de principio a fin y usar el método `unshift` en el nuevo array para insertar cada elemento en su inicio. La segunda es recorrer el array de entrada hacia atrás y utilizar el método `push`. Iterar sobre un array hacia atrás requiere una especificación de bucle (algo incómoda), como `(let i = array.length - 1; i >= 0; i--)`.
+Hay dos formas obvias de implementar `reverseArray`. La primera es simplemente recorrer el array de entrada de principio a fin y usar el método `unshift` en el nuevo array para insertar cada elemento en su inicio. La segunda es recorrer el array de entrada hacia atrás y utilizar el método `push`. Iterar sobre un array hacia atrás requiere una especificación (un poco rara) de bucle `for`, como `(let i = array.length - 1; i >= 0; i--)`.
 
 {{index "método slice"}}
 
-Invertir el array en su lugar es más difícil. Debes tener cuidado de no sobrescribir elementos que necesitarás más adelante. Utilizar `reverseArray` o copiar todo el array de otra manera (usar `array.slice()` es una buena forma de copiar un array) funciona pero es hacer trampa.
+Invertir el array en su lugar es más difícil. Debes tener cuidado de no sobrescribir elementos que necesitarás más adelante. Utilizar `reverseArray` o, si no, copiar todo el array (usar `array.slice()` es una buena forma de copiar un array) funciona pero es hacer trampa.
 
-El truco consiste en _intercambiar_ el primer y último elementos, luego el segundo y el penúltimo, y así sucesivamente. Puedes hacer esto recorriendo la mitad de la longitud del array (utiliza `Math.floor` para redondear hacia abajo, no necesitas tocar el elemento central en un array con un número impar de elementos) e intercambiando el elemento en la posición `i` con el que está en la posición `array.length - 1 - i`. Puedes utilizar una asignación local para retener brevemente uno de los elementos, sobrescribirlo con su imagen reflejada, y luego colocar el valor de la asignación local en el lugar donde solía estar la imagen reflejada.
+El truco consiste en _intercambiar_ el primer y último elementos, luego el segundo y el penúltimo, y así sucesivamente. Puedes hacer esto recorriendo la mitad de la longitud del array (utiliza `Math.floor` para redondear hacia abajo, no necesitas tocar el elemento central en un array con un número impar de elementos) e intercambiando el elemento en la posición `i` con el que está en la posición `array.length - 1 - i`. Puedes utilizar una asignación local para retener brevemente uno de los elementos, sobrescribirlo con su imagen reflejada, y luego colocar el valor de la asignación local en el lugar donde antes estaba la imagen reflejada.
 
 hint}}
 
@@ -1105,7 +1115,7 @@ if}}
 
 {{index "list (exercise)", "linked list"}}
 
-Construir una lista es más fácil cuando se hace de atrás hacia adelante. Por lo tanto, `arrayToList` podría iterar sobre el array en reversa (ver ejercicio anterior) y, para cada elemento, agregar un objeto a la lista. Puedes usar un enlace local para mantener la parte de la lista que se ha construido hasta el momento y usar una asignación como `lista = {value: X, rest: lista}` para añadir un elemento.
+Construir una lista es más fácil cuando se hace de atrás hacia adelante. Por lo tanto, `arrayToList` podría iterar sobre el array de atrás para alante (ver el ejercicio anterior) y, para cada elemento, agregar un objeto a la lista. Puedes usar un enlace local para mantener la parte de la lista que se ha construido hasta el momento y usar una asignación como `lista = {value: X, rest: lista}` para añadir un elemento.
 
 {{index "for loop"}}
 
@@ -1115,7 +1125,7 @@ Para recorrer una lista (en `listToArray` y `nth`), se puede utilizar una especi
 for (let nodo = list; nodo; nodo = nodo.rest) {}
 ```
 
-¿Puedes ver cómo funciona esto? En cada iteración del bucle, `nodo` apunta a la sublista actual, y el cuerpo puede leer su propiedad `value` para obtener el elemento actual. Al final de una iteración, `nodo` pasa a la siguiente sublista. Cuando eso es nulo, hemos llegado al final de la lista y el bucle ha terminado.
+¿Puedes ver cómo funciona esto? En cada iteración del bucle, `nodo` apunta a la sublista actual, y el cuerpo puede leer su propiedad `value` para obtener el elemento actual. Al final de una iteración, `nodo` pasa a la siguiente sublista. Cuando esta asignación de nulo, hemos llegado al final de la lista y el bucle ha terminado.
 
 {{index recursion}}
 
@@ -1131,14 +1141,14 @@ El operador `==` compara objetos por identidad, pero a veces preferirías compar
 
 Escribe una función `deepEqual` que tome dos valores y devuelva true solo si son el mismo valor o son objetos con las mismas propiedades, donde los valores de las propiedades son iguales cuando se comparan con una llamada recursiva a `deepEqual`.
 
-Para saber si los valores deben compararse directamente (usando el operador `===` para eso) o si sus propiedades deben compararse, puedes usar el operador `typeof`. Si produce `"object"` para ambos valores, deberías hacer una comparación profunda. Pero debes tener en cuenta una excepción tonta: debido a un accidente histórico, `typeof null` también produce `"object"`.
+Para saber si los valores deben compararse directamente (usando el operador `===` para eso) o si sus propiedades deben compararse, puedes usar el operador `typeof`. Si produce `"object"` para ambos valores, deberías hacer una comparación profunda. Pero debes tener en cuenta una excepción absurda: debido a un accidente histórico, `typeof null` también produce `"object"`.
 
 La función `Object.keys` será útil cuando necesites recorrer las propiedades de los objetos para compararlas.
 
 {{if interactive
 
 ```{test: no}
-// Your code here.
+// Tu código aquí.
 
 let obj = {here: {is: "an"}, object: 2};
 console.log(deepEqual(obj, obj));
@@ -1155,7 +1165,7 @@ if}}
 
 {{index "deep comparison (exercise)", [comparison, deep], "typeof operator", "=== operator"}}
 
-La prueba para determinar si estás tratando con un objeto real se verá algo así: `typeof x == "object" && x != null`. Ten cuidado de comparar propiedades solo cuando _ambos_ argumentos sean objetos. En todos los demás casos, simplemente puedes devolver inmediatamente el resultado de aplicar `===`.
+Tu comprobación para determinar si estás tratando con un objeto real tendrá una pinta como esta: `typeof x == "object" && x != null`. Ten cuidado de comparar propiedades solo cuando _ambos_ argumentos sean objetos. En todos los demás casos, simplemente puedes devolver inmediatamente el resultado de aplicar `===`.
 
 {{index "Object.keys function"}}
 
@@ -1163,6 +1173,6 @@ Utiliza `Object.keys` para recorrer las propiedades. Necesitas comprobar si ambo
 
 {{index "return value"}}
 
-Devolver el valor correcto de la función se hace mejor devolviendo inmediatamente false cuando se encuentra una diferencia y devolviendo true al final de la función.
+Lo mejor para devolver el valor correcto con la función es devolver inmediatamente false cuando se encuentra una diferencia y devolviendo true al final de la función.
 
 hint}}
\ No newline at end of file
diff --git a/html/02_program_structure.html b/html/02_program_structure.html
index 70b4ad62..f1dacd18 100644
--- a/html/02_program_structure.html
+++ b/html/02_program_structure.html
@@ -45,7 +45,7 @@ 

¿Cómo mantiene un programa un estado interno? ¿Cómo recuerda las cosas? Hemos visto cómo producir nuevos valores a partir de valores antiguos, pero esto no modifica los valores originales. Además, el nuevo valor debe utilizarse inmediatamente o desaparecerá tan pronto aparezca. Para atrapar y retener valores, JavaScript nos da algo llamado asociación, o enlace, o variable:

-

N. del T.: El uso de la palabra variable para denotar este concepto es muy común, aunque puede llegar a resultar confusa. En la versión original de este libro, el autor elige usar la palabra bind en lugar de variable para referirse a estas entidades. Nosotros haremos lo mismo utilizando la palabra asociación, aunque, en ocasiones, también se usará la palabra enlace. Rara vez usaremos la palabra variable, que se reservará en general para un tipo concreto de enlace. Veremos más sobre las diferencias entre cada tipo de asociación en este y el siguiente capítulo

+

N. del T.: El uso de la palabra variable para denotar este concepto es muy común, aunque puede llegar a resultar confusa. En la versión original de este libro, el autor elige usar la palabra bind en lugar de variable para referirse a estas entidades. Nosotros haremos lo mismo utilizando la palabra asociación, aunque, en ocasiones, también se usará la palabra enlace o asignación. Rara vez usaremos la palabra variable, que se reservará en general para un tipo concreto de enlace. Veremos más sobre las diferencias entre cada tipo de asociación en este y el siguiente capítulo

let caught = 5 * 5;
diff --git a/html/04_data.html b/html/04_data.html index b8533d55..98674f86 100644 --- a/html/04_data.html +++ b/html/04_data.html @@ -14,60 +14,64 @@

Estructuras de datos: Objetos y Arrays

-

En dos ocasiones me han preguntado: ‘Dígame, Sr. Babbage, si introduce en la máquina cifras erróneas, ¿saldrán respuestas correctas?’ [...] No soy capaz de entender correctamente el tipo de confusión de ideas que podría provocar tal pregunta.

+

En dos ocasiones me han preguntado: ‘Dígame, Sr. Babbage, si introduce en la máquina cifras incorrectas, ¿saldrán las respuestas correctas?’ [...] No soy capaz de comprender correctamente el tipo de confusión de ideas que podría provocar tal pregunta.

Charles Babbage, Passages from the Life of a Philosopher (1864)
Ilustración de una ardilla junto a un montón de libros y un par de gafas. Se pueden ver la luna y las estrellas en el fondo.
-

Números, booleanos y cadenas de texto son los átomos a partir de los cuales se construyen las estructuras de datos. Sin embargo, muchos tipos de información requieren más de un átomo. Los objetos nos permiten agrupar valores, incluyendo otros objetos, para construir estructuras más complejas.

+

Números, booleanos y cadenas de texto son los átomos con los que se construyen las estructuras de datos. Sin embargo, muchos tipos de información requieren más de un átomo. Los objetos nos permiten agrupar valores —incluyendo otros objetos— para construir estructuras más complejas.

-

Hasta ahora, los programas que hemos creado han estado limitados por el hecho de que operaban solo en tipos de datos simples. Después de aprender los conceptos básicos de estructuras de datos en este capítulo, sabrás lo suficiente como para comenzar a escribir programas útiles.

+

Hasta ahora, los programas que hemos escrito han estado limitados por el hecho de que operaban solo en tipos de datos simples. Después de aprender los conceptos básicos sobre estructuras de datos en este capítulo, sabrás suficiente como para comenzar a escribir programas útiles.

-

El capítulo trabajará a través de un ejemplo de programación más o menos realista, introduciendo conceptos a medida que se aplican al problema en cuestión. El código de ejemplo a menudo se basará en funciones y variables introducidas anteriormente en el libro.

+

En este capítulo trabajaremos con un ejemplo de programación más o menos realista, introduciendo conceptos a medida que se aplican al problema en cuestión. El código de ejemplo a menudo se basará en funciones y asociaciones introducidas anteriormente en el libro.

-

El hombreardilla

+

El hombre ardilla

-

De vez en cuando, usualmente entre las 8 p. m. y las 10 p. m., Jacques se transforma en un pequeño roedor peludo con una cola espesa.

+

De vez en cuando, normalmente entre las 8 p. m. y las 10 p. m., Jacques se transforma en un pequeño roedor peludo con espesa cola.

-

Por un lado, Jacques está bastante contento de no tener licantropía clásica. Convertirse en una ardilla causa menos problemas que convertirse en un lobo. En lugar de preocuparse por comer accidentalmente al vecino (eso sería incómodo), se preocupa por ser comido por el gato del vecino. Después de dos ocasiones de despertar en una rama peligrosamente delgada en la copa de un roble, desnudo y desorientado, ha optado por cerrar con llave las puertas y ventanas de su habitación por la noche y poner unas cuantas nueces en el suelo para mantenerse ocupado.

+

Por un lado, Jacques está bastante contento de no tener una licantropía convencional. Convertirse en una ardilla da menos problemas que convertirse en un lobo. En lugar de tenerse que preocupar por comerse accidentalmente al vecino (eso sería incómodo), se preocupa por que no se lo coma el gato del vecino. Tras despertar en dos ocasiones sobre una rama peligrosamente fina en la copa de un roble, desnudo y desorientado, ha optado por cerrar con llave las puertas y ventanas de su habitación por las noches y poner unas cuantas nueces en el suelo para mantenerse ocupado.

-

Pero Jacques preferiría deshacerse por completo de su condición. Las ocurrencias irregulares de la transformación hacen que sospeche que podrían ser desencadenadas por algo. Durante un tiempo, creyó que sucedía solo en días en los que había estado cerca de robles. Sin embargo, evitar los robles no resolvió el problema.

+

Pero Jacques preferiría deshacerse por completo de su condición. Las irregularidad con que suceden sus transformaciones hacen que sospeche que podrían ser desencadenadas por algo. Durante un tiempo, creyó que solo sucedía en días en los que había estado cerca de robles, pero evitar los robles no resolvió el problema.

-

Cambió a un enfoque más científico, Jacques ha comenzado a llevar un registro diario de todo lo que hace en un día dado y si cambió de forma. Con estos datos, espera estrechar las condiciones que desencadenan las transformaciones. Lo primero que necesita es una estructura de datos para almacenar esta información.

+

Cambiando a un enfoque más científico, Jacques ha comenzado a llevar un registro diario de todo lo que hace en el día y si cambió de forma ese día. Con estos datos, espera poder deliminar las condiciones que desencadenan sus transformaciones.

+ +

Lo primero que necesita es una estructura de datos para almacenar esta información.

Conjuntos de datos

-

Para trabajar con un conjunto de datos digitales, primero tenemos que encontrar una forma de representarlo en la memoria de nuestra máquina. Digamos, por ejemplo, que queremos representar una colección de los números 2, 3, 5, 7 y 11.

+

Para trabajar con un conjunto de datos digitales, primero tenemos que encontrar una manera de representarlo en la memoria de nuestra máquina. Digamos, por ejemplo, que queremos representar una colección de los números 2, 3, 5, 7 y 11.

-

Podríamos ser creativos con las cadenas, después de todo, las cadenas pueden tener cualquier longitud, por lo que podemos poner muchos datos en ellas, y usar "2 3 5 7 11" como nuestra representación. Pero esto es incómodo. Tendríamos que extraer de alguna manera los dígitos y convertirlos de vuelta a números para acceder a ellos.

+

Podríamos ponernos creativos con las cadenas —después de todo, las cadenas pueden tener cualquier longitud, por lo que podemos poner muchos datos en ellas— y usar "2 3 5 7 11" como representación de esta colección. Pero esto quizá no sea lo más apropiado. Tendríamos que extraer de alguna manera los dígitos y convertirlos de vuelta a números para acceder a ellos.

-

Afortunadamente, JavaScript proporciona un tipo de dato específicamente para almacenar secuencias de valores. Se llama un array y se escribe como una lista de valores entre corchetes, separados por comas:

+

Afortunadamente, JavaScript proporciona un tipo de dato específicamente para almacenar secuencias de valores. Se llama array (o arreglo) y se escribe como una lista de valores entre corchetes, separados por comas:

-
let listaDeNumeros = [2, 3, 5, 7, 11];
-console.log(listaDeNumeros[2]);
+
let listaDeNúmeros = [2, 3, 5, 7, 11];
+console.log(listaDeNúmeros[2]);
 // → 5
-console.log(listaDeNumeros[0]);
+console.log(listaDeNúmeros[0]);
 // → 2
-console.log(listaDeNumeros[2 - 1]);
+console.log(listaDeNúmeros[2 - 1]);
 // → 3

La notación para acceder a los elementos dentro de un array también utiliza corchetes. Un par de corchetes inmediatamente después de una expresión, con otra expresión dentro de ellos, buscará el elemento en la expresión de la izquierda que corresponde al índice dado por la expresión en los corchetes.

-

El primer índice de un array es cero, no uno, por lo que el primer elemento se recupera con listaDeNumeros[0]. El conteo basado en cero tiene una larga tradición en tecnología y, de cierta manera, tiene mucho sentido, pero se necesita un poco de tiempo para acostumbrarse. Piensa en el índice como el número de elementos a omitir, contando desde el inicio del array.

+

El primer índice de un array es cero, no uno, por lo que el primer elemento se recupera con listaDeNúmeros[0]. La numeración basada en cero tiene una larga tradición en tecnología y, en cierto modo, tiene mucho sentido, pero se necesita un poco de tiempo para hacerse a ella. Piensa en el índice como el número de elementos del array que hay que saltarse hasta llegar al elemento requerido, contando desde el inicio del array.

Propiedades

-

Hemos visto algunas expresiones como miCadena.length (para obtener la longitud de una cadena) y Math.max (la función máxima) en capítulos anteriores. Estas expresiones acceden a una propiedad de algún valor. En el primer caso, accedemos a la propiedad length del valor en miCadena. En el segundo, accedemos a la propiedad llamada max en el objeto Math (que es una colección de constantes y funciones relacionadas con matemáticas).

+

Hemos visto algunas expresiones como miCadena.length (para obtener la longitud de una cadena) y Math.max (la función máximo) en capítulos anteriores. Estas expresiones acceden a una propiedad de un valor. En el primer caso, accedemos a la propiedad length del valor en miCadena. En el segundo, accedemos a la propiedad llamada max en el objeto Math (que es una colección de constantes y funciones relacionadas con matemáticas).

-

Casi todos los valores de JavaScript tienen propiedades. Las excepciones son null y undefined. Si intentas acceder a una propiedad en uno de estos valores no definidos, obtendrás un error:

+

Casi todos los valores de JavaScript tienen propiedades. Las excepciones son null y undefined. Si intentas acceder a una propiedad de uno de estos valores no definidos, obtendrás un error:

null.length;
 // → TypeError: null no tiene propiedades
-

Las dos formas principales de acceder a propiedades en JavaScript son con un punto y con corchetes. Tanto valor.x como valor[x] acceden a una propiedad en valor, pero no necesariamente a la misma propiedad. La diferencia radica en cómo se interpreta x. Al usar un punto, la palabra después del punto es el nombre literal de la propiedad. Al usar corchetes, la expresión entre los corchetes es evaluada para obtener el nombre de la propiedad. Mientras que valor.x obtiene la propiedad de valor llamada “x”, valor[x] toma el valor de la variable llamada x y lo utiliza, convertido a cadena, como nombre de propiedad. Si sabes que la propiedad en la que estás interesado se llama color, dices valor.color. Si quieres extraer la propiedad nombrada por el valor almacenado en la vinculación i, dices valor[i]. Los nombres de las propiedades son cadenas de texto. Pueden ser cualquier cadena, pero la notación de punto solo funciona con nombres que parecen nombres de vinculaciones válidos, comenzando con una letra o guion bajo, y conteniendo solo letras, números y guiones bajos. Si deseas acceder a una propiedad llamada 2 o John Doe, debes utilizar corchetes: valor[2] o valor["John Doe"].

+

Las dos formas principales de acceder a propiedades en JavaScript son con un punto y con corchetes. Tanto valor.x como valor[x] acceden a una propiedad en valor, pero no necesariamente a la misma propiedad. La diferencia radica en cómo se interpreta x. Al usar un punto, la palabra después del punto es el nombre literal de la propiedad. Al usar corchetes, la expresión entre los corchetes es evaluada para obtener el nombre de la propiedad. Mientras que valor.x obtiene la propiedad de valor llamada “x”, valor[x] toma el valor de la variable llamada x y lo utiliza, convertido a cadena, como nombre de propiedad.

+ +

Si sabes que la propiedad en la que estás interesado se llama color, dices valor.color. Si quieres extraer la propiedad nombrada por el valor almacenado en la asociación i, dices valor[i]. Los nombres de las propiedades son cadenas de texto. Pueden ser cualquier cadena, pero la notación de punto solo funciona cuando se usan nombres que encajan en las reglas válidas para definir nombres de asociaciones —comenzando con una letra o guion bajo, y conteniendo solo letras, números y guiones bajos. Si quieres acceder a una propiedad llamada 2 o John Doe, tendrás que usar corchetes: valor[2] o valor["John Doe"].

-

Los elementos en un array se almacenan como propiedades del array, utilizando números como nombres de propiedades. Dado que no puedes usar la notación de punto con números y generalmente quieres usar una vinculación que contenga el índice de todos modos, debes utilizar la notación de corchetes para acceder a ellos.

+

Los elementos en un array se almacenan como propiedades del array, utilizando números como nombres de estas propiedades. Dado que se puede usar la notación de punto con números y aún así querríamos usar una asociación que contenga el índice, tendremos que usar la notación de corchetes para acceder a ellos.

Al igual que las cadenas de texto, los arrays tienen una propiedad length que nos dice cuántos elementos tiene el array.

@@ -81,13 +85,13 @@

// → DOH

-

Cada cadena de texto tiene una propiedad toUpperCase. Cuando se llama, devolverá una copia de la cadena en la que todas las letras se han convertido a mayúsculas. También existe toLowerCase, que hace lo contrario.

+

Toda cadena de texto tiene una propiedad toUpperCase. Cuando se llama, devolverá una copia de la cadena en la que todas las letras se han convertido a mayúsculas. También existe toLowerCase, que hace lo contrario.

Curiosamente, aunque la llamada a toUpperCase no pasa argumentos, de alguna manera la función tiene acceso a la cadena "Doh", el valor cuya propiedad llamamos. Descubrirás cómo funciona esto en el Capítulo 6.

-

Las propiedades que contienen funciones generalmente se llaman métodos del valor al que pertenecen, como en “toUpperCase es un método de una cadena”.

+

Las propiedades que contienen funciones generalmente se llaman métodos del valor al que pertenecen. Por ejemplo, toUpperCase es un método de una cadena.

-

Este ejemplo demuestra dos métodos que puedes utilizar para manipular arrays:

+

Este ejemplo muestra dos métodos que puedes utilizar para manipular arrays:

let secuencia = [1, 2, 3];
 secuencia.push(4);
@@ -101,42 +105,42 @@ 

El método push agrega valores al final de un array. El método pop hace lo opuesto, eliminando el último valor en el array y devolviéndolo.

-

Estos nombres un tanto tontos son términos tradicionales para operaciones en una pila. Una pila, en programación, es una estructura de datos que te permite agregar valores a ella y sacarlos en el orden opuesto para que lo que se agregó último se elimine primero. Las pilas son comunes en programación; es posible que recuerdes la función call stack del capítulo anterior, que es una instancia de la misma idea.

+

Estos nombres medio tontos son términos tradicionales (en inglés) para operaciones en una pila. Una pila, en programación, es una estructura de datos que te permite agregar valores a ella y sacarlos en el orden inverso para que lo que se agregó último se elimine primero. Las pilas son algo común en programación —puede que recuerdes la pila de llamadas call stack de una función que vimos en el capítulo anterior, que es un ejemplo de esta idea.

Objetos

-

De vuelta al hombre-ardilla. Un conjunto de entradas de registro diario se puede representar como un array, pero las entradas no consisten solo en un número o una cadena, cada entrada necesita almacenar una lista de actividades y un valor booleano que indique si Jacques se convirtió en ardilla o no. Idealmente, nos gustaría agrupar estos elementos en un único valor y luego poner esos valores agrupados en un array de entradas de registro.

+

De vuelta al hombre ardilla. Un conjunto de entradas del registro diario se puede representar como un array, pero las entradas no solo consisten en un número o una cadena —cada entrada necesita almacenar una lista de actividades y un valor booleano que indique si Jacques se convirtió en ardilla o no ese día. Idealmente, nos gustaría agrupar todo esto en un único valor y luego poner esos valores agrupados en un array de entradas de registro.

-

Los valores del tipo object son colecciones arbitrarias de propiedades. Una forma de crear un objeto es usando llaves como una expresión:

+

Los valores del tipo object son colecciones arbitrarias de propiedades. Una forma de crear un objeto es usando llaves como expresión.

-
let dia1 = {
+
let día1 = {
   hombreArdilla: false,
   eventos: ["trabajo", "tocó árbol", "pizza", "correr"]
 };
-console.log(dia1.hombreArdilla);
+console.log(día1.hombreArdilla);
 // → false
-console.log(dia1.lobo);
+console.log(día1.lobo);
 // → undefined
-dia1.lobo = false;
-console.log(dia1.lobo);
+día1.lobo = false;
+console.log(día1.lobo);
 // → false
-

Dentro de las llaves, se escribe una lista de propiedades separadas por comas. Cada propiedad tiene un nombre seguido por dos puntos y un valor. Cuando un objeto se escribe en varias líneas, indentarlo como se muestra en este ejemplo ayuda a la legibilidad. Las propiedades cuyos nombres no son nombres de enlace válidos o números válidos deben ir entre comillas:

+

Dentro de las llaves, se escribe una lista de propiedades separadas por comas. Cada propiedad tiene su nombre, seguido por dos puntos y un valor. Cuando un objeto se escribe en varias líneas, indentarlo como se muestra en este ejemplo ayuda a la legibilidad. Las propiedades cuyos nombres no son nombres de asociación válidos o números válidos deben ir entre comillas:

let descripciones = {
   trabajo: "Fui a trabajar",
   "tocó árbol": "Tocó un árbol"
 };
-

Esto significa que las llaves tienen dos significados en JavaScript. Al principio de una sentencia, comienzan un bloque de sentencias. En cualquier otra posición, describen un objeto. Afortunadamente, rara vez es útil comenzar una sentencia con un objeto entre llaves, por lo que la ambigüedad entre estos dos casos no es gran problema. El único caso en el que esto surge es cuando quiere devolver un objeto desde una función flecha abreviada: no se puede escribir n => {prop: n}, ya que las llaves se interpretarán como el cuerpo de una función. En cambio, se debe poner un conjunto de paréntesis alrededor del objeto para dejar claro que es una expresión.

+

Esto significa que las llaves tienen dos significados en JavaScript. Al principio de una sentencia, comienzan un bloque de sentencias. En cualquier otra posición, describen un objeto. Por suerte, rara vez es útil comenzar una sentencia con un objeto entre llaves, por lo que la ambigüedad entre estos dos casos no es un problema como tal. El único caso en el que se da algo así es cuando quierea devolver un objeto desde una función flecha abreviada: no se puede escribir n => {prop: n}, ya que las llaves se interpretarán como el cuerpo de una función. En cambio, se debe poner un conjunto de paréntesis alrededor del objeto para dejar claro que es una expresión.

-

Al leer una propiedad que no existe, obtendrás el valor undefined.

+

Leer una propiedad que no existe dará como resultado el valor undefined.

Es posible asignar un valor a una expresión de propiedad con el operador =. Esto reemplazará el valor de la propiedad si ya existía o creará una nueva propiedad en el objeto si no existía.

-

Para volver brevemente a nuestro modelo de tentáculos de enlaces, los enlaces de propiedad son similares. Agarran valores, pero otros enlaces y propiedades podrían estar aferrándose a esos mismos valores. Puedes pensar en los objetos como pulpos con cualquier cantidad de tentáculos, cada uno con un nombre escrito en él.

+

Por volver un momento a nuestro modelo de tentáculos para las asociaciones —las asociaciones de propiedad son parecidas. Estas agarran valores, pero otras asociaciones y propiedades podrían estar aferrándose a esos mismos valores. Puedes pensar en los objetos como pulpos con una cantidad cualquiera de tentáculos, cada uno con un nombre escrito en él.

-

El operador delete corta un tentáculo de dicho pulpo. Es un operador unario que, cuando se aplica a una propiedad de un objeto, eliminará la propiedad nombrada del objeto. Esto no es algo común de hacer, pero es posible.

+

El operador delete corta un tentáculo de dicho pulpo. Es un operador unario que, cuando se aplica a una propiedad de un objeto, eliminará la propiedad del objeto que se ha nombrado. No es que se trate de algo común, pero se puede hacer.

let unObjeto = {izquierda: 1, derecha: 2};
 console.log(unObjeto.izquierda);
@@ -149,9 +153,9 @@ 

"derecha" in unObjeto); // → true

-

El operador binario in, cuando se aplica a una cadena y a un objeto, te dice si ese objeto tiene una propiedad con ese nombre. La diferencia entre establecer una propiedad como undefined y realmente borrarla es que, en el primer caso, el objeto todavía tiene la propiedad (simplemente no tiene un valor muy interesante), mientras que en el segundo caso la propiedad ya no está presente y in devolverá false.

+

El operador binario in, cuando se aplica a una cadena y a un objeto, te dice si ese objeto tiene una propiedad con ese nombre. La diferencia entre establecer una propiedad como undefined y realmente borrarla es que, en el primer caso, el objeto todavía tiene la propiedad (simplemente no tiene un valor muy interesante), mientras que en el segundo caso la propiedad ya no está presente e in devolverá false.

-

Para averiguar qué propiedades tiene un objeto, puedes utilizar la función Object.keys. Al darle la función un objeto, devolverá un array de cadenas: los nombres de las propiedades del objeto:

+

Para averiguar qué propiedades tiene un objeto, puedes utilizar la función Object.keys. Dale a la función un objeto, y te devolverá un array de cadenas: los nombres de las propiedades del objeto.

console.log(Object.keys({x: 0, y: 0, z: 2}));
 // → ["x", "y", "z"]
@@ -163,9 +167,9 @@

// → {a: 1, b: 3, c: 4}

-

Los arrays, entonces, son solo un tipo de objeto especializado para almacenar secuencias de cosas. Si evalúas typeof [], producirá "object". Puedes visualizar los arrays como pulpos largos y planos con todos sus tentáculos en una fila ordenada, etiquetados con números.

+

Los arrays, por tanto, no son más que un tipo de objeto especializado para almacenar secuencias de cosas. Si evalúas typeof [], producirá "object". Puedes visualizar los arrays como pulpos largos y planos con todos sus tentáculos en una fila ordenada, etiquetados con números.

-

Jacques representará el diario que lleva como un array de objetos:

+

Jacques va a representar el diario que lleva como un array de objetos:

let diario = [
   {eventos: ["trabajo", "tocó árbol", "pizza",
@@ -182,32 +186,32 @@ 

Mutabilidad

-

Pronto llegaremos a la programación real, pero primero, hay una pieza más de teoría para entender.

+

Pronto llegaremos a la programación real, pero primero, hay una cosa más de la teoría que hay que saber.

-

Vimos que los valores de objetos pueden modificarse. Los tipos de valores discutidos en capítulos anteriores, como números, cadenas y booleanos, son todos inmutables—es imposible cambiar valores de esos tipos. Puedes combinarlos y derivar nuevos valores de ellos, pero al tomar un valor específico de cadena, ese valor siempre permanecerá igual. El texto dentro de él no puede ser cambiado. Si tienes una cadena que contiene "gato", no es posible que otro código cambie un carácter en tu cadena para que diga "rata".

+

Hemos visto que los valores de objetos pueden modificarse. Los tipos de valores de los que hemos hablado en capítulos anteriores, como números, cadenas y booleanos, son todos inmutables —es imposible cambiar valores de esos tipos. Puedes combinarlos y obtener nuevos valores a partir de ellos, pero cuando consideras un valor específico de cadena, ese valor siempre va a ser el mismo. El texto dentro de él no se puede cambiar. Si tienes una cadena que contiene "gato", no es posible que otro código cambie un carácter en tu cadena para que diga "rata".

-

Los objetos funcionan de manera diferente. Puedes cambiar sus propiedades, lo que hace que un valor de objeto tenga un contenido diferente en momentos diferentes.

+

Los objetos funcionan de manera distinta. Puedes cambiar sus propiedades, lo que hace que un valor de objeto vaya cambiando su contenido con el tiempo.

-

Cuando tenemos dos números, 120 y 120, podemos considerarlos precisamente el mismo número, tanto si se refieren a los mismos bits físicos como si no. Con los objetos, hay una diferencia entre tener dos referencias al mismo objeto y tener dos objetos diferentes que contienen las mismas propiedades. Considera el siguiente código:

+

Cuando tenemos dos números, 120 y 120, podemos considerarlos precisamente el mismo número, tanto si se refieren físicamente a los mismos bits como si no. Con los objetos, hay una diferencia entre tener dos referencias al mismo objeto y tener dos objetos diferentes que contienen las mismas propiedades. Considera el siguiente código:

-
let object1 = {value: 10};
-let object2 = object1;
-let object3 = {value: 10};
+
let objeto1 = {valor: 10};
+let objeto2 = objeto1;
+let objeto3 = {valor: 10};
 
-console.log(object1 == object2);
+console.log(objeto1 == objeto2);
 // → true
-console.log(object1 == object3);
+console.log(objeto1 == objeto3);
 // → false
 
-object1.value = 15;
-console.log(object2.value);
+objeto1.valor = 15;
+console.log(objeto2.valor);
 // → 15
-console.log(object3.value);
+console.log(objeto3.valor);
 // → 10
-

Las asignaciones object1 y object2 contienen la misma referencia al objeto, por lo que al cambiar object1 también se cambia el valor de object2. Se dice que tienen la misma identidad. La asignación object3 apunta a un objeto diferente, que inicialmente contiene las mismas propiedades que object1 pero vive una vida separada.

+

Las asignaciones object1 y object2 referencian al mismo objeto, por lo que al cambiar object1 también se cambia el valor de object2. Se dice que tienen la misma identidad. La asignación object3 apunta a un objeto diferente, que inicialmente contiene las mismas propiedades que object1 pero tiene su vida por separado.

-

Las asignaciones pueden ser modificables o constantes, pero esto es independiente de cómo se comportan sus valores. Aunque los valores numéricos no cambian, puedes utilizar una asignación let para hacer un seguimiento de un número que cambia al cambiar el valor al que apunta la asignación. De manera similar, aunque una asignación const a un objeto en sí no puede cambiarse y seguirá apuntando al mismo objeto, los contenidos de ese objeto pueden cambiar.

+

Las asociaciones pueden ser modificables o constantes, pero esto es independiente de cómo se comportan sus valores. Por mucho que los valores numéricos no cambien, siempre puedes utilizar una asignación usando let para modelar el seguimiento de un número que cambia simplemente cambiando el valor al que apunta la asignación. Del mismo modo, aunque una asignación a un objeto con const en sí no puede cambiarse y seguirá apuntando siempre al mismo objeto, los contenidos de ese objeto sí que pueden cambiar.

const score = {visitors: 0, home: 0};
 // Esto está bien
@@ -215,133 +219,141 @@ 

// Esto no está permitido score = {visitors: 1, home: 1};

-

Cuando se comparan objetos con el operador == de JavaScript, se compara por identidad: producirá true solo si ambos objetos son exactamente el mismo valor. Comparar objetos diferentes devolverá false, incluso si tienen propiedades idénticas. No hay una operación de comparación “profunda” incorporada en JavaScript que compare objetos por contenido, pero es posible escribirla tú mismo (lo cual es uno de los ejercicios al final de este capítulo).

+

Cuando se comparan objetos con el operador == de JavaScript, se compara por identidad: obtendremos true solo si ambos objetos son exactamente el mismo valor. Comparar objetos diferentes devolverá false, incluso aunque tengan propiedades idénticas. No hay una operación de comparación “profunda” incorporada en JavaScript que compare objetos por contenido, pero podrías escribirla tú mismo (lo cual es uno de los ejercicios al final de este capítulo).

El diario del licántropo

Jacques inicia su intérprete de JavaScript y configura el entorno que necesita para mantener su diario:

-
let journal = [];
+
let diario = [];
 
-function addEntry(events, squirrel) {
-  journal.push({events, squirrel});
+function añadirEntrada(eventos, ardilla) {
+  diario.push({eventos, ardilla});
 }
-

Observa que el objeto agregado al diario luce un poco extraño. En lugar de declarar propiedades como events: events, simplemente se da un nombre de propiedad: events. Esta es una forma abreviada que significa lo mismo: si un nombre de propiedad en notación de llaves no va seguido de un valor, su valor se toma del enlace con el mismo nombre.

+

Fíjate en que el objeto agregado al diario tiene una pinta un poco rara. En vez de declarar propiedades como eventos: eventos, simplemente se da un nombre de propiedad: eventos. Esta es una forma abreviada que significa lo mismo: si un nombre de propiedad en notación de llaves no va seguido de un valor, su valor se saca del enlace con el mismo nombre.

+ +

Cada noche a las 10 p.m. —o a veces a la mañana siguiente después de bajar de la repisa superior de su estantería—, Jacques registra el día:

+ +
añadirEntrada(["trabajo", "tocó árbol", "pizza", 
+          "correr", "televisión"], false);
+añadirEntrada(["trabajo", "helado", "coliflor", "lasaña",
+          "tocó árbol", "se cepilló los dientes"], false);
+añadirEntrada(["fin de semana", "ciclismo", "descanso", "cacahuetes",
+          "cerveza"], true);
-

Cada noche a las 10 p.m., o a veces a la mañana siguiente después de bajar de la repisa superior de su estantería, Jacques registra el día:

+

Una vez que tenga suficientes datos, tiene la intención de hacer estadística para descubrir cuáles de esos eventos pueden estar relacionados con las ardillificaciones.

-
addEntry(["work", "touched tree", "pizza", "running",
-          "television"], false);
-addEntry(["work", "ice cream", "cauliflower", "lasagna",
-          "touched tree", "brushed teeth"], false);
-addEntry(["weekend", "cycling", "break", "peanuts",
-          "beer"], true);
+

La correlación es una medida de la dependencia entre variables estadísticas. Una variable estadística no es exactamente lo mismo que una variable de programación. En estadística, normalmente tienes un conjunto de mediciones. En cada medición se miden todas las variables. La correlación entre variables suele expresarse como un valor entre -1 y 1. Una correlación de cero significa que las variables no tienen relación. Una correlación de 1 indica que las dos están perfectamente relacionadas: si conoces una, también conoces la otra. Un -1 también significa que las variables están perfectamente relacionadas pero son opuestas: cuando una es verdadera, la otra es falsa.

-

Una vez que tiene suficientes puntos de datos, tiene la intención de utilizar estadísticas para descubrir qué eventos pueden estar relacionados con las transformaciones en ardilla.

+

Para calcular la correlación entre dos variables booleanas, podemos utilizar el coeficiente phi (ϕ). Este es una función cuya entrada es una tabla de frecuencias que contiene la cantidad de veces que se han observado las diferentes combinaciones de las variables. La salida de la fórmula es un número entre -1 y 1 que describe la correlación.

-

La correlación es una medida de la dependencia entre variables estadísticas. Una variable estadística no es exactamente igual a una variable de programación. En estadística, típicamente tienes un conjunto de mediciones, y cada variable se mide para cada medición. La correlación entre variables suele expresarse como un valor que va de -1 a 1. Una correlación de cero significa que las variables no están relacionadas. Una correlación de 1 indica que las dos están perfectamente relacionadas: si conoces una, también conoces la otra. Un -1 también significa que las variables están perfectamente relacionadas pero son opuestas: cuando una es verdadera, la otra es falsa.

+

N. del T.: Aquí estamos usando la palabra función en el sentido matemático, evitando así el uso de la palabra fórmula para referirse a esta. Por supuesto, dicha función se calcula a través de una fórmula, o un cálculo matemático.

+
-

Para calcular la medida de correlación entre dos variables booleanas, podemos utilizar el coeficiente phi (ϕ). Esta es una fórmula cuya entrada es una tabla de frecuencias que contiene la cantidad de veces que se observaron las diferentes combinaciones de las variables. La salida de la fórmula es un número entre -1 y 1 que describe la correlación.

+

Podríamos considerar el evento de comer pizza y apuntarlo en una tabla de frecuencias como esta, donde cada número indica la cantidad de veces que ocurrido esa combinación en nuestras mediciones.

Una tabla de tamaño dos por dos que muestra la variable pizza en el eje horizontal y la variable ardilla en el eje vertical. Cada celda muestra cuántas veces ocurrió esa combinación. En 76 casos, no ha ocurrido ninguna. En 9 casos, solo se dio el suceso de comer pizza. En 4 casos, solo se dio el suceso de transformarse en ardilla. Y en un caso ambas cosas sucedieron a la vez.
-

Podríamos tomar el evento de comer pizza y ponerlo en una tabla de frecuencias como esta, donde cada número indica la cantidad de veces que ocurrió esa combinación en nuestras mediciones.

Una tabla de dos por dos que muestra la variable pizza en el eje horizontal y la variable ardilla en el eje vertical. Cada celda muestra cuántas veces ocurrió esa combinación. En 76 casos, ninguna ocurrió. En 9 casos, solo la pizza era verdadera. En 4 casos, solo la ardilla era verdadera. Y en un caso ambas ocurrieron.
+

N. del T.: squirrel significa ardilla. Las filas representan los posibles valores para el suceso de transformarse en ardilla, mientras que las columnas representan si se comió pizza ese día o no.

+
-

Si llamamos a esa tabla n, podemos calcular ϕ utilizando la siguiente fórmula:

ϕ =
n11n00n10n01
n1•n0•n•1n•0
+

Si llamamos n a esta tabla, podemos calcular el ϕ asociado utilizando la siguiente fórmula:

ϕ =
n11n00n10n01
n1•n0•n•1n•0
-

(Si en este punto estás dejando el libro para concentrarte en un terrible flashback a la clase de matemáticas de décimo grado, ¡espera! No pretendo torturarte con interminables páginas de notación críptica, solo es esta fórmula por ahora. Y incluso con esta, todo lo que haremos es convertirla en JavaScript).

+

—Si llegados a este punto estás tirando el libro mientras te concentras en un terrible flashback de la clase de matemáticas de 4º de ESO, ¡espera! No busco torturarte con interminables páginas de notación críptica. Por ahora será solo esta fórmula. E incluso con esta, lo único que vamos a hacer es convertirla en JavaScript.

-

La notación n01 indica la cantidad de mediciones donde la primera variable (ardilla) es falsa (0) y la segunda variable (pizza) es verdadera (1). En la tabla de pizza, n01 es 9.El valor n1• se refiere a la suma de todas las mediciones donde la primera variable es verdadera, que es 5 en el ejemplo de la tabla. De manera similar, n•0 se refiere a la suma de las mediciones donde la segunda variable es falsa.

+

La notación n01 indica la cantidad de mediciones donde la primera variable (ardilla) es falsa (0) y la segunda variable (pizza) es verdadera (1). En nuestra tabla de pizza, n01 es 9.

-

Entonces para la tabla de pizza, la parte encima de la línea de división (el dividendo) sería 1×76−4×9 = 40, y la parte debajo de ella (el divisor) sería la raíz cuadrada de 5×85×10×80, o √340,000. Esto da un valor de ϕ ≈ 0.069, que es muy pequeño. Comer pizza no parece tener influencia en las transformaciones.

+

El valor n1• se refiere a la suma de todas las mediciones donde la primera variable es verdadera, que, en el ejemplo de la tabla, es 5. De manera similar, n•0 se refiere a la suma de las mediciones donde la segunda variable es falsa.

+ +

Así que para la tabla de pizza, la parte de arriba de la fracción (el dividendo) sería 1×76−4×9 = 40, y la parte de abajo (el divisor) sería la raíz cuadrada de 5×85×10×80, o √340,000. Así que ϕ ≈ 0.069, que es un valor muy pequeño. Comer pizza no parece influir en las transformaciones.

Calculando la correlación

-

Podemos representar una tabla dos por dos en JavaScript con un array de cuatro elementos ([76, 9, 4, 1]). También podríamos usar otras representaciones, como un array que contiene dos arrays de dos elementos cada uno ([[76, 9], [4, 1]]) o un objeto con nombres de propiedades como "11" y "01", pero el array plano es simple y hace que las expresiones que acceden a la tabla sean agradabemente cortas. Interpretaremos los índices del array como números binarios de dos bits, donde el dígito más a la izquierda (más significativo) se refiere a la variable ardilla y el dígito más a la derecha (menos significativo) se refiere a la variable de evento. Por ejemplo, el número binario 10 se refiere al caso donde Jacques se transformó en ardilla, pero el evento (digamos, “pizza”) no ocurrió. Esto sucedió cuatro veces. Y como 10 en binario es 2 en notación decimal, almacenaremos este número en el índice 2 del array.

+

Podemos representar una tabla dos por dos en JavaScript con un array de cuatro elementos ([76, 9, 4, 1]). También podríamos usar otras representaciones, como un array que contiene dos arrays de dos elementos cada uno ([[76, 9], [4, 1]]) o un objeto con nombres de propiedades como "11" y "01", pero el primer array que hemos propuesto es simple y hace que las expresiones que acceden a la tabla sean agradablemente cortas. Vamos a interpretar los índices del array como números de dos bits en binario, donde el dígito más a la izquierda (el más significativo) se refiere a la variable ardilla y el dígito más a la derecha (el menos significativo) se refiere a la variable de evento. Por ejemplo, el número binario 10 se refiere al caso donde Jacques se transforma en ardilla, pero el evento (digamos, “pizza”) no ocurre. Esto sucede cuatro veces. Como el 10 es la representación en binario del número 2, almacenaremos este número en el índice 2 del array.

Esta es la función que calcula el coeficiente ϕ a partir de dicho array:

-
function phi(table) {
-  return (table[3] * table[0] - table[2] * table[1]) /
-    Math.sqrt((table[2] + table[3]) *
-              (table[0] + table[1]) *
-              (table[1] + table[3]) *
-              (table[0] + table[2]));
+
function phi(tabla) {
+  return (tabla[3] * tabla[0] - tabla[2] * tabla[1]) /
+    Math.sqrt((tabla[2] + tabla[3]) *
+              (tabla[0] + tabla[1]) *
+              (tabla[1] + tabla[3]) *
+              (tabla[0] + tabla[2]));
 }
 
 console.log(phi([76, 9, 4, 1]));
 // → 0.068599434
-

Esta es una traducción directa de la fórmula de ϕ a JavaScript. Math.sqrt es la función de raíz cuadrada, como se provee en el objeto Math en un entorno estándar de JavaScript. Debemos agregar dos campos de la tabla para obtener campos como n1• porque las sumas de filas o columnas no se almacenan directamente en nuestra estructura de datos.

+

Esta es una traducción directa de la fórmula de ϕ a JavaScript. Math.sqrt es la función de raíz cuadrada, que viene incluida en el objeto Math en un entorno estándar de JavaScript. Para obtener campos de la forma n1• tenemos que sumar los correspondientes pares de la tabla, ya que las sumas de filas o columnas no se almacenan directamente en nuestra estructura de datos.

-

Jacques mantiene su diario por tres meses. El conjunto de datos resultante está disponible en el sandbox de código para este capítulo, donde se almacena en el vínculo JOURNAL, y en un archivo descargable aquí.

+

Jacques escribe en su diario durante tres meses. El conjunto de datos resultante está disponible en el sandbox de código para este capítulo —donde se almacena en la variable JOURNAL— y en un archivo descargable.

-

Para extraer una tabla dos por dos para un evento específico del diario, debemos recorrer todas las entradas y contar cuántas veces ocurre el evento en relación con las transformaciones a ardilla:

+

Para montar una tabla dos por dos sobre un evento específico del diario, tenemos que recorrer todas las entradas y contar cuántas veces ocurre el evento en relación con las transformaciones en ardilla:

-
function tableFor(event, journal) {
-  let table = [0, 0, 0, 0];
-  for (let i = 0; i < journal.length; i++) {
-    let entry = journal[i], index = 0;
-    if (entry.events.includes(event)) index += 1;
-    if (entry.squirrel) index += 2;
-    table[index] += 1;
+
function tablaPara(evento, diario) {
+  let tabla = [0, 0, 0, 0];
+  for (let i = 0; i < diario.length; i++) {
+    let entrada = diario[i], índice = 0;
+    if (entrada.eventos.includes(evento)) índice += 1;
+    if (entrada.ardilla) índice += 2;
+    tabla[índice] += 1;
   }
-  return table;
+  return tabla;
 }
 
-console.log(tableFor("pizza", JOURNAL));
+console.log(tablaPara("pizza", JOURNAL));
 // → [76, 9, 4, 1]
-

Los arrays tienen un método includes que comprueba si un valor dado existe en el array. La función utiliza esto para determinar si el nombre del evento en el que está interesado forma parte de la lista de eventos de un día dado.

+

Los arrays tienen un método includes que comprueba si un valor dado existe en el array. La función utiliza esto para determinar si el nombre del evento en el que está interesada forma parte de la lista de eventos de un día dado.

-

El cuerpo del bucle en tableFor determina en qué caja de la tabla cae cada entrada del diario, verificando si la entrada contiene el evento específico en el que está interesado y si el evento ocurre junto con un incidente de ardilla. Luego, el bucle suma uno a la caja correcta de la tabla.

+

El cuerpo del bucle en la función tablaPara determina en qué parte de la tabla cae cada entrada del diario, verificando si la entrada contiene el evento específico en el que está interesada y si el evento ocurre un día en el que Jacques se transforma en ardilla. Luego, el bucle suma uno a la caja correcta de la tabla.

-

Ahora tenemos las herramientas necesarias para calcular correlaciones individuales. El único paso restante es encontrar una correlación para cada tipo de evento que se registró y ver si algo destaca.

+

Ahora tenemos las herramientas necesarias para calcular correlaciones individuales. El único paso restante es encontrar una correlación para cada tipo de evento que se registró y ver si hay aglo que destaque.

Bucles de Array

-

En la función tableFor, hay un bucle como este:

+

En la función tablaPara, hay un bucle como este:

-
for (let i = 0; i < JOURNAL.length; i++) {
-  let entry = JOURNAL[i];
-  // Hacer algo con entry
+
for (let i = 0; i < JOURNAL.length; i++) {
+  let entrada = JOURNAL[i];
+  // Hacer algo con entrada
 }
-

Este tipo de bucle es común en el JavaScript clásico; recorrer arrays elemento por elemento es algo que se hace con frecuencia, y para hacerlo se recorre un contador sobre la longitud del array y se selecciona cada elemento por turno.

+

Este tipo de bucle es común en el JavaScript clásico —recorrer arrays elemento a elemento es algo que se hace con frecuencia, y para hacerlo se recorre un contador sobre la longitud del array y se selecciona el elemento de turno.

-

Hay una forma más sencilla de escribir tales bucles en JavaScript moderno:

+

En JavaScript moderno hay una forma más sencilla de escribir tales bucles:

-
for (let entry of JOURNAL) {
-  console.log(`${entry.events.length} eventos.`);
+
for (let entrada of JOURNAL) {
+  console.log(`${entrada.eventos.length} eventos.`);
 }

Cuando un bucle for usa la palabra of después de la definición de su variable, recorrerá los elementos del valor dado después de of. Esto no solo funciona para arrays, sino también para cadenas y algunas otras estructuras de datos. Discutiremos cómo funciona en el Capítulo 6.

El análisis final

-

Necesitamos calcular una correlación para cada tipo de evento que ocurre en el conjunto de datos. Para hacerlo, primero necesitamos encontrar cada tipo de evento.

+

Necesitamos calcular una correlación para cada tipo de evento que aparece en el conjunto de datos. Para hacerlo, primero necesitamos encontrar todos los tipos de evento.

-
function journalEvents(journal) {
-  let events = [];
-  for (let entry of journal) {
-    for (let event of entry.events) {
-      if (!events.includes(event)) {
-        events.push(event);
+
function eventosDiario(diario) {
+  let eventos = [];
+  for (let entrada of diario) {
+    for (let evento of entrada.eventos) {
+      if (!eventos.includes(evento)) {
+        eventos.push(evento);
       }
     }
   }
-  return events;
+  return eventos;
 }
 
-console.log(journalEvents(JOURNAL));
+console.log(eventosDiario(JOURNAL));
 // → ["zanahoria", "ejercicio", "fin de semana", "pan", …]
-

Agregando los nombres de cualquier evento que no estén en él al array events, la función recopila todos los tipos de eventos.

+

La función recopila todos los tipos de evento añadiendo los nombres de cualquier evento que no esté ya en el array events.

Usando esa función, podemos ver todas las correlaciones:

-
for (let event of journalEvents(JOURNAL)) {
-  console.log(event + ":", phi(tableFor(event, JOURNAL)));
+
for (let evento of eventosDiario(JOURNAL)) {
+  console.log(evento + ":", phi(tablaPara(evento, JOURNAL)));
 }
 // → zanahoria:   0.0140970969
 // → ejercicio: 0.0685994341
@@ -350,12 +362,12 @@ 

// → pudín: -0.0648203724 // y así sucesivamente...

-

La mayoría de las correlaciones parecen estar cerca de cero. Comer zanahorias, pan o pudín aparentemente no desencadena la licantropía de las ardillas. Las transformaciones parecen ocurrir un poco más a menudo los fines de semana. Filtraremos los resultados para mostrar solo correlaciones mayores que 0.1 o menores que -0.1:

+

La mayoría de las correlaciones parecen estar cerca de cero. Comer zanahorias, pan o pudín aparentemente no desencadenan la ardillolicantropía. Las transformaciones parecen ocurrir un poco más a menudo en fines de semana. Filtraremos los resultados para mostrar solo correlaciones mayores que 0.1 o menores que -0.1:

-
for (let event of journalEvents(JOURNAL)) {
-  let correlation = phi(tableFor(event, JOURNAL));
-  if (correlation > 0.1 || correlation < -0.1) {
-    console.log(event + ":", correlation);
+
for (let evento of eventosDiario(JOURNAL)) {
+  let correlación = phi(tablaPara(evento, JOURNAL));
+  if (correlación > 0.1 || correlación < -0.1) {
+    console.log(evento + ":", correlación);
   }
 }
 // → fin de semana:        0.1371988681
@@ -366,30 +378,30 @@ 

// → lectura: 0.1106828054 // → cacahuetes: 0.5902679812

-

¡Ajá! Hay dos factores con una correlación claramente más fuerte que los demás. Comer cacahuetes tiene un fuerte efecto positivo en la posibilidad de convertirse en una ardilla, mientras que cepillarse los dientes tiene un efecto negativo significativo.

+

¡Ajá! Hay dos factores con una correlación claramente más fuerte que los demás. Comer cacahuetes tiene un fuerte efecto positivo en la posibilidad de convertirse en ardilla, mientras que cepillarse los dientes tiene un significante efecto negativo.

Interesante. Intentemos algo:

-
for (let entry of JOURNAL) {
-  if (entry.events.includes("cacahuetes") &&
-     !entry.events.includes("cepillarse los dientes")) {
-    entry.events.push("dientes de cacahuate");
+
for (let entrada of JOURNAL) {
+  if (entrada.eventos.includes("cacahuetes") &&
+     !entrada.eventos.includes("cepillarse los dientes")) {
+    entrada.eventos.push("cacahuate en los dientes");
   }
 }
-console.log(phi(tableFor("dientes de cacahuate", JOURNAL)));
+console.log(phi(tablaPara("cacahuate en los dientes", JOURNAL)));
 // → 1
-

Ese es un resultado sólido. El fenómeno ocurre precisamente cuando Jacques come cacahuetes y no se cepilla los dientes. Si tan solo no fuera tan descuidado con la higiene dental, ni siquiera se habría dado cuenta de su aflicción.

+

Ese es un resultado sólido. El fenómeno ocurre precisamente cuando Jacques come cacahuetes y no se cepilla los dientes. Si no fuera tan descuidado con la higiene dental, ni siquiera se habría dado cuenta de su trastorno.

Sabiendo esto, Jacques deja de comer cacahuetes por completo y descubre que sus transformaciones se detienen.

-

Pero solo pasan unos pocos meses antes de que se dé cuenta de que algo falta en esta forma de vivir completamente humana. Sin sus aventuras salvajes, Jacques apenas se siente vivo. Decide que prefiere ser un animal salvaje a tiempo completo. Después de construir una hermosa casita en un árbol en el bosque y equiparla con un dispensador de mantequilla de cacahuate y un suministro de diez años de mantequilla de cacahuate, cambia de forma por última vez y vive la corta y enérgica vida de una ardilla.

+

Pero solo pasan unos pocos meses antes de que se dé cuenta de que le falta algo en esta forma de vivir completamente humana. Sin sus aventuras salvajes, Jacques apenas se siente vivo. Decide que prefiere ser un animal salvaje a tiempo completo. Después de construir una hermosa casita en un árbol del bosque y equiparla con un dispensador de mantequilla de cacahuate con diez años de suministro, cambia de forma por última vez y vive la corta y enérgica vida de una ardilla.

-

Más arreología

+

Más arreglología

-

Antes de terminar el capítulo, quiero presentarte algunos conceptos más relacionados con objetos. Comenzaré presentando algunos métodos de array generalmente útiles.

+

Antes de terminar el capítulo, quiero presentarte algunos conceptos más relacionados con objetos. Comenzaré presentando algunos métodos generalmente útiles de los arrays.

-

Vimos push y pop, que agregan y eliminan elementos al final de un array, anteriormente en este capítulo. Los métodos correspondientes para agregar y eliminar cosas al principio de un array se llaman unshift y shift.

+

Anteriormente en este capítulo, vimos push y pop, que agregan y eliminan elementos al final de un array. Los métodos correspondientes para agregar y eliminar cosas al principio de un array se llaman unshift y shift.

let listaDeTareas = [];
 function recordar(tarea) {
@@ -402,9 +414,9 @@ 

-

Este programa gestiona una cola de tareas. Agregas tareas al final de la cola llamando a recordar("comestibles"), y cuando estás listo para hacer algo, llamas a obtenerTarea() para obtener (y eliminar) el primer elemento de la cola. La función recordarUrgente también agrega una tarea pero la agrega al principio en lugar de al final de la cola.

+

Este programa gestiona una cola de tareas. Agregas tareas al final de la cola llamando a recordar("compras"), y cuando estás listo para hacer algo, llamas a obtenerTarea() para obtener (y eliminar) el primer elemento de la cola. La función recordarUrgente también agrega una tarea pero la agrega al principio en lugar de al final de la cola.

-

Para buscar un valor específico, los arrays proporcionan un método indexOf. Este método busca a través del array desde el principio hasta el final y devuelve el índice en el que se encontró el valor solicitado, o -1 si no se encontró. Para buscar desde el final en lugar de desde el principio, existe un método similar llamado lastIndexOf:

+

Para buscar un valor específico, los arrays proporcionan un método indexOf. Este método busca a través del array desde el principio hasta el final y devuelve el primer índice en el que se encontró el valor solicitado, o -1 si no se encontró. Para buscar desde el final en lugar de desde el principio, existe un método similar llamado lastIndexOf:

console.log([1, 2, 3, 2, 1].indexOf(2));
 // → 1
@@ -413,7 +425,7 @@ 

Tanto indexOf como lastIndexOf admiten un segundo argumento opcional que indica dónde comenzar la búsqueda.

-

Otro método fundamental de los arrays es slice, que toma índices de inicio y fin y devuelve un array que solo contiene los elementos entre ellos. El índice de inicio es inclusivo, mientras que el índice de fin es exclusivo.

+

Otro método fundamental de los arrays es slice, que recibe un índice inicial y otro final y devuelve un array que solo contiene los elementos entre ellos. El índice de inicio es inclusivo, mientras que el índice de fin es exclusivo.

console.log([0, 1, 2, 3, 4].slice(2, 4));
 // → [2, 3]
@@ -424,58 +436,58 @@ 

El método concat se puede usar para concatenar arrays y crear un nuevo array, similar a lo que el operador + hace para las strings.

-

El siguiente ejemplo muestra tanto concat como slice en acción. Toma un array y un índice y devuelve un nuevo array que es una copia del array original con el elemento en el índice dado eliminado:

+

El siguiente ejemplo muestra tanto concat como slice en acción. Toma un array y un índice y devuelve un nuevo array que es una copia del array original sin el elemento correspondiente al índice dado:

-
function remove(array, index) {
-  return array.slice(0, index)
-    .concat(array.slice(index + 1));
+
function eliminar(array, índice) {
+  return array.slice(0, índice)
+    .concat(array.slice(índice + 1));
 }
-console.log(remove(["a", "b", "c", "d", "e"], 2));
+console.log(eliminar(["a", "b", "c", "d", "e"], 2));
 // → ["a", "b", "d", "e"]
-

Si le pasas a concat un argumento que no es un array, ese valor se agregará al nuevo array como si fuera un array de un solo elemento.

+

Si le pasas a concat un argumento que no es un array, ese valor se agregará al nuevo array creado por concat como si fuera un array de un solo elemento.

Strings y sus propiedades

-

Podemos acceder a propiedades como length y toUpperCase en valores de tipo string. Pero si intentamos añadir una nueva propiedad, esta no se conserva.

+

Podemos acceder a propiedades como length y toUpperCase en valores de tipo cadena (string). Pero si intentamos añadir una nueva propiedad, esta no se conserva.

let kim = "Kim";
 kim.age = 88;
 console.log(kim.age);
 // → undefined
-

Los valores de tipo string, number y Boolean no son objetos, y aunque el lenguaje no se queja si intentas establecer nuevas propiedades en ellos, en realidad no almacena esas propiedades. Como se mencionó anteriormente, dichos valores son inmutables y no pueden ser modificados.

+

Los valores de tipo string, number y Boolean no son objetos y, aunque el lenguaje no se queja si intentas establecer nuevas propiedades en ellos, en realidad no almacena esas propiedades. Como se dijo antes, dichos valores son inmutables y no pueden ser modificados.

-

Pero estos tipos tienen propiedades integradas. Cada valor string tiene varios métodos. Algunos muy útiles son slice e indexOf, que se parecen a los métodos de arrays del mismo nombre:

+

Pero estos tipos tienen propiedades integradas. Cada valor de tipo string tiene varios métodos. Algunos muy útiles son slice e indexOf, que se parecen a los métodos de arrays del mismo nombre:

-
console.log("coconuts".slice(4, 7));
-// → nut
-console.log("coconut".indexOf("u"));
-// → 5
+
console.log("cocos".slice(2, 4));
+// → co
+console.log("coco".indexOf("o"));
+// → 1

Una diferencia es que el indexOf de un string puede buscar un string que contenga más de un carácter, mientras que el método correspondiente de arrays busca solo un elemento:

-
console.log("one two three".indexOf("ee"));
-// → 11
+
console.log("me gusta leer".indexOf("ee"));
+// → 10
-

El método trim elimina los espacios en blanco (espacios, saltos de línea, tabulaciones y caracteres similares) del principio y final de una cadena:

+

El método trim elimina los espacios en blanco (espacios, saltos de línea, tabulaciones y caracteres similares) del principio y el final de una cadena:

-
console.log("  okay \n ".trim());
-// → okay
+
console.log("  de acuerdo \n ".trim());
+// → de acuerdo
-

La función zeroPad del capítulo anterior también existe como un método. Se llama padStart y recibe la longitud deseada y el carácter de relleno como argumentos:

+

La función rellenarConCeros del capítulo anterior también existe como un método. Se llama padStart y recibe la longitud deseada y el caracter de relleno como argumentos:

console.log(String(6).padStart(3, "0"));
 // → 006

Puedes dividir una cadena en cada ocurrencia de otra cadena con split y unirla nuevamente con join:

-
let sentence = "Secretarybirds specialize in stomping";
-let words = sentence.split(" ");
-console.log(words);
-// → ["Secretarybirds", "specialize", "in", "stomping"]
-console.log(words.join(". "));
-// → Secretarybirds. specialize. in. stomping
+
let frase = "El pájaro secretario se especializa en pisotear";
+let palabras = frase.split(" ");
+console.log(palabras);
+// → ["El", "pájaro", "secretario", "se", "especializa", "en", "pisotear"]
+console.log(palabras.join(". "));
+// → El. pájaro. secretario. se. especializa. en. pisotear

Una cadena puede repetirse con el método repeat, que crea una nueva cadena que contiene múltiples copias de la cadena original, pegadas juntas:

@@ -484,42 +496,42 @@

Ya hemos visto la propiedad length del tipo string. Acceder a los caracteres individuales en una cadena se parece a acceder a los elementos de un array (con una complicación que discutiremos en el Capítulo 5).

-
let string = "abc";
-console.log(string.length);
+
let cadena = "abc";
+console.log(cadena.length);
 // → 3
-console.log(string[1]);
+console.log(cadena[1]);
 // → b
-

Parámetros restantes

+

Parámetros Rest

-

Puede ser útil para una función aceptar cualquier cantidad de argumento). Por ejemplo, Math.max calcula el máximo de todos los argumentos que se le pasan. Para escribir una función así, colocas tres puntos antes del último parámetro de la función, de esta manera:

+

Puede ser útil para una función aceptar una cantidad cualquiera de argumentos. Por ejemplo, Math.max calcula el máximo de entre todos los argumentos que se le pasan. Para escribir una función así, colocas tres puntos antes del último parámetro de la función, de esta manera:

-
function max(...numbers) {
-  let result = -Infinity;
-  for (let number of numbers) {
-    if (number > result) result = number;
+
function max(...números) {
+  let resultado = -Infinity;
+  for (let número of números) {
+    if (número > resultado) resultado = número;
   }
-  return result;
+  return resultado;
 }
 console.log(max(4, 1, 9, -2));
 // → 9
-

Cuando se llama a una función así, el parámetro restante se vincula a un array que contiene todos los argumentos restantes. Si hay otros parámetros antes de él, sus valores no forman parte de ese array. Cuando, como en max, es el único parámetro, contendrá todos los argumentos.

+

Cuando se llama a una función así, el parámetro rest se vincula a un array que contiene todos los argumentos restantes. Si hay otros parámetros antes de él, sus valores no forman parte de ese array. Cuando, como en max, es el único parámetro, contendrá todos los argumentos.

Puedes usar una notación similar de tres puntos para llamar a una función con un array de argumentos:

-
let numbers = [5, 1, 7];
-console.log(max(...numbers));
+
let numbers = [5, 1, 7];
+console.log(max(...números));
 // → 7
-

Esto “expande” el array en la llamada de la función, pasando sus elementos como argumentos separados. Es posible incluir un array de esa manera junto con otros argumentos, como en max(9, ...numbers, 2).

+

Esto “expande” el array en la llamada de la función, pasando sus elementos como argumentos separados. Es posible incluir un array de esa manera junto con otros argumentos, como en max(9, ...números, 2).

La notación de array entre corchetes cuadrados permite al operador de triple punto expandir otro array en el nuevo array:

-
let words = ["never", "fully"];
+
let palabras = ["lo", "entenderé"];
 
-console.log(["will", ...words, "understand"]);
-// → ["will", "never", "fully", "understand"]
+console.log(["nunca", ...palabras, "del todo"]); +// → ["nunca", "lo", "entenderé", "del todo"]

Esto funciona incluso en objetos con llaves, donde agrega todas las propiedades de otro objeto. Si una propiedad se agrega varias veces, el último valor añadido es el que se conserva:

@@ -529,15 +541,15 @@

El objeto Math

-

Como hemos visto, Math es una bolsa de funciones de utilidad relacionadas con números, tales como Math.max (máximo), Math.min (mínimo) y Math.sqrt (raíz cuadrada).

+

Como hemos visto, Math es un saco de funciones tales como Math.max (máximo), Math.min (mínimo) y Math.sqrt (raíz cuadrada), para hacer cosas con números.

-

El objeto Math se utiliza como un contenedor para agrupar un conjunto de funcionalidades relacionadas. Solo hay un objeto Math y casi nunca es útil como un valor. Más bien, proporciona un espacio de nombres para que todas estas funciones y valores no tengan que ser enlaces globales.

+

El objeto Math se utiliza como un contenedor para agrupar un conjunto de funcionalidades relacionadas. Solo hay un objeto Math y casi nunca es útil como un valor. Más bien, proporciona un espacio de nombres para que todas estas funciones y valores no tengan que ser variables globales.

-

Tener demasiados enlaces globales “contamina” el espacio de nombres. Cuantos más nombres se hayan tomado, más probable es que sobrescribas accidentalmente el valor de algún enlace existente. Por ejemplo, es probable que quieras nombrar algo max en uno de tus programas. Dado que la función max integrada de JavaScript está protegida de forma segura dentro del objeto Math, no tienes que preocuparte por sobrescribirla.

+

Tener demasiados enlaces globales “contamina” el espacio de nombres. Cuantos más nombres se hayan tomado, más probable es que sobrescribas accidentalmente el valor de alguna asociación existente. Por ejemplo, es probable que quieras nombrar algo con max en uno de tus programas. Dado que la función max integrada de JavaScript está protegida de forma segura dentro del objeto Math, no tienes que preocuparte por sobrescribirla.

-

Muchos lenguajes te detendrán, o al menos te advertirán, cuando estés definiendo un enlace con un nombre que ya está tomado. JavaScript hace esto para enlaces que declaraste con let o const, pero —perversamente— no para enlaces estándar ni para enlaces declarados con var o function.

+

Muchos lenguajes te detendrán, o al menos te advertirán, cuando estés definiendo un enlace con un nombre que ya está tomado. JavaScript hace esto para enlaces que declaraste con let o const, pero —perversamente— no para asociaciones estándar ni para enlaces declarados con var o function.

-

Volviendo al objeto Math. Si necesitas hacer trigonometría, Math puede ayudarte. Contiene cos (coseno), sin (seno) y tan (tangente), así como sus funciones inversas, acos, asin y atan, respectivamente. El número π (pi) —o al menos la aproximación más cercana que cabe en un número de JavaScript— está disponible como Math.PI. Existe una antigua tradición de programación que consiste en escribir los nombres de valores constantes en mayúsculas:

+

Volviendo al objeto Math. Si necesitas hacer trigonometría, Math puede ayudarte. Contiene las funciones cos (coseno), sin (seno) y tan (tangente), así como sus funciones inversas, acos, asin y atan, respectivamente. El número π (pi) —o al menos la aproximación más cercana que cabe en un número de JavaScript— está disponible como Math.PI. Existe una antigua tradición de programación que consiste en escribir los nombres de valores constantes en mayúsculas:

function puntoAleatorioEnCirculo(radio) {
   let ángulo = Math.random() * 2 * Math.PI;
@@ -549,7 +561,7 @@ 

Si no estás familiarizado con senos y cosenos, no te preocupes. Los explicaré cuando se utilicen en este libro, en el Capítulo 14.

-

El ejemplo anterior utilizó Math.random. Esta es una función que devuelve un nuevo número pseudoaleatorio entre cero (inclusive) y uno (exclusivo) cada vez que la llamas:

+

En el ejemplo anterior se ha usado Math.random. Esta es una función que devuelve un nuevo número pseudoaleatorio entre cero (inclusive) y uno (exclusivo) cada vez que la llamas:

console.log(Math.random());
 // → 0.36993729369714856
@@ -558,7 +570,7 @@ 

// → 0.40180766698904335

-

Aunque las computadoras son máquinas deterministas —siempre reaccionan de la misma manera si se les da la misma entrada— es posible hacer que produzcan números que parezcan aleatorios. Para lograrlo, la máquina mantiene algún valor oculto y, cada vez que solicitas un nuevo número aleatorio, realiza cálculos complicados en este valor oculto para crear un valor nuevo. Almacena un nuevo valor y devuelve algún número derivado de este. De esta manera, puede producir números nuevos y difíciles de predecir que se aparentan aleatorios.

+

Aunque las computadoras son máquinas deterministas —siempre reaccionan de la misma manera si se les da la misma entrada—, es posible hacer que produzcan números que parezcan aleatorios. Para lograrlo, la máquina mantiene algún valor oculto y, cada vez que solicitas un nuevo número aleatorio, realiza cálculos complicados en este valor oculto para crear un valor nuevo. Almacena un nuevo valor y devuelve algún número derivado de este. De esta manera, puede producir números nuevos y difíciles de predecir que parecen aleatorios.

Si queremos un número entero aleatorio en lugar de uno fraccionario, podemos usar Math.floor (que redondea hacia abajo al número entero más cercano) en el resultado de Math.random:

@@ -567,21 +579,21 @@

Al multiplicar el número aleatorio por 10, obtenemos un número mayor o igual a 0 y menor que 10. Dado que Math.floor redondea hacia abajo, esta expresión producirá, con igual probabilidad, cualquier número del 0 al 9.

-

También existen las funciones Math.ceil (para “techo”, que redondea hacia arriba al número entero más cercano), Math.round (al número entero más cercano) y Math.abs, que toma el valor absoluto de un número, es decir, niega los valores negativos pero deja los positivos tal como están.

+

También existen las funciones Math.ceil (de “techo”, que redondea hacia arriba al número entero más cercano), Math.round (al número entero más cercano) y Math.abs, que proporciona el valor absoluto de un número, es decir, niega los valores negativos pero deja los positivos tal y como están.

Desestructuración

Volviendo por un momento a la función phi.

-
function phi(table) {
-  return (table[3] * table[0] - table[2] * table[1]) /
-    Math.sqrt((table[2] + table[3]) *
-              (table[0] + table[1]) *
-              (table[1] + table[3]) *
-              (table[0] + table[2]));
+
function phi(tabla) {
+  return (tabla[3] * tabla[0] - tabla[2] * tabla[1]) /
+    Math.sqrt((tabla[2] + tabla[3]) *
+              (tabla[0] + tabla[1]) *
+              (tabla[1] + tabla[3]) *
+              (tabla[0] + tabla[2]));
 }
-

Una razón por la que esta función es difícil de leer es que tenemos una asignación apuntando a nuestro array, pero preferiríamos tener asignaciones para los elementos del array, es decir, let n00 = table[0] y así sucesivamente. Afortunadamente, hay una forma concisa de hacer esto en JavaScript:

+

Una razón por la que esta función es difícil de leer es que tenemos una asociación apuntando a nuestro array, pero preferiríamos tener asociaciones para los elementos del array, es decir, let n00 = table[0] y así sucesivamente. Por suerte, hay una forma concisa de hacer esto en JavaScript:

function phi([n00, n01, n10, n11]) {
   return (n11 * n00 - n10 * n01) /
@@ -589,12 +601,12 @@ 

-

Esto también funciona para asignaciones creadas con let, var o const. Si sabes que el valor que estás asignando es un array, puedes usar corchetes para “mirar dentro” del valor y asignar sus contenidos.

+

Esto también funciona para asignaciones creadas con let, var o const. Si sabes que el valor que estás asignando es un array, puedes usar corchetes para “mirar dentro” de cada valor y crear una asociación a su contenido.

Un truco similar funciona para objetos, usando llaves en lugar de corchetes:

-
let {name} = {name: "Faraji", age: 23};
-console.log(name);
+
let {nombre} = {nombre: "Faraji", edad: 23};
+console.log(nombre);
 // → Faraji

Ten en cuenta que si intentas desestructurar null o undefined, obtendrás un error, igual que si intentaras acceder directamente a una propiedad de esos valores.

@@ -603,56 +615,56 @@

Cuando no estás seguro de si un valor dado produce un objeto pero aún deseas leer una propiedad de él cuando lo hace, puedes usar una variante de la notación de punto: objeto?.propiedad.

-
function city(objeto) {
-  return objeto.address?.city;
+
function ciudad(objeto) {
+  return objeto.dirección?.ciudad;
 }
-console.log(city({address: {city: "Toronto"}}));
+console.log(ciudad({dirección: {ciudad: "Toronto"}}));
 // → Toronto
-console.log(city({name: "Vera"}));
+console.log(ciudad({nombre: "Vera"}));
 // → undefined

La expresión a?.b significa lo mismo que a.b cuando a no es nulo o indefinido. Cuando lo es, se evalúa como indefinido. Esto puede ser conveniente cuando, como en el ejemplo, no estás seguro de si una propiedad dada existe o cuando una variable podría contener un valor indefinido.

Una notación similar se puede utilizar con el acceso a corchetes cuadrados, e incluso con llamadas de funciones, colocando ?. delante de los paréntesis o corchetes:

-
console.log("string".notAMethod?.());
+
console.log("cadena".metodoNoExistente?.());
 // → undefined
-console.log({}.arrayProp?.[0]);
+console.log({}.propiedadArray?.[0]);
 // → undefined

JSON

-

Debido a que las propiedades capturan su valor en lugar de contenerlo, los objetos y arrays se almacenan en la memoria de la computadora como secuencias de bits que contienen las direcciones—el lugar en la memoria—de sus contenidos. Un array con otro array dentro de él consiste en (al menos) una región de memoria para el array interno y otra para el array externo, que contiene (entre otras cosas) un número que representa la dirección del array interno.

+

Dado que las propiedades capturan su valor en lugar de contenerlo, los objetos y arrays se almacenan en la memoria de la computadora como secuencias de bits que contienen las direcciones —el lugar en la memoria— de sus contenidos. Un array con otro array dentro de él consiste en (al menos) una región de memoria para el array interno y otra para el array externo, que contiene (entre otras cosas) un número que representa la dirección del array interno.

-

Si deseas guardar datos en un archivo para más tarde o enviarlos a otra computadora a través de la red, debes convertir de alguna manera estas marañas de direcciones de memoria en una descripción que se pueda almacenar o enviar. Podrías enviar toda la memoria de tu computadora junto con la dirección del valor que te interesa, supongo, pero eso no parece ser el mejor enfoque.

+

Si deseas guardar datos en un archivo para más tarde o enviarlos a otra computadora a través de la red, debes convertir de alguna manera estas marañas de direcciones de memoria en una descripción que se pueda almacenar o enviar. Podrías enviar toda la memoria de tu computadora junto con la dirección del valor que te interesa, supongo, pero esa no parece ser la mejor estrategia.

-

Lo que podemos hacer es serializar los datos. Eso significa que se convierten en una descripción plana. Un formato de serialización popular se llama JSON (pronunciado “Jason”), que significa JavaScript Object Notacion. Se utiliza ampliamente como formato de almacenamiento y comunicación de datos en la Web, incluso en lenguajes que no son JavaScript.

+

Lo que podemos hacer es serializar los datos. Es decir, convertirlos en una descripción plana. Un formato de serialización popular se llama JSON (pronunciado como el nombre “Jason”), que viene de JavaScript Object Notacion. Se utiliza ampliamente como formato de almacenamiento y comunicación de datos en la Web, incluso en lenguajes que no son JavaScript.

-

JSON se parece al formato de escritura de arrays y objetos de JavaScript, con algunas restricciones. Todos los nombres de propiedades deben estar rodeados de comillas dobles y solo se permiten expresiones de datos simples—no llamadas a funciones, enlaces, o cualquier cosa que implique cálculos reales. Los comentarios no están permitidos en JSON.

+

JSON se parece al formato de escritura de arrays y objetos de JavaScript, con algunas restricciones. Todos los nombres de propiedades deben estar rodeados de comillas dobles y solo se permiten expresiones de datos simples —no llamadas a funciones, variables (asociaciones, en general), o cualquier cosa que implique cálculos reales. Los comentarios no están permitidos en JSON.

-

Una entrada de diario podría verse así cuando se representa como datos JSON:

+

Una entrada de diario podría verse así cuando se representa como datos en formato JSON:

-
{
-  "squirrel": false,
-  "events": ["work", "touched tree", "pizza", "running"]
+
{
+  "ardilla": false,
+  "eventos": ["trabajo", "tocar árbol", "pizza", "correr"]
 }

JavaScript nos proporciona las funciones JSON.stringify y JSON.parse para convertir datos a este formato y desde este formato. La primera toma un valor de JavaScript y devuelve una cadena codificada en JSON. La segunda toma dicha cadena y la convierte en el valor que codifica:

-
let string = JSON.stringify({squirrel: false,
-                             events: ["weekend"]});
-console.log(string);
-// → {"squirrel":false,"events":["weekend"]}
-console.log(JSON.parse(string).events);
-// → ["weekend"]
+
let cadena = JSON.stringify({ardilla: false,
+                             eventos: ["finde"]});
+console.log(cadena);
+// → {"ardilla":false,"eventos":["finde"]}
+console.log(JSON.parse(cadena).eventos);
+// → ["finde"]

Resumen

-

Los objetos y arrays proporcionan formas de agrupar varios valores en un único valor. Esto nos permite poner un montón de cosas relacionadas en una bolsa y correr con la bolsa en lugar de envolver nuestros brazos alrededor de cada una de las cosas individuales e intentar sostenerlas por separado.

+

Los objetos y arrays proporcionan formas de agrupar varios valores en un único valor. Esto nos permite poner un montón de cosas relacionadas en una bolsa y correr con la bolsa en lugar de envolver con nuestros brazos cada una de las cosas individuales e intentar sostenerlas todas por separado.

-

La mayoría de los valores en JavaScript tienen propiedades, con las excepciones de null y undefined. Las propiedades se acceden usando valor.prop o valor["prop"]. Los objetos tienden a usar nombres para sus propiedades y almacenan más o menos un conjunto fijo de ellas. Los arrays, por otro lado, suelen contener cantidades variables de valores conceptualmente idénticos y usan números (comenzando desde 0) como los nombres de sus propiedades.

+

La mayoría de los valores en JavaScript tienen propiedades, con las excepciones de null y undefined. Las propiedades se acceden usando valor.prop o valor["prop"]. Los objetos tienden a usar nombres para sus propiedades y almacenan más o menos un conjunto fijo de ellas. Los arrays, por otro lado, suelen contener cantidades variables de valores conceptualmente idénticos y usan números (comenzando desde 0) como nombres para sus propiedades.

-

hay algunas propiedades nombradas en arrays, como length y varios métodos. Los métodos son funciones que viven en propiedades y (usualmente) actúan sobre el valor del cual son una propiedad.

+

hay algunas propiedades con nombre en arrays, como length y varios métodos más. Los métodos son funciones que viven en propiedades y (usualmente) actúan sobre el valor del cual son una propiedad.

Puedes iterar sobre arrays usando un tipo especial de bucle for: for (let elemento of array).

@@ -662,21 +674,21 @@

La introducción de este libro insinuó lo siguiente como una forma agradable de calcular la suma de un rango de números:

-
console.log(sum(range(1, 10)));
+
console.log(suma(rango(1, 10)));
-

Escribe una función range que tome dos argumentos, inicio y fin, y devuelva un array que contenga todos los números desde inicio hasta fin, incluyendo fin.

+

Escribe una función rango que tome dos argumentos, inicio y fin, y devuelva un array que contenga todos los números desde inicio hasta fin, incluyendo fin.

-

Luego, escribe una función sum que tome un array de números y devuelva la suma de estos números. Ejecuta el programa de ejemplo y verifica si realmente devuelve 55.

+

Luego, escribe una función suma que tome un array de números y devuelva la suma de estos números. Ejecuta el programa de ejemplo y verifica si realmente devuelve 55.

-

Como asignación adicional, modifica tu función range para que tome un tercer argumento opcional que indique el valor de “paso” utilizado al construir el array. Si no se proporciona un paso, los elementos deberían aumentar en incrementos de uno, correspondiendo al comportamiento anterior. La llamada a la función range(1, 10, 2) debería devolver [1, 3, 5, 7, 9]. Asegúrate de que esto también funcione con valores de paso negativos, de modo que range(5, 2, -1) produzca [5, 4, 3, 2].

+

Como bonus, modifica tu función rango para que tome un tercer argumento opcional que indique el valor de “paso” utilizado al construir el array. Si no se proporciona un paso, los elementos deberían aumentar en incrementos de uno, correspondiendo al comportamiento anterior. La llamada a la función range(1, 10, 2) debería devolver [1, 3, 5, 7, 9]. Asegúrate de que esto también funcione con valores de paso negativos, de modo que range(5, 2, -1) produzca [5, 4, 3, 2].

-
// Tu código aquí.
+
// Tu código aquí.
 
-console.log(range(1, 10));
+console.log(rango(1, 10));
 // → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
-console.log(range(5, 2, -1));
+console.log(rango(5, 2, -1));
 // → [5, 4, 3, 2]
-console.log(sum(range(1, 10)));
+console.log(suma(rango(1, 10)));
 // → 55
Mostrar pistas...
@@ -685,19 +697,19 @@

Dado que el límite final es inclusivo, necesitarás usar el operador <= en lugar de < para verificar el final de tu bucle.

-

El parámetro de paso puede ser un parámetro opcional que por defecto (usando el operador =) es 1.

+

El parámetro de paso puede ser un parámetro opcional que por defecto (usando el operador =) sea 1.

-

Hacer que range comprenda valores negativos de paso probablemente sea mejor haciendo escribiendo dos bucles separados: uno para contar hacia arriba y otro para contar hacia abajo, porque la comparación que verifica si el bucle ha terminado necesita ser >= en lugar de <= al contar hacia abajo.

+

Hacer que rango comprenda valores negativos de paso probablemente sea mejor haciendo escribiendo dos bucles separados: uno para contar hacia arriba y otro para contar hacia abajo, porque la comparación que verifica si el bucle ha terminado necesita ser >= en lugar de <= al contar hacia abajo.

-

También puede valer la pena usar un paso predeterminado diferente, es decir, -1, cuando el final del rango es menor que el principio. De esa manera, range(5, 2) devuelve algo significativo, en lugar de quedarse atascado en un bucle infinito. Es posible hacer referencia a parámetros anteriores en el valor predeterminado de un parámetro.

+

También puede valer la pena usar un paso predeterminado diferente, es decir, -1, cuando el final del rango es menor que el principio. De esa manera, rango(5, 2) devuelve algo con sentido, en lugar de quedarse atascado en un bucle infinito. Es posible hacer referencia a parámetros anteriores en el valor predeterminado de un parámetro.

Reversión de un array

-

Los arrays tienen un método reverse que cambia el array invirtiendo el orden en el que aparecen sus elementos. Para este ejercicio, escribe dos funciones, reverseArray y reverseArrayInPlace. La primera, reverseArray, debería tomar un array como argumento y producir un nuevo array que tenga los mismos elementos en orden inverso. La segunda, reverseArrayInPlace, debería hacer lo que hace el método reverse: modificar el array dado como argumento invirtiendo sus elementos. Ninguna de las funciones puede utilizar el método reverse estándar.

+

Los arrays tienen un método reverse que modifica el array invirtiendo el orden en el que aparecen sus elementos. Para este ejercicio, escribe dos funciones, reverseArray y reverseArrayInPlace. La primera, reverseArray, debería tomar un array como argumento y producir un nuevo array que tenga los mismos elementos en orden inverso. La segunda, reverseArrayInPlace, debería hacer lo que hace el método reverse: modificar el array dado como argumento invirtiendo sus elementos. Ninguna de las funciones puede utilizar el método reverse estándar.

-

Recordando las notas sobre efectos secundarios y funciones puras en el capítulo anterior, ¿qué variante esperas que sea útil en más situaciones? ¿Cuál se ejecuta más rápido?

+

Recordando las notas sobre efectos secundarios y funciones puras del capítulo anterior, ¿qué variante esperas que sea útil en más situaciones? ¿Cuál se ejecuta más rápido?

// Tu código aquí.
 
@@ -713,11 +725,11 @@ 

Mostrar pistas...
-

Hay dos formas obvias de implementar reverseArray. La primera es simplemente recorrer el array de entrada de principio a fin y usar el método unshift en el nuevo array para insertar cada elemento en su inicio. La segunda es recorrer el array de entrada hacia atrás y utilizar el método push. Iterar sobre un array hacia atrás requiere una especificación de bucle (algo incómoda), como (let i = array.length - 1; i >= 0; i--).

+

Hay dos formas obvias de implementar reverseArray. La primera es simplemente recorrer el array de entrada de principio a fin y usar el método unshift en el nuevo array para insertar cada elemento en su inicio. La segunda es recorrer el array de entrada hacia atrás y utilizar el método push. Iterar sobre un array hacia atrás requiere una especificación (un poco rara) de bucle for, como (let i = array.length - 1; i >= 0; i--).

-

Invertir el array en su lugar es más difícil. Debes tener cuidado de no sobrescribir elementos que necesitarás más adelante. Utilizar reverseArray o copiar todo el array de otra manera (usar array.slice() es una buena forma de copiar un array) funciona pero es hacer trampa.

+

Invertir el array en su lugar es más difícil. Debes tener cuidado de no sobrescribir elementos que necesitarás más adelante. Utilizar reverseArray o, si no, copiar todo el array (usar array.slice() es una buena forma de copiar un array) funciona pero es hacer trampa.

-

El truco consiste en intercambiar el primer y último elementos, luego el segundo y el penúltimo, y así sucesivamente. Puedes hacer esto recorriendo la mitad de la longitud del array (utiliza Math.floor para redondear hacia abajo, no necesitas tocar el elemento central en un array con un número impar de elementos) e intercambiando el elemento en la posición i con el que está en la posición array.length - 1 - i. Puedes utilizar una asignación local para retener brevemente uno de los elementos, sobrescribirlo con su imagen reflejada, y luego colocar el valor de la asignación local en el lugar donde solía estar la imagen reflejada.

+

El truco consiste en intercambiar el primer y último elementos, luego el segundo y el penúltimo, y así sucesivamente. Puedes hacer esto recorriendo la mitad de la longitud del array (utiliza Math.floor para redondear hacia abajo, no necesitas tocar el elemento central en un array con un número impar de elementos) e intercambiando el elemento en la posición i con el que está en la posición array.length - 1 - i. Puedes utilizar una asignación local para retener brevemente uno de los elementos, sobrescribirlo con su imagen reflejada, y luego colocar el valor de la asignación local en el lugar donde antes estaba la imagen reflejada.

@@ -757,13 +769,13 @@

Mostrar pistas...
-

Construir una lista es más fácil cuando se hace de atrás hacia adelante. Por lo tanto, arrayToList podría iterar sobre el array en reversa (ver ejercicio anterior) y, para cada elemento, agregar un objeto a la lista. Puedes usar un enlace local para mantener la parte de la lista que se ha construido hasta el momento y usar una asignación como lista = {value: X, rest: lista} para añadir un elemento.

+

Construir una lista es más fácil cuando se hace de atrás hacia adelante. Por lo tanto, arrayToList podría iterar sobre el array de atrás para alante (ver el ejercicio anterior) y, para cada elemento, agregar un objeto a la lista. Puedes usar un enlace local para mantener la parte de la lista que se ha construido hasta el momento y usar una asignación como lista = {value: X, rest: lista} para añadir un elemento.

Para recorrer una lista (en listToArray y nth), se puede utilizar una especificación de bucle for de esta forma:

for (let nodo = list; nodo; nodo = nodo.rest) {}
-

¿Puedes ver cómo funciona esto? En cada iteración del bucle, nodo apunta a la sublista actual, y el cuerpo puede leer su propiedad value para obtener el elemento actual. Al final de una iteración, nodo pasa a la siguiente sublista. Cuando eso es nulo, hemos llegado al final de la lista y el bucle ha terminado.

+

¿Puedes ver cómo funciona esto? En cada iteración del bucle, nodo apunta a la sublista actual, y el cuerpo puede leer su propiedad value para obtener el elemento actual. Al final de una iteración, nodo pasa a la siguiente sublista. Cuando esta asignación de nulo, hemos llegado al final de la lista y el bucle ha terminado.

La versión recursiva de nth mirará de manera similar una parte cada vez más pequeña de la “cola” de la lista y al mismo tiempo contará hacia abajo el índice hasta llegar a cero, momento en el que puede devolver la propiedad value del nodo que está observando. Para obtener el elemento cero de una lista, simplemente tomas la propiedad value de su nodo principal. Para obtener el elemento N + 1, tomas el elemento N-ésimo de la lista que se encuentra en la propiedad rest de esta lista.

@@ -775,11 +787,11 @@

Escribe una función deepEqual que tome dos valores y devuelva true solo si son el mismo valor o son objetos con las mismas propiedades, donde los valores de las propiedades son iguales cuando se comparan con una llamada recursiva a deepEqual.

-

Para saber si los valores deben compararse directamente (usando el operador === para eso) o si sus propiedades deben compararse, puedes usar el operador typeof. Si produce "object" para ambos valores, deberías hacer una comparación profunda. Pero debes tener en cuenta una excepción tonta: debido a un accidente histórico, typeof null también produce "object".

+

Para saber si los valores deben compararse directamente (usando el operador === para eso) o si sus propiedades deben compararse, puedes usar el operador typeof. Si produce "object" para ambos valores, deberías hacer una comparación profunda. Pero debes tener en cuenta una excepción absurda: debido a un accidente histórico, typeof null también produce "object".

La función Object.keys será útil cuando necesites recorrer las propiedades de los objetos para compararlas.

-
// Your code here.
+
// Tu código aquí.
 
 let obj = {here: {is: "an"}, object: 2};
 console.log(deepEqual(obj, obj));
@@ -791,11 +803,11 @@ 

Mostrar pistas...
-

La prueba para determinar si estás tratando con un objeto real se verá algo así: typeof x == "object" && x != null. Ten cuidado de comparar propiedades solo cuando ambos argumentos sean objetos. En todos los demás casos, simplemente puedes devolver inmediatamente el resultado de aplicar ===.

+

Tu comprobación para determinar si estás tratando con un objeto real tendrá una pinta como esta: typeof x == "object" && x != null. Ten cuidado de comparar propiedades solo cuando ambos argumentos sean objetos. En todos los demás casos, simplemente puedes devolver inmediatamente el resultado de aplicar ===.

Utiliza Object.keys para recorrer las propiedades. Necesitas comprobar si ambos objetos tienen el mismo conjunto de nombres de propiedades y si esas propiedades tienen valores idénticos. Una forma de hacerlo es asegurarse de que ambos objetos tengan el mismo número de propiedades (las longitudes de las listas de propiedades son iguales). Y luego, al recorrer las propiedades de uno de los objetos para compararlas, asegúrate siempre primero de que el otro realmente tenga una propiedad con ese nombre. Si tienen el mismo número de propiedades y todas las propiedades en uno también existen en el otro, tienen el mismo conjunto de nombres de propiedades.

-

Devolver el valor correcto de la función se hace mejor devolviendo inmediatamente false cuando se encuentra una diferencia y devolviendo true al final de la función.

+

Lo mejor para devolver el valor correcto con la función es devolver inmediatamente false cuando se encuentra una diferencia y devolviendo true al final de la función.

From 0696b65edbbf45cd959323ce782901891f9f6f9f Mon Sep 17 00:00:00 2001 From: ckdvk Date: Wed, 12 Feb 2025 17:18:44 +0800 Subject: [PATCH 14/36] =?UTF-8?q?revisiones=20de=20las=20traducciones=20de?= =?UTF-8?q?=20ejercicios=20hasta=20el=20cap=C3=ADtulo=204.=20A=C3=B1adidas?= =?UTF-8?q?=20algunas=20notas.=20Cambiados=20nombres=20de=20algunos=20ejer?= =?UTF-8?q?cicios=20(de=20ah=C3=AD=20la=20edici=C3=B3n=20de=20chapter=5Fin?= =?UTF-8?q?fo)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 00_intro.md | 2 +- 03_functions.md | 20 +++++++++------- 04_data.md | 50 ++++++++++++++++++++------------------- html/00_intro.html | 2 +- html/03_functions.html | 25 +++++++++++--------- html/04_data.html | 53 ++++++++++++++++++++++-------------------- src/chapter_info.mjs | 4 ++-- 7 files changed, 83 insertions(+), 73 deletions(-) diff --git a/00_intro.md b/00_intro.md index b9e0685c..b50685b0 100644 --- a/00_intro.md +++ b/00_intro.md @@ -150,7 +150,7 @@ Al final del programa, después de que la construcción `while` haya terminado, {{index "función de suma", "función de rango", "abstracción", "función"}} -Finalmente, así es como podría verse el programa si tuviéramos a nuestra disposición las útiles operaciones `rango` y `suma`, que crean una colección de números dentro de un rango y calculan la suma de una colección de números, respectivamente: +Finalmente, así es como podría verse el programa si tuviéramos a nuestra disposición las útiles operaciones `rango` y `suma`, que crean una colección de números enteros dentro de un intervalo (o rango) y calculan la suma de una colección de números, respectivamente: ```{startCode: true} console.log(suma(rango(1, 10))); diff --git a/03_functions.md b/03_functions.md index 30eb5098..e2aaa544 100644 --- a/03_functions.md +++ b/03_functions.md @@ -700,7 +700,7 @@ Hemos visto que podemos usar `%` (el operador de resto) para verificar si un nú - Para cualquier otro número _N_, su paridad es la misma que la de _N_ - 2. -Define una función recursiva `esPar` que corresponda a esta descripción. La función debe aceptar un solo parámetro (un número entero positivo) y devolver un booleano. +Define una función recursiva `isEven` que corresponda a esta descripción. La función debe aceptar un solo parámetro (un número entero positivo) y devolver un booleano. {{index "stack overflow"}} @@ -711,16 +711,18 @@ Pruébalo con 50 y 75. Observa cómo se comporta con -1. ¿Por qué? ¿Puedes pe ```{test: no} // Tu código aquí. -console.log(esPar(50)); +console.log(isEven(50)); // → true -console.log(esPar(75)); +console.log(isEven(75)); // → false -console.log(esPar(-1)); +console.log(isEven(-1)); // → ?? ``` if}} +{{note "**N. del T.:** En esta traducción se mantendrán en inglés los nombres de funciones, asociaciones y otros que aparezcan como parte del enunciado de un ejercicio. De este modo, se mantiene el buen funcionamiento de los recursos relacionados con la resolución de los mismos."}} + {{hint {{index "isEven (exercise)", ["if keyword", chaining], recursion}} @@ -739,18 +741,18 @@ hint}} Puedes obtener el N-ésimo caracter, o letra, de una cadena escribiendo `[N]` después de la cadena (por ejemplo, `cadena[2]`). El valor resultante será una cadena que contiene solo un carácter (por ejemplo, `"b"`). El primer carácter tiene la posición 0, lo que hace que el último se encuentre en la posición `cadena.length - 1`. En otras palabras, una cadena de dos caracteres tiene longitud 2, y sus caracteres tienen posiciones 0 y 1. -Escribe una función `contarBs` que tome una cadena como único argumento y devuelva un número que indique cuántos caracteres B en mayúscula hay en la cadena. +Escribe una función `countBs` (contarBs) que tome una cadena como único argumento y devuelva un número que indique cuántos caracteres B en mayúscula hay en la cadena. -A continuación, escribe una función llamada `contarCaracter` que se comporte como `contarBs`, excepto que toma un segundo argumento que indica el carácter que se va a contar (en lugar de contar solo caracteres B en mayúscula). Reescribe `contarBs` para hacer uso de esta nueva función. +A continuación, escribe una función llamada `countChar` (contarCaracter) que se comporte como `countBs`, excepto que toma un segundo argumento que indica el carácter que se va a contar (en lugar de contar solo caracteres B en mayúscula). Reescribe `countBs` para hacer uso de esta nueva función. {{if interactive ```{test: no} // Tu código aquí. -console.log(contarBs("BOB")); +console.log(countBs("BOB")); // → 2 -console.log(contarCaracter("kakkerlak", "k")); +console.log(countChar("kakkerlak", "k")); // → 4 ``` @@ -760,7 +762,7 @@ if}} {{index "bean counting (exercise)", ["length property", "for string"], "counter variable"}} -Tu función necesita un ((bucle)) que mire cada carácter en la cadena. Puede recorrer un índice desde cero hasta uno menos que su longitud (`< string.length`). Si el caracter en la posición actual es el mismo que el que la función está buscando, agrega 1 a una variable contadora. Una vez que el bucle ha terminado, el contador puede ser devuelto. +Tu función necesita un ((bucle)) que mire cada carácter en la cadena. Puede recorrer un índice desde cero hasta uno menos que su longitud (`< cadena.length`). Si el caracter en la posición actual es el mismo que el que la función está buscando, agrega 1 a una variable contadora. Una vez que el bucle ha terminado, el contador puede ser devuelto. {{index "local binding"}} diff --git a/04_data.md b/04_data.md index f2f2d8e0..8e26eb28 100644 --- a/04_data.md +++ b/04_data.md @@ -960,11 +960,11 @@ Puedes iterar sobre arrays usando un tipo especial de bucle `for`: `for (let ele ## Ejercicios -### La suma de un rango +### La suma de un intervalo {{index "summing (exercise)"}} -La [introducción](intro) de este libro insinuó lo siguiente como una forma agradable de calcular la suma de un rango de números: +La [introducción](intro) de este libro insinuó lo siguiente como una forma cómoda de calcular la suma de un intervalo (o rango) de números enteros: ```{test: no} console.log(suma(rango(1, 10))); @@ -972,24 +972,26 @@ console.log(suma(rango(1, 10))); {{index "range function", "sum function"}} -Escribe una función `rango` que tome dos argumentos, `inicio` y `fin`, y devuelva un array que contenga todos los números desde `inicio` hasta `fin`, incluyendo `fin`. +Escribe una función `range` que tome dos argumentos, `inicio` y `fin`, y devuelva un array que contenga todos los números desde `inicio` hasta `fin`, incluyendo `fin`. -Luego, escribe una función `suma` que tome un array de números y devuelva la suma de estos números. Ejecuta el programa de ejemplo y verifica si realmente devuelve 55. +{{note "**N. del T.:** Recordamos aquí la decisión de mantener en el idioma original los nombres de elementos que se vayan a utilizar en la resolución de ejercicios. Por tanto, aquí, `rango` será `range` y `suma` será `sum`."}} + +Luego, escribe una función `sum` que tome un array de números y devuelva la suma de estos números. Ejecuta el programa de ejemplo y verifica si realmente devuelve 55. {{index "optional argument"}} -Como bonus, modifica tu función `rango` para que tome un tercer argumento opcional que indique el valor de "paso" utilizado al construir el array. Si no se proporciona un paso, los elementos deberían aumentar en incrementos de uno, correspondiendo al comportamiento anterior. La llamada a la función `range(1, 10, 2)` debería devolver `[1, 3, 5, 7, 9]`. Asegúrate de que esto también funcione con valores de paso negativos, de modo que `range(5, 2, -1)` produzca `[5, 4, 3, 2]`. +Como bonus, modifica tu función `range` para que tome un tercer argumento opcional que indique el valor de "paso" utilizado al construir el array. Si no se proporciona un paso, los elementos deberían aumentar en incrementos de uno, correspondiendo al comportamiento anterior. La llamada a la función `range(1, 10, 2)` debería devolver `[1, 3, 5, 7, 9]`. Asegúrate de que esto también funcione con valores de paso negativos, de modo que `range(5, 2, -1)` produzca `[5, 4, 3, 2]`. {{if interactive ```{test: no} // Tu código aquí. -console.log(rango(1, 10)); +console.log(range(1, 10)); // → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -console.log(rango(5, 2, -1)); +console.log(range(5, 2, -1)); // → [5, 4, 3, 2] -console.log(suma(rango(1, 10))); +console.log(sum(range(1, 10))); // → 55 ``` @@ -1011,21 +1013,21 @@ El parámetro de paso puede ser un parámetro opcional que por defecto (usando e {{index "range function", "for loop"}} -Hacer que `rango` comprenda valores negativos de paso probablemente sea mejor haciendo escribiendo dos bucles separados: uno para contar hacia arriba y otro para contar hacia abajo, porque la comparación que verifica si el bucle ha terminado necesita ser `>=` en lugar de `<=` al contar hacia abajo. +Hacer que `range` comprenda valores negativos de paso probablemente sea mejor haciendo escribiendo dos bucles separados: uno para contar hacia arriba y otro para contar hacia abajo, porque la comparación que verifica si el bucle ha terminado necesita ser `>=` en lugar de `<=` al contar hacia abajo. -También puede valer la pena usar un paso predeterminado diferente, es decir, -1, cuando el final del rango es menor que el principio. De esa manera, `rango(5, 2)` devuelve algo con sentido, en lugar de quedarse atascado en un ((bucle infinito)). Es posible hacer referencia a parámetros anteriores en el valor predeterminado de un parámetro. +También puede valer la pena usar un paso predeterminado diferente, es decir, -1, cuando el final del rango es menor que el principio. De esa manera, `range(5, 2)` devuelve algo con sentido, en lugar de quedarse atascado en un ((bucle infinito)). Es posible hacer referencia a parámetros anteriores en el valor predeterminado de un parámetro. hint}} -### Reversión de un array +### Dando la vuelta a un array {{index "reversing (exercise)", "método reverse", [array, "métodos"]}} -Los arrays tienen un método `reverse` que modifica el array invirtiendo el orden en el que aparecen sus elementos. Para este ejercicio, escribe dos funciones, `reverseArray` y `reverseArrayInPlace`. La primera, `reverseArray`, debería tomar un array como argumento y producir un _nuevo_ array que tenga los mismos elementos en orden inverso. La segunda, `reverseArrayInPlace`, debería hacer lo que hace el método `reverse`: _modificar_ el array dado como argumento invirtiendo sus elementos. Ninguna de las funciones puede utilizar el método `reverse` estándar. +Los arrays tienen un método `reverse` que modifica el array invirtiendo el orden en el que aparecen sus elementos. Para este ejercicio, escribe dos funciones: `reverseArray` y `reverseArrayInPlace`. La primera, `reverseArray`, debería tomar un array como argumento y producir un _nuevo_ array que tenga los mismos elementos pero en orden inverso. La segunda, `reverseArrayInPlace`, debería hacer lo que hace el método `reverse`: _modificar_ el array dado como argumento invirtiendo sus elementos. Ninguna de las funciones puede utilizar el método `reverse` estándar. {{index eficiencia, "función pura", "efecto secundario"}} -Recordando las notas sobre efectos secundarios y funciones puras del [capítulo anterior](functions#pure), ¿qué variante esperas que sea útil en más situaciones? ¿Cuál se ejecuta más rápido? +Pensando en la parte sobre efectos secundarios y funciones puras del [capítulo anterior](functions#pure), ¿qué variante esperas que sea útil en más situaciones? ¿Cuál se ejecuta más rápido? {{if interactive @@ -1053,9 +1055,9 @@ Hay dos formas obvias de implementar `reverseArray`. La primera es simplemente r {{index "método slice"}} -Invertir el array en su lugar es más difícil. Debes tener cuidado de no sobrescribir elementos que necesitarás más adelante. Utilizar `reverseArray` o, si no, copiar todo el array (usar `array.slice()` es una buena forma de copiar un array) funciona pero es hacer trampa. +Invertir el array en sí (modificando el objeto) es más difícil. Debes tener cuidado de no sobrescribir elementos que necesitarás más adelante. Utilizar `reverseArray` o copiar todo el array (usar `array.slice()` es una buena forma de copiar un array) funciona pero es hacer trampa. -El truco consiste en _intercambiar_ el primer y último elementos, luego el segundo y el penúltimo, y así sucesivamente. Puedes hacer esto recorriendo la mitad de la longitud del array (utiliza `Math.floor` para redondear hacia abajo, no necesitas tocar el elemento central en un array con un número impar de elementos) e intercambiando el elemento en la posición `i` con el que está en la posición `array.length - 1 - i`. Puedes utilizar una asignación local para retener brevemente uno de los elementos, sobrescribirlo con su imagen reflejada, y luego colocar el valor de la asignación local en el lugar donde antes estaba la imagen reflejada. +El truco consiste en _intercambiar_ el primer elemento con el último, luego el segundo con el penúltimo, y así sucesivamente. Puedes hacer esto recorriendo la mitad de la longitud del array (utiliza `Math.floor` para redondear hacia abajo —no necesitas tocar el elemento central en un array con un número impar de elementos—) e intercambiando el elemento en la posición `i` con el que está en la posición `array.length - 1 - i`. Puedes utilizar una asignación local para retener brevemente uno de los elementos, sobrescribirlo con el elemento que toca, y luego colocar el valor de la asignación local en el lugar donde antes estaba el otro elemento. hint}} @@ -1086,9 +1088,9 @@ Los objetos resultantes forman una cadena, como se muestra en el siguiente diagr {{index "structure sharing", [memory, structure sharing]}} -Una ventaja de las listas es que pueden compartir partes de su estructura. Por ejemplo, si creo dos nuevos valores `{value: 0, rest: list}` y `{value: -1, rest: list}` (siendo `list` la referencia definida anteriormente), son listas independientes, pero comparten la estructura que conforma sus últimos tres elementos. La lista original también sigue siendo válida como una lista de tres elementos. +Una ventaja de las listas es que pueden compartir partes de su estructura. Por ejemplo, si creo dos nuevos valores `{value: 0, rest: list}` y `{value: -1, rest: list}` (siendo `list` la referencia definida anteriormente), son listas independientes, pero comparten la estructura que conforma sus últimos tres elementos. La lista original también sigue siendo válida como lista (de tres elementos). -Escribe una función `arrayToList` que construya una estructura de lista como la mostrada cuando se le da `[1, 2, 3]` como argumento. También escribe una función `listToArray` que produzca un array a partir de una lista. Agrega las funciones auxiliares `prepend`, que toma un elemento y una lista y crea una nueva lista que añade el elemento al principio de la lista de entrada, y `nth`, que toma una lista y un número y devuelve el elemento en la posición dada en la lista (siendo cero el primer elemento) o `undefined` cuando no hay tal elemento. +Escribe una función `arrayToList` que construya una estructura de lista como la mostrada cuando se le da `[1, 2, 3]` como argumento. También escribe una función `listToArray` que produzca un array a partir de una lista. Agrega las funciones auxiliares `prepend`, que toma un elemento y una lista y crea una nueva lista que añade el elemento al principio de la lista de entrada, y `nth`, que toma una lista y un número y devuelve el elemento de la lista en la posición dada (siendo cero el primer elemento) o `undefined` cuando no hay tal elemento. {{index recursion}} @@ -1115,7 +1117,7 @@ if}} {{index "list (exercise)", "linked list"}} -Construir una lista es más fácil cuando se hace de atrás hacia adelante. Por lo tanto, `arrayToList` podría iterar sobre el array de atrás para alante (ver el ejercicio anterior) y, para cada elemento, agregar un objeto a la lista. Puedes usar un enlace local para mantener la parte de la lista que se ha construido hasta el momento y usar una asignación como `lista = {value: X, rest: lista}` para añadir un elemento. +Construir una lista es más fácil cuando se hace de atrás hacia adelante. Por lo tanto, `arrayToList` podría iterar sobre el array de atrás para alante (ver el ejercicio anterior) y, para cada elemento, agregar un objeto a la lista. Puedes usar una variable local para mantener la parte de la lista que se ha construido hasta el momento y hacer una reasignación del estilo `list = {value: X, rest: list}` para añadir un elemento. {{index "for loop"}} @@ -1125,11 +1127,11 @@ Para recorrer una lista (en `listToArray` y `nth`), se puede utilizar una especi for (let nodo = list; nodo; nodo = nodo.rest) {} ``` -¿Puedes ver cómo funciona esto? En cada iteración del bucle, `nodo` apunta a la sublista actual, y el cuerpo puede leer su propiedad `value` para obtener el elemento actual. Al final de una iteración, `nodo` pasa a la siguiente sublista. Cuando esta asignación de nulo, hemos llegado al final de la lista y el bucle ha terminado. +¿Entiendes cómo funciona? En cada iteración del bucle, `nodo` apunta a la sublista actual, y el cuerpo puede leer su propiedad `value` para obtener el elemento actual. Al final de una iteración, `nodo` pasa a la siguiente sublista. Cuando esta asignación dé nulo, hemos llegado al final de la lista y el bucle acaba. {{index recursion}} -La versión recursiva de `nth` mirará de manera similar una parte cada vez más pequeña de la "cola" de la lista y al mismo tiempo contará hacia abajo el índice hasta llegar a cero, momento en el que puede devolver la propiedad `value` del nodo que está observando. Para obtener el elemento cero de una lista, simplemente tomas la propiedad `value` de su nodo principal. Para obtener el elemento _N_ + 1, tomas el elemento *N*-ésimo de la lista que se encuentra en la propiedad `rest` de esta lista. +La versión recursiva de `nth` mirará de manera similar una parte cada vez más pequeña de la "cola" de la lista y al mismo tiempo irá disminuyendo el valor del índice hasta llegar a cero, momento en el que puede devolver la propiedad `value` del nodo que está observando. Para obtener el elemento cero de una lista, simplemente tomas la propiedad `value` de su nodo principal. Para obtener el elemento _N_ + 1, tomas *N*-ésimo elemento de la lista que se encuentra en la propiedad `rest` de esta lista. hint}} @@ -1137,11 +1139,11 @@ hint}} ### Comparación profunda -El operador `==` compara objetos por identidad, pero a veces preferirías comparar los valores de sus propiedades reales. +El operador `==` compara objetos por identidad, pero a veces preferirías comparar los valores de sus propiedades. -Escribe una función `deepEqual` que tome dos valores y devuelva true solo si son el mismo valor o son objetos con las mismas propiedades, donde los valores de las propiedades son iguales cuando se comparan con una llamada recursiva a `deepEqual`. +Escribe una función `deepEqual` que tome dos valores y devuelva true solo si son el mismo valor o son objetos con las mismas propiedades, donde los valores de las propiedades son iguales cuando lo son al comparar con una llamada recursiva a `deepEqual`. -Para saber si los valores deben compararse directamente (usando el operador `===` para eso) o si sus propiedades deben compararse, puedes usar el operador `typeof`. Si produce `"object"` para ambos valores, deberías hacer una comparación profunda. Pero debes tener en cuenta una excepción absurda: debido a un accidente histórico, `typeof null` también produce `"object"`. +Para saber si los valores deben compararse directamente (usando el operador `===` para eso) o si sus propiedades deben compararse, puedes usar el operador `typeof`. Si produce `"object"` para ambos valores, deberías hacer una comparación profunda. Pero debes tener en cuenta una excepción: debido a un accidente histórico, `typeof null` también produce `"object"`. La función `Object.keys` será útil cuando necesites recorrer las propiedades de los objetos para compararlas. @@ -1169,7 +1171,7 @@ Tu comprobación para determinar si estás tratando con un objeto real tendrá u {{index "Object.keys function"}} -Utiliza `Object.keys` para recorrer las propiedades. Necesitas comprobar si ambos objetos tienen el mismo conjunto de nombres de propiedades y si esas propiedades tienen valores idénticos. Una forma de hacerlo es asegurarse de que ambos objetos tengan el mismo número de propiedades (las longitudes de las listas de propiedades son iguales). Y luego, al recorrer las propiedades de uno de los objetos para compararlas, asegúrate siempre primero de que el otro realmente tenga una propiedad con ese nombre. Si tienen el mismo número de propiedades y todas las propiedades en uno también existen en el otro, tienen el mismo conjunto de nombres de propiedades. +Utiliza `Object.keys` para recorrer las propiedades. Necesitas comprobar si ambos objetos tienen el mismo conjunto de nombres de propiedades y si esas propiedades tienen valores idénticos. Una forma de hacerlo es asegurarse de que ambos objetos tengan el mismo número de propiedades (que las longitudes de las listas de propiedades sean iguales). Y luego, al recorrer las propiedades de uno de los objetos para compararlas, asegúrate siempre primero de que el otro realmente tenga una propiedad con ese nombre. Si tienen el mismo número de propiedades y todas las propiedades en uno también existen en el otro, entonces tienen el mismo conjunto de nombres de propiedades. {{index "return value"}} diff --git a/html/00_intro.html b/html/00_intro.html index bee5e720..563c7de1 100644 --- a/html/00_intro.html +++ b/html/00_intro.html @@ -146,7 +146,7 @@

Al final del programa, después de que la construcción while haya terminado, se utiliza la operación console.log para mostrar el resultado.

-

Finalmente, así es como podría verse el programa si tuviéramos a nuestra disposición las útiles operaciones rango y suma, que crean una colección de números dentro de un rango y calculan la suma de una colección de números, respectivamente:

+

Finalmente, así es como podría verse el programa si tuviéramos a nuestra disposición las útiles operaciones rango y suma, que crean una colección de números enteros dentro de un intervalo (o rango) y calculan la suma de una colección de números, respectivamente:

console.log(suma(rango(1, 10)));
 // → 55
diff --git a/html/03_functions.html b/html/03_functions.html index 455d5c3e..0bf161af 100644 --- a/html/03_functions.html +++ b/html/03_functions.html @@ -515,19 +515,22 @@

Para cualquier otro número N, su paridad es la misma que la de N - 2.

-

Define una función recursiva esPar que corresponda a esta descripción. La función debe aceptar un solo parámetro (un número entero positivo) y devolver un booleano.

+

Define una función recursiva isEven que corresponda a esta descripción. La función debe aceptar un solo parámetro (un número entero positivo) y devolver un booleano.

Pruébalo con 50 y 75. Observa cómo se comporta con -1. ¿Por qué? ¿Puedes pensar en una forma de solucionarlo?

-
// Tu código aquí.
+
// Tu código aquí.
 
-console.log(esPar(50));
+console.log(isEven(50));
 // → true
-console.log(esPar(75));
+console.log(isEven(75));
 // → false
-console.log(esPar(-1));
+console.log(isEven(-1));
 // → ??
+

N. del T.: En esta traducción se mantendrán en inglés los nombres de funciones, asociaciones y otros que aparezcan como parte del enunciado de un ejercicio. De este modo, se mantiene el buen funcionamiento de los recursos relacionados con la resolución de los mismos.

+
+
Mostrar pistas...

Es probable que tu función se parezca en cierta medida a la función interna encontrar en el ejemplo recursivo encontrarSolucion ejemplo de este capítulo, con una cadena if/else if/else que prueba cuál de los tres casos aplica. El else final, correspondiente al tercer caso, realiza la llamada recursiva. Cada una de las ramas debe contener una declaración return o de algún modo, asegurarse de que se devuelva un valor específico.

@@ -540,20 +543,20 @@

Puedes obtener el N-ésimo caracter, o letra, de una cadena escribiendo [N] después de la cadena (por ejemplo, cadena[2]). El valor resultante será una cadena que contiene solo un carácter (por ejemplo, "b"). El primer carácter tiene la posición 0, lo que hace que el último se encuentre en la posición cadena.length - 1. En otras palabras, una cadena de dos caracteres tiene longitud 2, y sus caracteres tienen posiciones 0 y 1.

-

Escribe una función contarBs que tome una cadena como único argumento y devuelva un número que indique cuántos caracteres B en mayúscula hay en la cadena.

+

Escribe una función countBs (contarBs) que tome una cadena como único argumento y devuelva un número que indique cuántos caracteres B en mayúscula hay en la cadena.

-

A continuación, escribe una función llamada contarCaracter que se comporte como contarBs, excepto que toma un segundo argumento que indica el carácter que se va a contar (en lugar de contar solo caracteres B en mayúscula). Reescribe contarBs para hacer uso de esta nueva función.

+

A continuación, escribe una función llamada countChar (contarCaracter) que se comporte como countBs, excepto que toma un segundo argumento que indica el carácter que se va a contar (en lugar de contar solo caracteres B en mayúscula). Reescribe countBs para hacer uso de esta nueva función.

-
// Tu código aquí.
+
// Tu código aquí.
 
-console.log(contarBs("BOB"));
+console.log(countBs("BOB"));
 // → 2
-console.log(contarCaracter("kakkerlak", "k"));
+console.log(countChar("kakkerlak", "k"));
 // → 4
Mostrar pistas...
-

Tu función necesita un bucle que mire cada carácter en la cadena. Puede recorrer un índice desde cero hasta uno menos que su longitud (< string.length). Si el caracter en la posición actual es el mismo que el que la función está buscando, agrega 1 a una variable contadora. Una vez que el bucle ha terminado, el contador puede ser devuelto.

+

Tu función necesita un bucle que mire cada carácter en la cadena. Puede recorrer un índice desde cero hasta uno menos que su longitud (< cadena.length). Si el caracter en la posición actual es el mismo que el que la función está buscando, agrega 1 a una variable contadora. Una vez que el bucle ha terminado, el contador puede ser devuelto.

Ten cuidado de que todas las asociaciones utilizadas en la función sean locales a la función, declarándolas correctamente con la palabra clave let o const.

diff --git a/html/04_data.html b/html/04_data.html index 98674f86..7e6c292d 100644 --- a/html/04_data.html +++ b/html/04_data.html @@ -670,25 +670,28 @@

Ejercicios

-

La suma de un rango

+

La suma de un intervalo

-

La introducción de este libro insinuó lo siguiente como una forma agradable de calcular la suma de un rango de números:

+

La introducción de este libro insinuó lo siguiente como una forma cómoda de calcular la suma de un intervalo (o rango) de números enteros:

console.log(suma(rango(1, 10)));
-

Escribe una función rango que tome dos argumentos, inicio y fin, y devuelva un array que contenga todos los números desde inicio hasta fin, incluyendo fin.

+

Escribe una función range que tome dos argumentos, inicio y fin, y devuelva un array que contenga todos los números desde inicio hasta fin, incluyendo fin.

-

Luego, escribe una función suma que tome un array de números y devuelva la suma de estos números. Ejecuta el programa de ejemplo y verifica si realmente devuelve 55.

+

N. del T.: Recordamos aquí la decisión de mantener en el idioma original los nombres de elementos que se vayan a utilizar en la resolución de ejercicios. Por tanto, aquí, rango será range y suma será sum.

+
+ +

Luego, escribe una función sum que tome un array de números y devuelva la suma de estos números. Ejecuta el programa de ejemplo y verifica si realmente devuelve 55.

-

Como bonus, modifica tu función rango para que tome un tercer argumento opcional que indique el valor de “paso” utilizado al construir el array. Si no se proporciona un paso, los elementos deberían aumentar en incrementos de uno, correspondiendo al comportamiento anterior. La llamada a la función range(1, 10, 2) debería devolver [1, 3, 5, 7, 9]. Asegúrate de que esto también funcione con valores de paso negativos, de modo que range(5, 2, -1) produzca [5, 4, 3, 2].

+

Como bonus, modifica tu función range para que tome un tercer argumento opcional que indique el valor de “paso” utilizado al construir el array. Si no se proporciona un paso, los elementos deberían aumentar en incrementos de uno, correspondiendo al comportamiento anterior. La llamada a la función range(1, 10, 2) debería devolver [1, 3, 5, 7, 9]. Asegúrate de que esto también funcione con valores de paso negativos, de modo que range(5, 2, -1) produzca [5, 4, 3, 2].

-
// Tu código aquí.
+
// Tu código aquí.
 
-console.log(rango(1, 10));
+console.log(range(1, 10));
 // → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
-console.log(rango(5, 2, -1));
+console.log(range(5, 2, -1));
 // → [5, 4, 3, 2]
-console.log(suma(rango(1, 10)));
+console.log(sum(range(1, 10)));
 // → 55
Mostrar pistas...
@@ -699,17 +702,17 @@

El parámetro de paso puede ser un parámetro opcional que por defecto (usando el operador =) sea 1.

-

Hacer que rango comprenda valores negativos de paso probablemente sea mejor haciendo escribiendo dos bucles separados: uno para contar hacia arriba y otro para contar hacia abajo, porque la comparación que verifica si el bucle ha terminado necesita ser >= en lugar de <= al contar hacia abajo.

+

Hacer que range comprenda valores negativos de paso probablemente sea mejor haciendo escribiendo dos bucles separados: uno para contar hacia arriba y otro para contar hacia abajo, porque la comparación que verifica si el bucle ha terminado necesita ser >= en lugar de <= al contar hacia abajo.

-

También puede valer la pena usar un paso predeterminado diferente, es decir, -1, cuando el final del rango es menor que el principio. De esa manera, rango(5, 2) devuelve algo con sentido, en lugar de quedarse atascado en un bucle infinito. Es posible hacer referencia a parámetros anteriores en el valor predeterminado de un parámetro.

+

También puede valer la pena usar un paso predeterminado diferente, es decir, -1, cuando el final del rango es menor que el principio. De esa manera, range(5, 2) devuelve algo con sentido, en lugar de quedarse atascado en un bucle infinito. Es posible hacer referencia a parámetros anteriores en el valor predeterminado de un parámetro.

-

Reversión de un array

+

Dando la vuelta a un array

-

Los arrays tienen un método reverse que modifica el array invirtiendo el orden en el que aparecen sus elementos. Para este ejercicio, escribe dos funciones, reverseArray y reverseArrayInPlace. La primera, reverseArray, debería tomar un array como argumento y producir un nuevo array que tenga los mismos elementos en orden inverso. La segunda, reverseArrayInPlace, debería hacer lo que hace el método reverse: modificar el array dado como argumento invirtiendo sus elementos. Ninguna de las funciones puede utilizar el método reverse estándar.

+

Los arrays tienen un método reverse que modifica el array invirtiendo el orden en el que aparecen sus elementos. Para este ejercicio, escribe dos funciones: reverseArray y reverseArrayInPlace. La primera, reverseArray, debería tomar un array como argumento y producir un nuevo array que tenga los mismos elementos pero en orden inverso. La segunda, reverseArrayInPlace, debería hacer lo que hace el método reverse: modificar el array dado como argumento invirtiendo sus elementos. Ninguna de las funciones puede utilizar el método reverse estándar.

-

Recordando las notas sobre efectos secundarios y funciones puras del capítulo anterior, ¿qué variante esperas que sea útil en más situaciones? ¿Cuál se ejecuta más rápido?

+

Pensando en la parte sobre efectos secundarios y funciones puras del capítulo anterior, ¿qué variante esperas que sea útil en más situaciones? ¿Cuál se ejecuta más rápido?

// Tu código aquí.
 
@@ -727,9 +730,9 @@ 

Hay dos formas obvias de implementar reverseArray. La primera es simplemente recorrer el array de entrada de principio a fin y usar el método unshift en el nuevo array para insertar cada elemento en su inicio. La segunda es recorrer el array de entrada hacia atrás y utilizar el método push. Iterar sobre un array hacia atrás requiere una especificación (un poco rara) de bucle for, como (let i = array.length - 1; i >= 0; i--).

-

Invertir el array en su lugar es más difícil. Debes tener cuidado de no sobrescribir elementos que necesitarás más adelante. Utilizar reverseArray o, si no, copiar todo el array (usar array.slice() es una buena forma de copiar un array) funciona pero es hacer trampa.

+

Invertir el array en sí (modificando el objeto) es más difícil. Debes tener cuidado de no sobrescribir elementos que necesitarás más adelante. Utilizar reverseArray o copiar todo el array (usar array.slice() es una buena forma de copiar un array) funciona pero es hacer trampa.

-

El truco consiste en intercambiar el primer y último elementos, luego el segundo y el penúltimo, y así sucesivamente. Puedes hacer esto recorriendo la mitad de la longitud del array (utiliza Math.floor para redondear hacia abajo, no necesitas tocar el elemento central en un array con un número impar de elementos) e intercambiando el elemento en la posición i con el que está en la posición array.length - 1 - i. Puedes utilizar una asignación local para retener brevemente uno de los elementos, sobrescribirlo con su imagen reflejada, y luego colocar el valor de la asignación local en el lugar donde antes estaba la imagen reflejada.

+

El truco consiste en intercambiar el primer elemento con el último, luego el segundo con el penúltimo, y así sucesivamente. Puedes hacer esto recorriendo la mitad de la longitud del array (utiliza Math.floor para redondear hacia abajo —no necesitas tocar el elemento central en un array con un número impar de elementos—) e intercambiando el elemento en la posición i con el que está en la posición array.length - 1 - i. Puedes utilizar una asignación local para retener brevemente uno de los elementos, sobrescribirlo con el elemento que toca, y luego colocar el valor de la asignación local en el lugar donde antes estaba el otro elemento.

@@ -750,9 +753,9 @@

Los objetos resultantes forman una cadena, como se muestra en el siguiente diagrama:

Un diagrama que muestra la estructura de memoria de una lista enlazada. Hay 3 celdas, cada una con un campo de valor que contiene un número y un campo 'rest' con una flecha que apunta al resto de la lista. La flecha de la primera celda apunta a la segunda celda, la flecha de la segunda celda apunta a la última celda y el campo 'rest' de la última celda contiene nulo.
-

Una ventaja de las listas es que pueden compartir partes de su estructura. Por ejemplo, si creo dos nuevos valores {value: 0, rest: list} y {value: -1, rest: list} (siendo list la referencia definida anteriormente), son listas independientes, pero comparten la estructura que conforma sus últimos tres elementos. La lista original también sigue siendo válida como una lista de tres elementos.

+

Una ventaja de las listas es que pueden compartir partes de su estructura. Por ejemplo, si creo dos nuevos valores {value: 0, rest: list} y {value: -1, rest: list} (siendo list la referencia definida anteriormente), son listas independientes, pero comparten la estructura que conforma sus últimos tres elementos. La lista original también sigue siendo válida como lista (de tres elementos).

-

Escribe una función arrayToList que construya una estructura de lista como la mostrada cuando se le da [1, 2, 3] como argumento. También escribe una función listToArray que produzca un array a partir de una lista. Agrega las funciones auxiliares prepend, que toma un elemento y una lista y crea una nueva lista que añade el elemento al principio de la lista de entrada, y nth, que toma una lista y un número y devuelve el elemento en la posición dada en la lista (siendo cero el primer elemento) o undefined cuando no hay tal elemento.

+

Escribe una función arrayToList que construya una estructura de lista como la mostrada cuando se le da [1, 2, 3] como argumento. También escribe una función listToArray que produzca un array a partir de una lista. Agrega las funciones auxiliares prepend, que toma un elemento y una lista y crea una nueva lista que añade el elemento al principio de la lista de entrada, y nth, que toma una lista y un número y devuelve el elemento de la lista en la posición dada (siendo cero el primer elemento) o undefined cuando no hay tal elemento.

Si aún no lo has hecho, escribe también una versión recursiva de nth.

@@ -769,25 +772,25 @@

Mostrar pistas...
-

Construir una lista es más fácil cuando se hace de atrás hacia adelante. Por lo tanto, arrayToList podría iterar sobre el array de atrás para alante (ver el ejercicio anterior) y, para cada elemento, agregar un objeto a la lista. Puedes usar un enlace local para mantener la parte de la lista que se ha construido hasta el momento y usar una asignación como lista = {value: X, rest: lista} para añadir un elemento.

+

Construir una lista es más fácil cuando se hace de atrás hacia adelante. Por lo tanto, arrayToList podría iterar sobre el array de atrás para alante (ver el ejercicio anterior) y, para cada elemento, agregar un objeto a la lista. Puedes usar una variable local para mantener la parte de la lista que se ha construido hasta el momento y hacer una reasignación del estilo list = {value: X, rest: list} para añadir un elemento.

Para recorrer una lista (en listToArray y nth), se puede utilizar una especificación de bucle for de esta forma:

for (let nodo = list; nodo; nodo = nodo.rest) {}
-

¿Puedes ver cómo funciona esto? En cada iteración del bucle, nodo apunta a la sublista actual, y el cuerpo puede leer su propiedad value para obtener el elemento actual. Al final de una iteración, nodo pasa a la siguiente sublista. Cuando esta asignación de nulo, hemos llegado al final de la lista y el bucle ha terminado.

+

¿Entiendes cómo funciona? En cada iteración del bucle, nodo apunta a la sublista actual, y el cuerpo puede leer su propiedad value para obtener el elemento actual. Al final de una iteración, nodo pasa a la siguiente sublista. Cuando esta asignación dé nulo, hemos llegado al final de la lista y el bucle acaba.

-

La versión recursiva de nth mirará de manera similar una parte cada vez más pequeña de la “cola” de la lista y al mismo tiempo contará hacia abajo el índice hasta llegar a cero, momento en el que puede devolver la propiedad value del nodo que está observando. Para obtener el elemento cero de una lista, simplemente tomas la propiedad value de su nodo principal. Para obtener el elemento N + 1, tomas el elemento N-ésimo de la lista que se encuentra en la propiedad rest de esta lista.

+

La versión recursiva de nth mirará de manera similar una parte cada vez más pequeña de la “cola” de la lista y al mismo tiempo irá disminuyendo el valor del índice hasta llegar a cero, momento en el que puede devolver la propiedad value del nodo que está observando. Para obtener el elemento cero de una lista, simplemente tomas la propiedad value de su nodo principal. Para obtener el elemento N + 1, tomas N-ésimo elemento de la lista que se encuentra en la propiedad rest de esta lista.

Comparación profunda

-

El operador == compara objetos por identidad, pero a veces preferirías comparar los valores de sus propiedades reales.

+

El operador == compara objetos por identidad, pero a veces preferirías comparar los valores de sus propiedades.

-

Escribe una función deepEqual que tome dos valores y devuelva true solo si son el mismo valor o son objetos con las mismas propiedades, donde los valores de las propiedades son iguales cuando se comparan con una llamada recursiva a deepEqual.

+

Escribe una función deepEqual que tome dos valores y devuelva true solo si son el mismo valor o son objetos con las mismas propiedades, donde los valores de las propiedades son iguales cuando lo son al comparar con una llamada recursiva a deepEqual.

-

Para saber si los valores deben compararse directamente (usando el operador === para eso) o si sus propiedades deben compararse, puedes usar el operador typeof. Si produce "object" para ambos valores, deberías hacer una comparación profunda. Pero debes tener en cuenta una excepción absurda: debido a un accidente histórico, typeof null también produce "object".

+

Para saber si los valores deben compararse directamente (usando el operador === para eso) o si sus propiedades deben compararse, puedes usar el operador typeof. Si produce "object" para ambos valores, deberías hacer una comparación profunda. Pero debes tener en cuenta una excepción: debido a un accidente histórico, typeof null también produce "object".

La función Object.keys será útil cuando necesites recorrer las propiedades de los objetos para compararlas.

@@ -805,7 +808,7 @@

Tu comprobación para determinar si estás tratando con un objeto real tendrá una pinta como esta: typeof x == "object" && x != null. Ten cuidado de comparar propiedades solo cuando ambos argumentos sean objetos. En todos los demás casos, simplemente puedes devolver inmediatamente el resultado de aplicar ===.

-

Utiliza Object.keys para recorrer las propiedades. Necesitas comprobar si ambos objetos tienen el mismo conjunto de nombres de propiedades y si esas propiedades tienen valores idénticos. Una forma de hacerlo es asegurarse de que ambos objetos tengan el mismo número de propiedades (las longitudes de las listas de propiedades son iguales). Y luego, al recorrer las propiedades de uno de los objetos para compararlas, asegúrate siempre primero de que el otro realmente tenga una propiedad con ese nombre. Si tienen el mismo número de propiedades y todas las propiedades en uno también existen en el otro, tienen el mismo conjunto de nombres de propiedades.

+

Utiliza Object.keys para recorrer las propiedades. Necesitas comprobar si ambos objetos tienen el mismo conjunto de nombres de propiedades y si esas propiedades tienen valores idénticos. Una forma de hacerlo es asegurarse de que ambos objetos tengan el mismo número de propiedades (que las longitudes de las listas de propiedades sean iguales). Y luego, al recorrer las propiedades de uno de los objetos para compararlas, asegúrate siempre primero de que el otro realmente tenga una propiedad con ese nombre. Si tienen el mismo número de propiedades y todas las propiedades en uno también existen en el otro, entonces tienen el mismo conjunto de nombres de propiedades.

Lo mejor para devolver el valor correcto con la función es devolver inmediatamente false cuando se encuentra una diferencia y devolviendo true al final de la función.

diff --git a/src/chapter_info.mjs b/src/chapter_info.mjs index e858cb31..4c254cd6 100644 --- a/src/chapter_info.mjs +++ b/src/chapter_info.mjs @@ -13,8 +13,8 @@ const TRANSLATIONS_MAP = { '03_1_mnimo.js': '03_1_minimum.js', '03_2_recursin.js': '03_2_recursion.js', '03_3_contando_minuciosamente.js': '03_3_bean_counting.js', - '04_1_la_suma_de_un_rango.js': '04_1_the_sum_of_a_range.js', - '04_2_reversin_de_un_array.js': '04_2_reversing_an_array.js', + '04_1_la_suma_de_un_intervalo.js': '04_1_the_sum_of_a_range.js', + '04_2_dando_la_vuelta_a_un_array.js': '04_2_reversing_an_array.js', '04_3_lista.js': '04_3_a_list.js', '04_4_comparacin_profunda.js': '04_4_deep_comparison.js', '05_1_aplanamiento.js': '05_1_flattening.js', From 7a416a125c22a0702c8345e2104b7048338b5157 Mon Sep 17 00:00:00 2001 From: ckdvk Date: Wed, 12 Feb 2025 21:41:46 +0800 Subject: [PATCH 15/36] Revisado cap. 5. tener cuidado a la hora de escribir cosas como _((abstraccion))es_. Ya veremos si hay que modificarlo (linea 37 en chap 5) --- 00_intro.md | 24 +-- 05_higher_order.md | 326 +++++++++++++++++++------------------ html/00_intro.html | 26 +-- html/05_higher_order.html | 333 ++++++++++++++++++++------------------ 4 files changed, 367 insertions(+), 342 deletions(-) diff --git a/00_intro.md b/00_intro.md index b50685b0..5c23443a 100644 --- a/00_intro.md +++ b/00_intro.md @@ -114,13 +114,13 @@ Aunque eso ya es más legible que la sopa de bits anterior, sigue siendo bastant ```{lang: "null"} Establecer “total” en 0. - Establecer “count” en 1. + Establecer “contador” en 1. [bucle] - Establecer “compare” en “count”. - Restar 11 de “compare”. - Si “compare” es cero, continuar en [fin]. - Sumar “count” a “total”. - Añadir 1 a “count”. + Establecer “comparación” en “contador”. + Restar 11 de “comparación”. + Si “comparación” es cero, continuar en [fin]. + Sumar “contador” a “total”. + Añadir 1 a “contador”. Continuar en [bucle]. [fin] Mostrar “total”. @@ -128,13 +128,13 @@ Aunque eso ya es más legible que la sopa de bits anterior, sigue siendo bastant {{index bucle, salto, "ejemplo de suma"}} -¿Puedes ver cómo funciona el programa en este punto? Las dos primeras líneas asignan los valores iniciales a dos ubicaciones de memoria: `total` se utilizará para construir el resultado de la suma, y `count` llevará la cuenta del número que estamos observando en ese momento. Las líneas que utilizan `compare` probablemente sean las más confusas. El programa quiere ver si `count` es igual a 11 para decidir si puede parar de ejecutarse. Debido a que nuestra máquina hipotética es bastante primitiva, solo puede comprobar si un número es cero y tomar una decisión en función de ese valor. Por lo tanto, utiliza la ubicación de memoria etiquetada como `compare` para calcular el valor de `count - 11` y tomar una decisión basada en el resultado. Las siguientes dos líneas suman el valor de `count` al resultado e incrementan `count` en 1 cada vez que el programa decide que `count` aún no vale 11. Aquí está el mismo programa en JavaScript: +¿Puedes ver cómo funciona el programa en este punto? Las dos primeras líneas asignan los valores iniciales a dos ubicaciones de memoria: `total` se utilizará para construir el resultado de la suma, y `contador` llevará la cuenta del número que estamos observando en ese momento. Las líneas que utilizan `comparación` probablemente sean las más confusas. El programa quiere ver si `contador` es igual a 11 para decidir si puede parar de ejecutarse. Debido a que nuestra máquina hipotética es bastante primitiva, solo puede comprobar si un número es cero y tomar una decisión en función de ese valor. Por lo tanto, utiliza la ubicación de memoria etiquetada como `comparación` para calcular el valor de `contador - 11` y tomar una decisión basada en el resultado. Las siguientes dos líneas suman el valor de `contador` al resultado e incrementan `contador` en 1 cada vez que el programa decide que `contador` aún no vale 11. Aquí está el mismo programa en JavaScript: ``` -let total = 0, count = 1; -while (count <= 10) { - total += count; - count += 1; +let total = 0, contador = 1; +while (contador <= 10) { + total += contador; + contador += 1; } console.log(total); // → 55 @@ -142,7 +142,7 @@ console.log(total); {{index "bucle while", bucle, [llaves, bloque]}} -Esta versión nos proporciona algunas mejoras más. Lo más importante es que ya no es necesario especificar la forma en que queremos que el programa salte hacia adelante y hacia atrás; la construcción `while` se encarga de eso. Continúa ejecutando el bloque (entre llaves) debajo de él siempre y cuando se cumpla la condición que se le ha dado. Esa condición es `count <= 10`, lo que significa "el recuento es menor o igual a 10". Ya no tenemos que crear un valor temporal y compararlo con cero, lo cual era simplemente un detalle carente de interés. Parte del poder de los lenguajes de programación es que pueden encargarse de los detalles que no nos interesan. +Esta versión nos proporciona algunas mejoras más. Lo más importante es que ya no es necesario especificar la forma en que queremos que el programa salte hacia adelante y hacia atrás; la construcción `while` se encarga de eso. Continúa ejecutando el bloque (entre llaves) debajo de él siempre y cuando se cumpla la condición que se le ha dado. Esa condición es `contador <= 10`, lo que significa "el recuento es menor o igual a 10". Ya no tenemos que crear un valor temporal y compararlo con cero, lo cual era simplemente un detalle carente de interés. Parte del poder de los lenguajes de programación es que pueden encargarse de los detalles que no nos interesan. {{index "console.log"}} diff --git a/05_higher_order.md b/05_higher_order.md index 12662882..c6fb693f 100644 --- a/05_higher_order.md +++ b/05_higher_order.md @@ -1,19 +1,19 @@ {{meta {load_files: ["code/scripts.js", "code/chapter/05_higher_order.js", "code/intro.js"], zip: "node/html"}}} # Funciones de Orden Superior -_"Hay dos formas de construir un diseño de software: Una forma es hacerlo tan simple que obviamente no haya deficiencias, y la otra forma es hacerlo tan complicado que no haya deficiencias obvias."_ +_"Hay dos maneras de construir un diseño de software: una forma es hacerlo tan simple que obviamente no haya defectos, y la otra forma es hacerlo tan complicado que no haya defectos obvios."_ — C.A.R. Hoare, _Discurso de Recepción del Premio Turing de la ACM de 1980_ -Un programa grande es un programa costoso, y no solo por el tiempo que lleva construirlo. El tamaño casi siempre implica complejidad, y la complejidad confunde a los programadores. Los programadores confundidos, a su vez, introducen errores (_((bugs))_) en los programas. Un programa grande proporciona mucho espacio para que estos errores se escondan, lo que los hace difíciles de encontrar. +Un programa grande es un programa costoso, y no solo por el tiempo que lleva construirlo. El tamaño casi siempre implica complejidad, y la complejidad confunde a los programadores. Los programadores confundidos, a su vez, introducen errores (_((bugs))_) en los programas. Un programa grande da mucho hueco para que estos errores se escondan, lo que los hace difíciles de encontrar. -Volviendo brevemente a los dos ejemplos finales de programas en la introducción. El primero es autocontenido y tiene seis líneas: +Vamos a volver por un momento a los dos ejemplos de programas del final de la introducción. El primero es autocontenido y tiene seis líneas: ``` -let total = 0, count = 1; -while (count <= 10) { - total += count; - count += 1; +let total = 0, contador = 1; +while (contador <= 10) { + total += contador; + contador += 1; } console.log(total); ``` @@ -26,35 +26,45 @@ console.log(suma(rango(1, 10))); ¿Cuál es más probable que contenga un error? -Si contamos el tamaño de las definiciones de `suma` y `rango`, el segundo programa también es grande, incluso más que el primero. Pero, aún así, argumentaría que es más probable que sea correcto. +Si contamos el tamaño de las definiciones de `suma` y `rango`, el segundo programa también es grande, incluso más que el primero. Pero, aún así, diría que es más probable que sea correcto. -Esto se debe a que la solución se expresa en un ((vocabulary)) que corresponde al problema que se está resolviendo. Sumar un rango de números no se trata de bucles y contadores. Se trata de rangos y sumas. +Esto se debe a que la solución se expresa en un ((vocabulario)) que corresponde al problema que se está resolviendo. Sumar un intervalo de números no va considerar bucles y contadores. Va de intervalos y sumas. -Las definiciones de este vocabulario (las funciones `suma` y `rango`) seguirán involucrando bucles, contadores y otros detalles incidentales. Pero debido a que expresan conceptos más simples que el programa en su totalidad, son más fáciles de hacer correctamente. +Las definiciones de este vocabulario (las funciones `suma` y `rango`) no dejan de consistir en trabajar con bucles, contadores y otros detalles. Pero debido a que expresan conceptos más simples que el programa en su totalidad, son más fáciles de hacer correctamente. ## Abstracción -En el contexto de la programación, este tipo de vocabularios se suelen llamar _((abstraction))s_. Las abstracciones nos brindan la capacidad de hablar sobre problemas a un nivel superior (o más abstracto), sin distraernos con detalles no interesantes. +En el contexto de la programación, este tipo de vocabularios se suelen llamar _((abstraccion))es_. Las abstracciones nos brindan la capacidad de hablar sobre problemas a un nivel superior (o más abstracto), sin distraernos con detalles no interesantes. Como analogía, compara estas dos recetas de sopa de guisantes. La primera es así: -_"Pon 1 taza de guisantes secos por persona en un recipiente. Agrega agua hasta que los guisantes estén bien cubiertos. Deja los guisantes en agua durante al menos 12 horas. Saca los guisantes del agua y ponlos en una olla. Agrega 4 tazas de agua por persona. Cubre la olla y deja que los guisantes hiervan a fuego lento durante dos horas. Toma media cebolla por persona. Córtala en trozos con un cuchillo. Agrégala a los guisantes. Toma un tallo de apio por persona. Córtalo en trozos con un cuchillo. Agrégalo a los guisantes. Toma una zanahoria por persona. ¡Córtala en trozos! ¡Con un cuchillo! Agrégala a los guisantes. Cocina durante 10 minutos más."_Cita: +{{quote + +Pon 1 taza de guisantes secos por persona en un recipiente. Añade agua hasta que los guisantes estén bien cubiertos. Deja los guisantes en agua durante al menos 12 horas. Saca los guisantes del agua y ponlos en una olla. Agrega 4 tazas de agua por persona. Cubre la olla y deja los guisantes cociendo a fuego lento durante dos horas. Toma media cebolla por persona. Córtala en trozos con un cuchillo. Agrégala a los guisantes. Toma un tallo de apio por persona. Córtalo en trozos con un cuchillo. Agrégalo a los guisantes. Toma una zanahoria por persona. ¡Córtala en trozos! ¡Con un cuchillo! Agrégala a los guisantes. Cocina durante 10 minutos más. + +quote}} Y esta es la segunda receta: +{{quote + Por persona: 1 taza de guisantes partidos secos, 4 tazas de agua, media cebolla picada, un tallo de apio y una zanahoria. Remoja los guisantes durante 12 horas. Cocina a fuego lento durante 2 horas. Pica y agrega las verduras. Cocina durante 10 minutos más. -El segundo es más corto y más fácil de interpretar. Pero necesitas entender algunas palabras más relacionadas con la cocina, como _remojar_, _cocinar a fuego lento_, _picar_, y, supongo, _verdura_. +quote}} -Cuando se programa, no podemos depender de que todas las palabras que necesitamos estén esperándonos en el diccionario. Por lo tanto, podríamos caer en el patrón de la primera receta: trabajar en los pasos precisos que la computadora tiene que realizar, uno por uno, ciegos a los conceptos de más alto nivel que expresan. +La segunda es más corta y fácil de interpretar. Pero necesitas entender algunas palabras más relacionadas con la cocina, como _remojar_, _cocinar a fuego lento_, _picar_, y, supongo, _verdura_. -Abstraer la repetición +Cuando se programa, no podemos depender de que todas las palabras que necesitamos estén ya escritas en el diccionario para nosotros. Por lo tanto, podríamos caer en el patrón de la primera receta: ejecutar los pasos precisos que la computadora tiene que realizar, uno por uno, sin atender a los conceptos de más alto nivel que expresan. -Las funciones simples, como las hemos visto hasta ahora, son una buena manera de construir abstracciones. Pero a veces se quedan cortas. +Una habilidad útil en programación es darse cuenta de cuándo se está trabajando a un muy bajo nivel de abstracción. -Es común que un programa haga algo un número determinado de veces. Puedes escribir un `for` para eso, así: +## Abstraer la repetición + +Funciones simples como las hemos visto hasta ahora son una buena manera de construir abstracciones. Pero a veces se quedan cortas. + +Es común que un programa haga algo una cantidad determinada de veces. Puedes escribir un `for` para eso, así: ``` for (let i = 0; i < 10; i++) { @@ -65,19 +75,19 @@ for (let i = 0; i < 10; i++) { ¿Podemos abstraer "hacer algo _N_ veces" como una función? Bueno, es fácil escribir una función que llame a `console.log` _N_ veces: ``` -function repeatLog(n) { +function repetirLog(n) { for (let i = 0; i < n; i++) { console.log(i); } } ``` -¿Y si queremos hacer algo que no sea solo registrar los números? Dado que "hacer algo" se puede representar como una función y las funciones son solo valores, podemos pasar nuestra acción como un valor de función: +¿Y si queremos hacer algo que no sea solo pintar los los números? Dado que "hacer algo" se puede representar como una función y las funciones son solo valores, podemos pasar nuestra acción como un valor de función: ```{includeCode: "top_lines: 5"} -function repetir(n, action) { +function repetir(n, acción) { for (let i = 0; i < n; i++) { - action(i); + acción(i); } } @@ -91,22 +101,24 @@ No tenemos que pasar una función predefinida a `repetir`. A menudo, es más fá ``` let etiquetas = []; -repetir(5, i => { - etiquetas.push(`Unidad ${i + 1}`); +repetir(5, x => { + etiquetas.push(`Unidad ${x + 1}`); }); console.log(etiquetas); // → ["Unidad 1", "Unidad 2", "Unidad 3", "Unidad 4", "Unidad 5"] ``` -Esto está estructurado un poco como un `for` loop: primero describe el tipo de loop y luego proporciona un cuerpo. Sin embargo, el cuerpo ahora está escrito como un valor de función, que está envuelto entre los paréntesis de la llamada a `repetir`. Por eso tiene que cerrarse con el corchete de cierre y el paréntesis de cierre. En casos como este ejemplo donde el cuerpo es una sola expresión pequeña, también podrías omitir los corchetes y escribir el bucle en una sola línea. +{{note "**N. del T.:** Con respecto a la versión original del texto, se ha cambiado el nombre del parámetro en la función flecha de `i` a `x` para enfatizar la no necesidad de que el parámetro de dicha función se llame como el parámetro contador del bucle for de la implementación de la función `repetir`."}} + +Esto está estructurado un poco como un bucle `for`: primero describe el tipo de bucle y luego proporciona un cuerpo. Sin embargo, el cuerpo ahora está escrito como un valor de función, que está envuelto entre los paréntesis de la llamada a `repetir`. Por eso tiene que cerrarse con el corchete de cierre _y_ el paréntesis de cierre. En casos como este ejemplo donde el cuerpo es una sola expresión pequeña, también podrías omitir los corchetes y escribir el bucle en una sola línea. -Funciones de orden superior +## Funciones de orden superior -Las funciones que operan en otras funciones, ya sea tomandolas como argumentos o devolviéndolas, se llaman _funciones de orden superior_. Dado que ya hemos visto que las funciones son valores regulares, no hay nada particularmente notable sobre el hecho de que existan tales funciones. El término proviene de las matemáticas, donde se toma más en serio la distinción entre funciones y otros valores. +Las funciones que operan sobre otras funciones, ya sea tomándolas como argumentos o devolviéndolas, se llaman _funciones de orden superior_. Dado que ya hemos visto que las funciones son valores como cualquier otro, no hay nada particularmente notable en el hecho de que existan tales funciones. El término proviene de las matemáticas, donde se toma más en serio la distinción entre funciones y otros valores. {{index abstraction}} -Las funciones de orden superior nos permiten abstraer sobre _acciones_, no solo sobre valores. Vienen en varias formas. Por ejemplo, podemos tener funciones que crean nuevas funciones: +Las funciones de orden superior nos permiten abstraer _acciones_, no solo valores. Las hay de muchas formas. Por ejemplo, podemos tener funciones que crean nuevas funciones: ``` function mayorQue(n) { @@ -151,7 +163,7 @@ repetir(3, n => { {{index [array, "métodos"], [array, "iteración"], "método forEach"}} -Existe un método incorporado de arrays, `forEach`, que proporciona algo similar a un bucle `for`/`of` como una función de orden superior: +Existe un método ya incorporado en los arrays, `forEach`, que proporciona algo similar a un bucle `for`/`of` como una función de orden superior: ``` ["A", "B"].forEach(l => console.log(l)); @@ -161,11 +173,11 @@ Existe un método incorporado de arrays, `forEach`, que proporciona algo similar {{id scripts}} -## Conjunto de datos de script +## Conjunto de datos de sistemas de escritura -Un área donde las funciones de orden superior destacan es en el procesamiento de datos. Para procesar datos, necesitaremos algunos ejemplos de datos reales. Este capítulo utilizará un ((conjunto de datos)) sobre scripts—sistemas de escritura tales como el latín, cirílico o árabe. +Un área donde las funciones de orden superior destacan es en el procesamiento de datos. Para procesar datos, vamos a necesitar algunos datos de ejemplo. Este capítulo utilizará un ((conjunto de datos)) sobre sistemas de escritura tales como el latín, cirílico o árabe. -¿Recuerdas ((Unicode)) del [Capítulo ?](values#unicode), el sistema que asigna un número a cada carácter en lenguaje escrito? La mayoría de estos caracteres están asociados con un script específico. El estándar contiene 140 scripts diferentes, de los cuales 81 aún se utilizan hoy en día y 59 son históricos. +¿Recuerdas ((Unicode)) del [Capítulo ?](values#unicode), el sistema que asigna un número a cada carácter en lenguaje escrito? La mayoría de estos caracteres están asociados con un sistema de escritura concreto. El estándar contiene 140 sistemas diferentes, de los cuales 81 aún se utilizan hoy en día y 59 son históricos. Aunque solo puedo leer con fluidez caracteres latinos, aprecio el hecho de que las personas estén escribiendo textos en al menos otros 80 sistemas de escritura, muchos de los cuales ni siquiera reconocería. Por ejemplo, aquí tienes una muestra de escritura ((Tamil)): @@ -173,56 +185,56 @@ Aunque solo puedo leer con fluidez caracteres latinos, aprecio el hecho de que l {{index "conjunto de datos SCRIPTS"}} -El ejemplo del ((conjunto de datos)) contiene algunas piezas de información sobre los 140 scripts definidos en Unicode. Está disponible en el [sandbox de código](https://eloquentjavascript.net/code#5) para este capítulo[ ([_https://eloquentjavascript.net/code#5_](https://eloquentjavascript.net/code#5))]{if book} como el enlace `SCRIPTS`. El enlace contiene un array de objetos, cada uno describe un script: +El ((conjunto de datos)) de ejemplo contiene información sobre los 140 sistemas de escritura definidos en Unicode. Está disponible en el [sandbox de código](https://eloquentjavascript.net/code#5) para este capítulo[ ([_https://eloquentjavascript.net/code#5_](https://eloquentjavascript.net/code#5))]{if book} como la asociación de nombre `SCRIPTS`. La variable contiene un array de objetos, cada uno describiendo un sistema de escritura: ```{lang: "json"} { - name: "Copto", - rangos: [[994, 1008], [11392, 11508], [11513, 11520]], - dirección: "ltr", - año: -200, - vivo: false, - enlace: "https://es.wikipedia.org/wiki/Alfabeto_copto" + name: "Coptic", + ranges: [[994, 1008], [11392, 11508], [11513, 11520]], + direction: "ltr", + year: -200, + living: false, + link: "https://en.wikipedia.org/wiki/Coptic_alphabet" } ``` -Tal objeto nos informa sobre el nombre del script, los rangos Unicode asignados a él, la dirección en la que se escribe, el tiempo de origen (aproximado), si todavía se utiliza, y un enlace a más información. La dirección puede ser `"ltr"` para izquierda a derecha, `"rtl"` para derecha a izquierda (como se escribe el texto en árabe y hebreo) o `"ttb"` para arriba hacia abajo (como en la escritura mongola). +Tal objeto nos informa sobre el nombre del sistema de lenguaje, los rangos Unicode asignados a él, la dirección en la que se escribe, el momento de origen (aproximado), si todavía se utiliza, y un enlace a más información. La dirección puede ser `"ltr"` para izquierda a derecha, `"rtl"` para derecha a izquierda (como se escribe el texto en árabe y hebreo) o `"ttb"` para arriba hacia abajo (como en la escritura mongola). {{index "método de segmento"}} -La propiedad `ranges` contiene una matriz de ((rangos)) de caracteres Unicode, cada uno de los cuales es una matriz de dos elementos que contiene un límite inferior y un límite superior. Todos los códigos de caracteres dentro de estos rangos se asignan al guion. El límite inferior es inclusivo (el código 994 es un carácter copto) y el límite superior no es inclusivo (el código 1008 no lo es). +La propiedad `ranges` contiene un array de ((rangos)) de caracteres Unicode, cada uno de los cuales es un array de dos elementos que contiene un límite inferior y un límite superior. Todos los códigos de caracteres dentro de estos rangos se asignan al sistema de escritura en cuestión. El límite inferior es inclusivo (el código 994 es un carácter copto) y el límite superior es no inclusivo (el código 1008 no lo es). ## Filtrado de arrays {{index [array, "métodos"], [array, filtrado], "método de filtrado", ["función", "de orden superior"], "función de predicado"}} -Si queremos encontrar los guiones en el conjunto de datos que todavía se utilizan, la siguiente función puede ser útil. Filtra los elementos de una matriz que no pasan una prueba. +Si queremos encontrar en el conjunto de datos qué sistemas de escritura todavía se utilizan, la siguiente función puede ser útil. Deja fuera los elementos de un array que no cumplen una cierta comprobación. ``` -function filter(array, test) { - let passed = []; - for (let element of array) { - if (test(element)) { - passed.push(element); +function filtrar(array, comprobación) { + let pasada = []; + for (let elemento of array) { + if (comprobación(elemento)) { + pasada.push(elemento); } } - return passed; + return pasada; } -console.log(filter(SCRIPTS, script => script.living)); +console.log(filtrar(SCRIPTS, sistema => sistema.living)); // → [{name: "Adlam", …}, …] ``` {{index ["función", "como valor"], ["función", "aplicación"]}} -La función utiliza el argumento llamado `test`, un valor de función, para llenar un "vacío" en la computación, el proceso de decidir qué elementos recopilar. +La función utiliza el argumento llamado `comprobación`, un valor de función, para llenar un "hueco" en el procedimiento de filtrado: el proceso de decidir qué elementos recopilar. {{index "método de filtrado", "función pura", "efecto secundario"}} -Observa cómo la función `filter`, en lugar de eliminar elementos de la matriz existente, construye una nueva matriz con solo los elementos que pasan la prueba. Esta función es _pura_. No modifica la matriz que se le pasa. +Observa cómo la función `filtrar`, en lugar de eliminar elementos de la matriz existente, construye una nueva matriz con solo los elementos que pasan la prueba. Esta función es _pura_. No modifica la matriz que se le pasa. -Al igual que `forEach`, `filter` es un método de matriz ((estándar)). El ejemplo definió la función solo para mostrar qué hace internamente. De ahora en adelante, lo usaremos de esta manera en su lugar: +Al igual que con `forEach`, hay un método ((estándar)) para `filtrar` en los arrays, el método `filter`. En el ejemplo se define la función solo para mostrar qué hace internamente. De ahora en adelante, lo usaremos de esta manera en su lugar: ``` console.log(SCRIPTS.filter(s => s.direction == "ttb")); @@ -235,58 +247,58 @@ console.log(SCRIPTS.filter(s => s.direction == "ttb")); {{index [array, "métodos"], "método de mapeo"}} -Digamos que tenemos una matriz de objetos que representan guiones, producida al filtrar la matriz `SCRIPTS` de alguna manera. Queremos una matriz de nombres en su lugar, que es más fácil de inspeccionar. +Digamos que tenemos un array de objetos que representan sistemas de escritura, producido al filtrar el array `SCRIPTS` de alguna manera. En su lugar, queremos un array de nombres, que es más fácil de inspeccionar. {{index ["función", "de orden superior"]}} -El método `map` transforma una matriz aplicando una función a todos sus elementos y construyendo una nueva matriz a partir de los valores devueltos. La nueva matriz tendrá la misma longitud que la matriz de entrada, pero su contenido habrá sido _mapeado_ a una nueva forma por la función: +El método `map` transforma un array aplicando una función a todos sus elementos y construyendo un nuevo array a partir de los valores devueltos. El nuevo array tendrá la misma longitud que el de entrada, pero su contenido habrá sido _mapeado_ a una nueva forma por la función: ``` -function map(array, transform) { - let mapped = []; - for (let element of array) { - mapped.push(transform(element)); +function mapear(array, transformación) { + let mapeados = []; + for (let elemento of array) { + mapeados.push(transformación(elemento)); } - return mapped; + return mapeados; } let rtlScripts = SCRIPTS.filter(s => s.direction == "rtl"); -console.log(map(rtlScripts, s => s.name)); +console.log(mapear(rtlScripts, s => s.name)); // → ["Adlam", "Arabic", "Imperial Aramaic", …] ``` -Al igual que `forEach` y `filter`, `map` es un método de matriz estándar. +Al igual que `forEach` y `filter`, hay un método estándar para `mapear` en los arrays, el método `map`. -## Resumen con reduce +## Resumiendo con reduce {{index [array, "métodos"], "ejemplo de suma", "método de reducción"}} -Otra cosa común que hacer con matrices es calcular un único valor a partir de ellas. Nuestro ejemplo recurrente, sumar una colección de números, es una instancia de esto. Otro ejemplo es encontrar el guion con más caracteres. +Otra cosa común que hacer con arrays es calcular un único valor a partir de ellos. Nuestro ejemplo de siempre, sumar una colección de números, es una ejemplo de esto. Otro ejemplo es encontrar el sistema de escritura con más caracteres. {{indexsee "fold", "método de reducción"}} {{index ["función", "de orden superior"], "método de reducción"}} -La operación de orden superior que representa este patrón se llama _reduce_ (a veces también llamada _fold_). Construye un valor tomando repetidamente un único elemento del array y combinándolo con el valor actual. Al sumar números, comenzarías con el número cero y, para cada elemento, lo sumarías al total. +La operación de orden superior que representa esta idea se llama _reduce_ (a veces también llamada _fold_). Construye un valor tomando repetidamente un único elemento del array y combinándolo con el valor actual. Al sumar números empezarías con el número cero y añadirías cada elemento a la suma. -Los parámetros de `reduce` son, además del array, una función de combinación y un valor inicial. Esta función es un poco menos directa que `filter` y `map`, así que obsérvala detenidamente: +Los parámetros de `reduce` son, además del array, una función de combinación y un valor inicial. Esta función es un poco menos directa que `filter` y `map`, así que obsérva detenidamente: ``` -function reduce(array, combine, start) { - let current = start; - for (let element of array) { - current = combine(current, element); +function reducir(array, combinación, principio) { + let actual = inicio; + for (let elemento of array) { + actual = combinación(actual, elemento); } - return current; + return actual; } -console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0)); +console.log(reducir([1, 2, 3, 4], (a, b) => a + b, 0)); // → 10 ``` {{index "método reduce", "conjunto de datos SCRIPTS"}} -El método estándar de arrays `reduce`, que por supuesto corresponde a esta función, tiene una conveniencia adicional. Si tu array contiene al menos un elemento, puedes omitir el argumento `start`. El método tomará el primer elemento del array como su valor inicial y comenzará a reducir en el segundo elemento. +El método estándar de arrays, `reduce` —que por supuesto corresponde a esta función— tiene una ventaja adicional. Si tu array contiene al menos un elemento, puedes omitir el argumento `start`. El método tomará el primer elemento del array como su valor inicial y comenzará a reducir en el segundo elemento. ``` console.log([1, 2, 3, 4].reduce((a, b) => a + b)); @@ -295,123 +307,123 @@ console.log([1, 2, 3, 4].reduce((a, b) => a + b)); {{index "máximo", "función characterCount"}} -Para usar `reduce` (dos veces) y encontrar el script con más caracteres, podemos escribir algo así: +Para usar `reduce` (dos veces) y encontrar el sistema de escritura con más caracteres, podemos escribir algo así: ``` -function characterCount(script) { - return script.ranges.reduce((count, [from, to]) => { - return count + (to - from); +function contarCaracteres(sistema) { + return sistema.ranges.reduce((contador, [desde, hasta]) => { + return contador + (hasta - desde); }, 0); } console.log(SCRIPTS.reduce((a, b) => { - return characterCount(a) < characterCount(b) ? b : a; + return contarCaracteres(a) < contarCaracteres(b) ? b : a; })); // → {name: "Han", …} ``` -La función `characterCount` reduce los rangos asignados a un script sumando sus tamaños. Observa el uso de la desestructuración en la lista de parámetros de la función reductora. La segunda llamada a `reduce` luego utiliza esto para encontrar el script más grande comparando repetidamente dos scripts y devolviendo el más grande. +La función `contarCaracteres` reduce los rangos asignados a un sistema de escritura sumando sus tamaños. Observa el uso de la desestructuración en la lista de parámetros de la función reductora. La segunda llamada a `reduce` luego utiliza esto para encontrar el sistema de escritura más grande comparando repetidamente dos sistemas y devolviendo el más grande. -El script Han tiene más de 89,000 caracteres asignados en el estándar Unicode, convirtiéndolo en el sistema de escritura más grande en el conjunto de datos. Han es un script a veces utilizado para texto en chino, japonés y coreano. Esos idiomas comparten muchos caracteres, aunque tienden a escribirlos de manera diferente. El Consorcio Unicode (con sede en EE. UU.) decidió tratarlos como un único sistema de escritura para ahorrar códigos de caracteres. Esto se llama _unificación Han_ y todavía molesta a algunas personas. +El sistema de escritura Han (es decir, el sistema de escritura chino actual) tiene más de 89000 caracteres asignados en el estándar Unicode, convirtiéndolo en el sistema de escritura más grande del conjunto de datos. El sistema Han es un sistema a veces utilizado para texto en chino, japonés y coreano. Estos idiomas comparten muchos caracteres, aunque tienden a escribirlos de manera diferente. El Consorcio Unicode (con sede en EE. UU.) decidió tratarlos como un único sistema de escritura para ahorrar códigos de caracteres. Esto se llama _unificación Han_ y aún hay gente que no está muy contenta con ella. ## Composabilidad {{index bucle, "máximo"}} -Considera cómo hubiéramos escrito el ejemplo anterior (encontrando el script más grande) sin funciones de orden superior. El código no es mucho peor: +Considera cómo hubiéramos escrito el ejemplo anterior (encontrar el sistema más grande) sin funciones de orden superior. El código no es tan inferior al anterior. ```{test: no} -let biggest = null; -for (let script of SCRIPTS) { - if (biggest == null || - characterCount(biggest) < characterCount(script)) { - biggest = script; +let másGrande = null; +for (let sistema of SCRIPTS) { + if (másGrande == null || + contarCaracteres(másGrande) < contarCaracteres(sistema)) { + másGrande = sistema; } } -console.log(biggest); +console.log(másGrande); // → {name: "Han", …} ``` -Hay algunas variables adicionales y el programa tiene cuatro líneas más, pero sigue siendo muy legible. +Hay algunas variables más y el programa tiene cuatro líneas más, pero sigue siendo muy legible. {{index "función promedio", composabilidad, ["función", "de orden superior"], "método filter", "método map", "método reduce"}} {{id average_function}} -Las abstracciones proporcionadas por estas funciones brillan realmente cuando necesitas _componer_ operaciones. Como ejemplo, escribamos un código que encuentre el año promedio de origen para scripts vivos y muertos en el conjunto de datos: +Las abstracciones proporcionadas por estas funciones brillan realmente cuando necesitas _componer_ operaciones. Como ejemplo, escribamos un código que encuentre el año promedio de origen para sistemas vivos y muertos en el conjunto de datos: ``` -function average(array) { +function promedio(array) { return array.reduce((a, b) => a + b) / array.length; } -console.log(Math.round(average( +console.log(Math.round(promedio( SCRIPTS.filter(s => s.living).map(s => s.year)))); // → 1165 -console.log(Math.round(average( +console.log(Math.round(promedio( SCRIPTS.filter(s => !s.living).map(s => s.year)))); // → 204 ``` -Como puedes ver, los scripts muertos en Unicode son, en promedio, más antiguos que los vivos. Esta no es una estadística muy significativa o sorprendente. Pero espero que estés de acuerdo en que el código utilizado para calcularlo no es difícil de leer. Puedes verlo como un pipeline: empezamos con todos los scripts, filtramos los vivos (o muertos), tomamos los años de esos scripts, calculamos el promedio y redondeamos el resultado. +Como puedes ver, los sistemas de escritura muertos en Unicode son, en promedio, más antiguos que los vivos. Esta no es una estadística muy significativa o sorprendente. Pero espero que estés de acuerdo en que el código utilizado para calcularlo no es difícil de leer. Puedes verlo como una cadena de procesos (pipeline): empezamos con todos los sistemas, filtramos los vivos (o muertos), tomamos los años de esos sistemas, calculamos el promedio y redondeamos el resultado. -Definitivamente también podrías escribir este cálculo como un único ((loop)) grande: +Definitivamente también podrías escribir este cálculo como un único ((bucle)) grande: ``` -let total = 0, count = 0; -for (let script of SCRIPTS) { - if (script.living) { - total += script.year; - count += 1; +let total = 0, contador = 0; +for (let sistema of SCRIPTS) { + if (sistema.living) { + total += sistema.year; + contador += 1; } } -console.log(Math.round(total / count)); +console.log(Math.round(total / contador)); // → 1165 ``` -Sin embargo, es más difícil ver qué se estaba calculando y cómo. Y debido a que los resultados intermedios no se representan como valores coherentes, sería mucho más trabajo extraer algo como `average` en una función separada. +Sin embargo, es más difícil ver qué se estaba calculando y cómo. Y como los resultados intermedios no se representan como valores coherentes, sería mucho más trabajo extraer algo como el `promedio` en una función separada. {{index efficiency, [array, creation]}} -En términos de lo que realmente está haciendo la computadora, estos dos enfoques también son bastante diferentes. El primero construirá nuevos arrays al ejecutar `filter` y `map`, mientras que el segundo calcula solo algunos números, haciendo menos trabajo. Por lo general, puedes permitirte el enfoque legible, pero si estás procesando matrices enormes y haciéndolo muchas veces, el estilo menos abstracto podría valer la pena por la velocidad adicional. +En términos de lo que realmente está haciendo la computadora, estos dos enfoques también son bastante distintos. El primero construirá nuevos arrays al ejecutar `filter` y `map`, mientras que el segundo calcula solo algunos números, haciendo menos trabajo. Por lo general, puedes permitirte el enfoque legible, pero si estás procesando arrays enormes y haciéndolo muchas veces, un estilo menos abstracto podría valer la pena a cambio de velocidad adicional. ## Cadenas y códigos de caracteres {{index "SCRIPTS data set"}} -Un uso interesante de este conjunto de datos sería averiguar qué script está utilizando un fragmento de texto. Vamos a través de un programa que hace esto. +Un uso interesante de este conjunto de datos sería averiguar qué sistema de escritura está utilizando un fragmento de texto. Veamos un programa que hace esto. -Recuerda que cada script tiene asociado un array de intervalos de códigos de caracteres. Dado un código de carácter, podríamos usar una función como esta para encontrar el script correspondiente (si lo hay): +Recuerda que cada sistema de escritura tiene asociado un array de intervalos de códigos de caracteres. Dado un código de carácter, podríamos usar una función como esta para encontrar el sistema correspondiente (si lo hay): {{index "some method", "predicate function", [array, methods]}} ```{includeCode: strip_log} -function characterScript(code) { - for (let script of SCRIPTS) { - if (script.ranges.some(([from, to]) => { - return code >= from && code < to; +function sistemaCaracteres(código) { + for (let sistema of SCRIPTS) { + if (sistema.ranges.some(([desde, hasta]) => { + return código >= desde && código < hasta; })) { - return script; + return sistema; } } return null; } -console.log(characterScript(121)); +console.log(sistemaCaracteres(121)); // → {name: "Latin", …} ``` -El método `some` es otra función de orden superior. Toma una función de prueba y te dice si esa función devuelve true para alguno de los elementos en el array. +El método `some` es otra función de orden superior. Toma una función de comprobación y te dice si esa función devuelve true para alguno de los elementos en el array. {{id code_units}} Pero, ¿cómo obtenemos los códigos de caracteres en una cadena? -En [Chapter ?](values) mencioné que las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estos se llaman _((unidades de código))_. Un código de carácter Unicode inicialmente se suponía que cabía dentro de tal unidad (lo que te da un poco más de 65,000 caracteres). Cuando quedó claro que eso no iba a ser suficiente, muchas personas se mostraron reacias a la necesidad de usar más memoria por carácter. Para abordar estas preocupaciones, se inventó ((UTF-16)), el formato también utilizado por las cadenas de JavaScript. Describe la mayoría de los caracteres comunes usando una única unidad de código de 16 bits, pero usa un par de dos unidades de dicho tipo para otros. +En el [Capítulo ?](values) mencioné que las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estos se llaman _((unidades de código))_. Al principio, se suponía que un código de caracter Unicode cabía dentro de tal unidad (lo que te da algo más de 65000 caracteres). Cuando quedó claro que eso no iba a ser suficiente, mucha gente se mostró reacia a la necesidad de usar más memoria por caracter. Para abordar estas preocupaciones, se inventó ((UTF-16)), el formato que usan las cadenas de JavaScript. Describe la mayoría de los caracteres comunes usando una única unidad de código de 16 bits, pero usa un par de dos unidades de dicho tipo para otros. {{index error}} -UTF-16 generalmente se considera una mala idea hoy en día. Parece casi diseñado intencionalmente para invitar a errores. Es fácil escribir programas que pretendan que las unidades de código y los caracteres son lo mismo. Y si tu lenguaje no utiliza caracteres de dos unidades, eso parecerá funcionar perfectamente. Pero tan pronto como alguien intente usar dicho programa con algunos caracteres chinos menos comunes, fallará. Afortunadamente, con la llegada de los emoji, todo el mundo ha comenzado a usar caracteres de dos unidades, y la carga de tratar con tales problemas está más equitativamente distribuida. +UTF-16 generalmente se considera una mala idea hoy en día. Parece casi diseñado intencionalmente para provocar errores. Es fácil escribir programas que asuman que las unidades de código y los caracteres son lo mismo. Y si tu lenguaje no utiliza caracteres de dos unidades, eso parecerá funcionar perfectamente. Pero tan pronto como alguien intente usar dicho programa con algunos caracteres menos comunes como los chinos, fallará. Por suerte, con la llegada de los emoji, todo el mundo ha comenzado a usar caracteres de dos unidades, y tratar con tales problemas se está haciendo más llevadero. {{index [cadena, longitud], [cadena, "indexación"], "método charCodeAt"}} @@ -419,14 +431,14 @@ Lamentablemente, las operaciones obvias en las cadenas de JavaScript, como obten ```{test: no} // Dos caracteres emoji, caballo y zapato -let horseShoe = "🐴👟"; -console.log(horseShoe.length); +let caballoZapato = "🐴👟"; +console.log(caballoZapato.length); // → 4 -console.log(horseShoe[0]); -// → (Mitad de carácter inválida) -console.log(horseShoe.charCodeAt(0)); -// → 55357 (Código de la mitad de carácter) -console.log(horseShoe.codePointAt(0)); +console.log(caballoZapato[0]); +// → (Mitad de caracter inválida) +console.log(caballoZapato.charCodeAt(0)); +// → 55357 (Código de la mitad de caracter) +console.log(caballoZapato.codePointAt(0)); // → 128052 (Código real para el emoji de caballo) ``` @@ -439,42 +451,42 @@ El método `charCodeAt` de JavaScript te da una unidad de código, no un código En el [capítulo anterior](datos#bucle_for_of), mencioné que un bucle `for`/`of` también se puede usar en cadenas. Al igual que `codePointAt`, este tipo de bucle se introdujo en un momento en que la gente era muy consciente de los problemas con UTF-16. Cuando lo usas para recorrer una cadena, te proporciona caracteres reales, no unidades de código: ``` -let roseDragon = "🌹🐉"; -for (let char of roseDragon) { - console.log(char); +let rosaDragón = "🌹🐉"; +for (let caracter of rosaDragón) { + console.log(caracter); } // → 🌹 // → 🐉 ``` -Si tienes un carácter (que será una cadena de una o dos unidades de código), puedes usar `codePointAt(0)` para obtener su código. +Si tienes un caracter (que será una cadena de una o dos unidades de código), puedes usar `codePointAt(0)` para obtener su código. ## Reconociendo texto {{index "conjunto de datos SCRIPTS", "función countBy", [array, conteo]}} -Tenemos una función `characterScript` y una forma de recorrer correctamente los caracteres. El próximo paso es contar los caracteres que pertenecen a cada script. La siguiente abstracción de conteo será útil para eso: +Tenemos una función `sistemaCaracteres` y una forma de recorrer correctamente los caracteres. El próximo paso es contar los caracteres que pertenecen a cada sistema de escritura. La siguiente abstracción de recuento será útil para eso: ```{includeCode: strip_log} -function countBy(items, groupName) { - let counts = []; +function contarPor(items, nombreGrupo) { + let recuentos = []; for (let item of items) { - let name = groupName(item); - let known = counts.find(c => c.name == name); - if (!known) { - counts.push({name, count: 1}); + let nombre = nombreGrupo(item); + let conocido = recuentos.find(c => c.nombre == nombre); + if (!conocido) { + recuentos.push({nombre, recuento: 1}); } else { - known.count++; + conocido.recuento++; } } - return counts; + return recuentos; } -console.log(countBy([1, 2, 3, 4, 5], n => n > 2)); -// → [{name: false, count: 2}, {name: true, count: 3}] +console.log(contarPor([1, 2, 3, 4, 5], n => n > 2)); +// → [{nombre: false, recuento: 2}, {nombre: true, recuento: 3}] ``` -La función `countBy` espera una colección (cualquier cosa por la que podamos iterar con `for`/`of`) y una función que calcule un nombre de grupo para un elemento dado. Devuelve una matriz de objetos, cada uno de los cuales nombra un grupo y te dice el número de elementos que se encontraron en ese grupo. +La función `contarPor` espera una colección (cualquier cosa por la que podamos iterar con `for`/`of`) y una función que calcule un nombre de grupo para un elemento dado. Devuelve una matriz de objetos, cada uno de los cuales nombra un grupo y te dice el número de elementos que se encontraron en ese grupo. {{index "método find"}} @@ -482,40 +494,40 @@ Utiliza otro método de array, `find`, que recorre los elementos en el array y d {{index "función textScripts", "caracteres chinos"}} -Usando `countBy`, podemos escribir la función que nos dice qué scripts se utilizan en un fragmento de texto: +Usando `contarPor`, podemos escribir la función que nos dice qué sistemas de escritura se utilizan en un fragmento de texto: ```{includeCode: strip_log, startCode: true} -function textScripts(text) { - let scripts = countBy(text, char => { - let script = characterScript(char.codePointAt(0)); - return script ? script.name : "ninguno"; - }).filter(({name}) => name != "ninguno"); +function sistemasTexto(texto) { + let sistemas = contarPor(texto, caracter => { + let sistema = sistemaCaracteres(caracter.codePointAt(0)); + return sistema ? sistema.name : "ninguno"; + }).filter(({nombre}) => nombre != "ninguno"); - let total = scripts.reduce((n, {count}) => n + count, 0); - if (total == 0) return "No se encontraron scripts"; + let total = sistemas.reduce((n, {recuento}) => n + recuento, 0); + if (total == 0) return "No se encontraron sistemas"; - return scripts.map(({name, count}) => { - return `${Math.round(count * 100 / total)}% ${name}`; + return sistemas.map(({nombre, recuento}) => { + return `${Math.round(recuento * 100 / total)}% ${nombre}`; }).join(", "); } -console.log(textScripts('英国的狗说"woof", 俄罗斯的狗说"тяв"')); +console.log(sistemasTexto('英国的狗说"woof", 俄罗斯的狗说"тяв"')); // → 61% Han, 22% Latin, 17% Cyrillic ``` {{index "función characterScript", "método filter"}} -La función primero cuenta los caracteres por nombre, usando `characterScript` para asignarles un nombre y retrocediendo a la cadena `"ninguno"` para los caracteres que no forman parte de ningún script. La llamada a `filter` elimina la entrada de `"ninguno"` del array resultante, ya que no nos interesan esos caracteres. +La función primero recoge los nombres de los sistemas de escritura de los caracteres en el texto usando `sistemaCaracteres` para asignarles un nombre y recurriendo a la cadena `"ninguno"` para los caracteres que no forman parte de ningún sistema. La llamada a `filter` elimina la entrada correspondiente a `"ninguno"` del array resultante, ya que no nos interesan esos caracteres. {{index "método reduce", "método map", "método join", [array, methods]}} -Para poder calcular porcentajes, primero necesitamos el número total de caracteres que pertenecen a un script, lo cual podemos calcular con `reduce`. Si no se encuentran dichos caracteres, la función devuelve una cadena específica. De lo contrario, transforma las entradas de conteo en cadenas legibles con `map` y luego las combina con `join`. +Para poder calcular porcentajes, primero necesitamos el número total de caracteres que pertenecen a un sistema dado, lo cual podemos calcular con `reduce`. Si no se encuentran dichos caracteres, la función devuelve una cadena específica. De lo contrario, transforma las entradas de conteo en cadenas legibles con `map` y luego las combina con `join`. ## Resumen -Poder pasar valores de funciones a otras funciones es un aspecto muy útil de JavaScript. Nos permite escribir funciones que modelan cálculos con "vacíos". El código que llama a estas funciones puede llenar los vacíos proporcionando valores de funciones. +Poder pasar valores de funciones a otras funciones es un aspecto muy útil de JavaScript. Nos permite escribir funciones que modelan cálculos con "huecos a rellenar" en ellas. El código que llama a estas funciones puede llenar los huecos proporcionando valores de funciones. -Los arrays proporcionan diversos métodos de orden superior útiles. Puedes usar `forEach` para recorrer los elementos de un array. El método `filter` devuelve un nuevo array que contiene solo los elementos que pasan la ((función de predicado)). Transformar un array poniendo cada elemento en una función se hace con `map`. Puedes usar `reduce` para combinar todos los elementos de un array en un único valor. El método `some` comprueba si algún elemento coincide con una función de predicado dada, mientras que `find` encuentra el primer elemento que coincide con un predicado. +Los arrays proporcionan diversos métodos de orden superior muy útiles. Puedes usar `forEach` para recorrer los elementos de un array. El método `filter` devuelve un nuevo array que contiene solo los elementos que pasan la ((función de predicado)). Transformar un array poniendo cada elemento en una función se hace con `map`. Puedes usar `reduce` para combinar todos los elementos de un array en un único valor. El método `some` comprueba si algún elemento satisface una función de predicado dada, mientras que `find` encuentra el primer elemento que satisface un predicado. ## Ejercicios @@ -538,9 +550,9 @@ if}} {{index "ejemplo tu propio bucle", "bucle for"}} -Escribe una función de orden superior `loop` que proporcione algo similar a una declaración `for` loop. Debería recibir un valor, una función de prueba, una función de actualización y una función de cuerpo. En cada iteración, primero debe ejecutar la función de prueba en el valor actual del bucle y detenerse si devuelve falso. Luego debe llamar a la función de cuerpo, dándole el valor actual, y finalmente llamar a la función de actualización para crear un nuevo valor y empezar de nuevo desde el principio. +Escribe una función de orden superior `loop` que proporcione algo similar a una declaración de bucle `for`. Debería recibir un valor, una función de comprobación, una función de actualización y una función de cuerpo. En cada iteración, primero debe ejecutar la función de comprobación en el valor actual del bucle y detenerse si devuelve falso. Luego debe llamar a la función de cuerpo, pasándole el valor actual, y finalmente llamar a la función de actualización para crear un nuevo valor y empezar de nuevo desde el principio. -Al definir la función, puedes usar un bucle regular para hacer el bucle real. +Al definir la función, puedes usar un bucle normal para hacer el bucle real. {{if interactive @@ -557,7 +569,7 @@ if}} ### Everything -Los arrays también tienen un método `every` análogo al método `some`. Este método devuelve `true` cuando la función dada devuelve `true` para _cada_ elemento en el array. En cierto modo, `some` es una versión del operador `||` que actúa en arrays, y `every` es como el operador `&&`. +Los arrays también tienen un método `every` análogo al método `some`. Este método devuelve `true` cuando la función dada devuelve `true` para _todo_ elemento en el array. En cierto modo, `some` es una versión del operador `||` que actúa en arrays, y `every` es como el operador `&&`. Implementa `every` como una función que recibe un array y una función de predicado como parámetros. Escribe dos versiones, una usando un bucle y otra usando el método `some`. @@ -582,17 +594,17 @@ if}} {{index "everything (exercise)", "short-circuit evaluation", "return keyword"}} -Como el operador `&&`, el método `every` puede dejar de evaluar más elementos tan pronto como encuentre uno que no coincida. Por lo tanto, la versión basada en bucle puede salir del bucle—con `break` o `return—tan pronto como encuentre un elemento para el que la función de predicado devuelva false. Si el bucle se ejecuta hasta el final sin encontrar dicho elemento, sabemos que todos los elementos coincidieron y deberíamos devolver true. +Al igual que el operador `&&`, el método `every` puede dejar de evaluar más elementos tan pronto como encuentre uno que no coincida. Por lo tanto, la versión basada en un bucle puede salir del bucle —con `break` o `return`— tan pronto como encuentre un elemento para el que la función de predicado devuelva false. Si el bucle se ejecuta hasta el final sin encontrar dicho elemento, sabemos que todos los elementos coincidieron y deberíamos devolver true. {{index "método some"}} -Para construir `every` sobre `some`, podemos aplicar _((leyes de De Morgan))_, que establecen que `a && b` es igual a `!(!a || !b)`. Esto se puede generalizar a arrays, donde todos los elementos en el array coinciden si no hay ningún elemento en el array que no coincida. +Para construir `every` sobre `some`, podemos aplicar _((leyes de De Morgan))_, que establecen que `a && b` tiene el mismo valor que `!(!a || !b)`. Esto se puede generalizar a arrays, donde todos los elementos en el array coinciden si no hay ningún elemento en el array que no coincida. hint}} ### Dirección de escritura dominante -Escribe una función que calcule la dirección de escritura dominante en una cadena de texto. Recuerda que cada objeto script tiene una propiedad `direction` que puede ser `"ltr"` (de izquierda a derecha), `"rtl"` (de derecha a izquierda) o `"ttb"` (de arriba a abajo). +Escribe una función que calcule la dirección de escritura dominante en una cadena de texto. Recuerda que cada objeto de sistema de escritura tiene una propiedad `direction` que puede ser `"ltr"` (de izquierda a derecha), `"rtl"` (de derecha a izquierda) o `"ttb"` (de arriba a abajo). {{if interactive @@ -612,10 +624,10 @@ if}} {{index "dirección dominante (ejercicio)", "función textScripts", "método filter", "función characterScript"}} -Tu solución podría parecerse mucho a la primera mitad del ejemplo de `textScripts`. De nuevo, debes contar caracteres según un criterio basado en `characterScript` y luego filtrar la parte del resultado que se refiere a caracteres no interesantes (sin script). +Tu solución podría parecerse mucho a la primera mitad del ejemplo de `sistemasTexto`. De nuevo, debes contar caracteres según un criterio basado en `sistemaCaracteres` y luego filtrar la parte del resultado que se refiere a caracteres no interesantes (sin sistema asociado). {{index "método reduce"}} -Encontrar la dirección con el recuento de caracteres más alto se puede hacer con `reduce`. Si no está claro cómo hacerlo, consulta el ejemplo anterior en el capítulo, donde se usó `reduce` para encontrar el script con más caracteres. +Encontrar la dirección con el recuento de caracteres más alto es algo que se puede hacer con `reduce`. Si no está claro cómo hacerlo, consulta el ejemplo que vimos antes en el capítulo, donde se usó `reduce` para encontrar el script con más caracteres. hint}} \ No newline at end of file diff --git a/html/00_intro.html b/html/00_intro.html index 563c7de1..69a24ea4 100644 --- a/html/00_intro.html +++ b/html/00_intro.html @@ -120,29 +120,29 @@

Aunque eso ya es más legible que la sopa de bits anterior, sigue siendo bastante confuso. Usar nombres en lugar de números para las instrucciones y las ubicaciones de memoria ayuda:

-
 Establecer “total” en 0.
- Establecer “count” en 1.
+
 Establecer “total” en 0.
+ Establecer “contador” en 1.
 [bucle]
- Establecer “compare” en “count”.
- Restar 11 de “compare”.
- Si “compare” es cero, continuar en [fin].
- Sumar “count” a “total”.
- Añadir 1 a “count”.
+ Establecer “comparación” en “contador”.
+ Restar 11 de “comparación”.
+ Si “comparación” es cero, continuar en [fin].
+ Sumar “contador” a “total”.
+ Añadir 1 a “contador”.
  Continuar en [bucle].
 [fin]
  Mostrar “total”.
-

¿Puedes ver cómo funciona el programa en este punto? Las dos primeras líneas asignan los valores iniciales a dos ubicaciones de memoria: total se utilizará para construir el resultado de la suma, y count llevará la cuenta del número que estamos observando en ese momento. Las líneas que utilizan compare probablemente sean las más confusas. El programa quiere ver si count es igual a 11 para decidir si puede parar de ejecutarse. Debido a que nuestra máquina hipotética es bastante primitiva, solo puede comprobar si un número es cero y tomar una decisión en función de ese valor. Por lo tanto, utiliza la ubicación de memoria etiquetada como compare para calcular el valor de count - 11 y tomar una decisión basada en el resultado. Las siguientes dos líneas suman el valor de count al resultado e incrementan count en 1 cada vez que el programa decide que count aún no vale 11. Aquí está el mismo programa en JavaScript:

+

¿Puedes ver cómo funciona el programa en este punto? Las dos primeras líneas asignan los valores iniciales a dos ubicaciones de memoria: total se utilizará para construir el resultado de la suma, y contador llevará la cuenta del número que estamos observando en ese momento. Las líneas que utilizan comparación probablemente sean las más confusas. El programa quiere ver si contador es igual a 11 para decidir si puede parar de ejecutarse. Debido a que nuestra máquina hipotética es bastante primitiva, solo puede comprobar si un número es cero y tomar una decisión en función de ese valor. Por lo tanto, utiliza la ubicación de memoria etiquetada como comparación para calcular el valor de contador - 11 y tomar una decisión basada en el resultado. Las siguientes dos líneas suman el valor de contador al resultado e incrementan contador en 1 cada vez que el programa decide que contador aún no vale 11. Aquí está el mismo programa en JavaScript:

-
let total = 0, count = 1;
-while (count <= 10) {
-  total += count;
-  count += 1;
+
let total = 0, contador = 1;
+while (contador <= 10) {
+  total += contador;
+  contador += 1;
 }
 console.log(total);
 // → 55
-

Esta versión nos proporciona algunas mejoras más. Lo más importante es que ya no es necesario especificar la forma en que queremos que el programa salte hacia adelante y hacia atrás; la construcción while se encarga de eso. Continúa ejecutando el bloque (entre llaves) debajo de él siempre y cuando se cumpla la condición que se le ha dado. Esa condición es count <= 10, lo que significa “el recuento es menor o igual a 10”. Ya no tenemos que crear un valor temporal y compararlo con cero, lo cual era simplemente un detalle carente de interés. Parte del poder de los lenguajes de programación es que pueden encargarse de los detalles que no nos interesan.

+

Esta versión nos proporciona algunas mejoras más. Lo más importante es que ya no es necesario especificar la forma en que queremos que el programa salte hacia adelante y hacia atrás; la construcción while se encarga de eso. Continúa ejecutando el bloque (entre llaves) debajo de él siempre y cuando se cumpla la condición que se le ha dado. Esa condición es contador <= 10, lo que significa “el recuento es menor o igual a 10”. Ya no tenemos que crear un valor temporal y compararlo con cero, lo cual era simplemente un detalle carente de interés. Parte del poder de los lenguajes de programación es que pueden encargarse de los detalles que no nos interesan.

Al final del programa, después de que la construcción while haya terminado, se utiliza la operación console.log para mostrar el resultado.

diff --git a/html/05_higher_order.html b/html/05_higher_order.html index ab8ffbd4..8f85d351 100644 --- a/html/05_higher_order.html +++ b/html/05_higher_order.html @@ -12,18 +12,18 @@

Funciones de Orden Superior

-

“Hay dos formas de construir un diseño de software: Una forma es hacerlo tan simple que obviamente no haya deficiencias, y la otra forma es hacerlo tan complicado que no haya deficiencias obvias.”

+

“Hay dos maneras de construir un diseño de software: una forma es hacerlo tan simple que obviamente no haya defectos, y la otra forma es hacerlo tan complicado que no haya defectos obvios.”

— C.A.R. Hoare, Discurso de Recepción del Premio Turing de la ACM de 1980

-

Un programa grande es un programa costoso, y no solo por el tiempo que lleva construirlo. El tamaño casi siempre implica complejidad, y la complejidad confunde a los programadores. Los programadores confundidos, a su vez, introducen errores (bugs) en los programas. Un programa grande proporciona mucho espacio para que estos errores se escondan, lo que los hace difíciles de encontrar.

+

Un programa grande es un programa costoso, y no solo por el tiempo que lleva construirlo. El tamaño casi siempre implica complejidad, y la complejidad confunde a los programadores. Los programadores confundidos, a su vez, introducen errores (bugs) en los programas. Un programa grande da mucho hueco para que estos errores se escondan, lo que los hace difíciles de encontrar.

-

Volviendo brevemente a los dos ejemplos finales de programas en la introducción. El primero es autocontenido y tiene seis líneas:

+

Vamos a volver por un momento a los dos ejemplos de programas del final de la introducción. El primero es autocontenido y tiene seis líneas:

-
let total = 0, count = 1;
-while (count <= 10) {
-  total += count;
-  count += 1;
+
let total = 0, contador = 1;
+while (contador <= 10) {
+  total += contador;
+  contador += 1;
 }
 console.log(total);
@@ -33,35 +33,45 @@

Funciones de Orden Superior

¿Cuál es más probable que contenga un error?

-

Si contamos el tamaño de las definiciones de suma y rango, el segundo programa también es grande, incluso más que el primero. Pero, aún así, argumentaría que es más probable que sea correcto.

+

Si contamos el tamaño de las definiciones de suma y rango, el segundo programa también es grande, incluso más que el primero. Pero, aún así, diría que es más probable que sea correcto.

-

Esto se debe a que la solución se expresa en un vocabulary que corresponde al problema que se está resolviendo. Sumar un rango de números no se trata de bucles y contadores. Se trata de rangos y sumas.

+

Esto se debe a que la solución se expresa en un vocabulario que corresponde al problema que se está resolviendo. Sumar un intervalo de números no va considerar bucles y contadores. Va de intervalos y sumas.

-

Las definiciones de este vocabulario (las funciones suma y rango) seguirán involucrando bucles, contadores y otros detalles incidentales. Pero debido a que expresan conceptos más simples que el programa en su totalidad, son más fáciles de hacer correctamente.

+

Las definiciones de este vocabulario (las funciones suma y rango) no dejan de consistir en trabajar con bucles, contadores y otros detalles. Pero debido a que expresan conceptos más simples que el programa en su totalidad, son más fáciles de hacer correctamente.

Abstracción

-

En el contexto de la programación, este tipo de vocabularios se suelen llamar abstractions. Las abstracciones nos brindan la capacidad de hablar sobre problemas a un nivel superior (o más abstracto), sin distraernos con detalles no interesantes.

+

En el contexto de la programación, este tipo de vocabularios se suelen llamar abstracciones. Las abstracciones nos brindan la capacidad de hablar sobre problemas a un nivel superior (o más abstracto), sin distraernos con detalles no interesantes.

Como analogía, compara estas dos recetas de sopa de guisantes. La primera es así:

-

_”Pon 1 taza de guisantes secos por persona en un recipiente. Agrega agua hasta que los guisantes estén bien cubiertos. Deja los guisantes en agua durante al menos 12 horas. Saca los guisantes del agua y ponlos en una olla. Agrega 4 tazas de agua por persona. Cubre la olla y deja que los guisantes hiervan a fuego lento durante dos horas. Toma media cebolla por persona. Córtala en trozos con un cuchillo. Agrégala a los guisantes. Toma un tallo de apio por persona. Córtalo en trozos con un cuchillo. Agrégalo a los guisantes. Toma una zanahoria por persona. ¡Córtala en trozos! ¡Con un cuchillo! Agrégala a los guisantes. Cocina durante 10 minutos más.”_Cita:

+
+ +

Pon 1 taza de guisantes secos por persona en un recipiente. Añade agua hasta que los guisantes estén bien cubiertos. Deja los guisantes en agua durante al menos 12 horas. Saca los guisantes del agua y ponlos en una olla. Agrega 4 tazas de agua por persona. Cubre la olla y deja los guisantes cociendo a fuego lento durante dos horas. Toma media cebolla por persona. Córtala en trozos con un cuchillo. Agrégala a los guisantes. Toma un tallo de apio por persona. Córtalo en trozos con un cuchillo. Agrégalo a los guisantes. Toma una zanahoria por persona. ¡Córtala en trozos! ¡Con un cuchillo! Agrégala a los guisantes. Cocina durante 10 minutos más.

+ +

Y esta es la segunda receta:

+
+

Por persona: 1 taza de guisantes partidos secos, 4 tazas de agua, media cebolla picada, un tallo de apio y una zanahoria.

Remoja los guisantes durante 12 horas. Cocina a fuego lento durante 2 horas. Pica y agrega las verduras. Cocina durante 10 minutos más.

-

El segundo es más corto y más fácil de interpretar. Pero necesitas entender algunas palabras más relacionadas con la cocina, como remojar, cocinar a fuego lento, picar, y, supongo, verdura.

+
-

Cuando se programa, no podemos depender de que todas las palabras que necesitamos estén esperándonos en el diccionario. Por lo tanto, podríamos caer en el patrón de la primera receta: trabajar en los pasos precisos que la computadora tiene que realizar, uno por uno, ciegos a los conceptos de más alto nivel que expresan.

+

La segunda es más corta y fácil de interpretar. Pero necesitas entender algunas palabras más relacionadas con la cocina, como remojar, cocinar a fuego lento, picar, y, supongo, verdura.

-

Abstraer la repetición

+

Cuando se programa, no podemos depender de que todas las palabras que necesitamos estén ya escritas en el diccionario para nosotros. Por lo tanto, podríamos caer en el patrón de la primera receta: ejecutar los pasos precisos que la computadora tiene que realizar, uno por uno, sin atender a los conceptos de más alto nivel que expresan.

-

Las funciones simples, como las hemos visto hasta ahora, son una buena manera de construir abstracciones. Pero a veces se quedan cortas.

+

Una habilidad útil en programación es darse cuenta de cuándo se está trabajando a un muy bajo nivel de abstracción.

-

Es común que un programa haga algo un número determinado de veces. Puedes escribir un for para eso, así:

+

Abstraer la repetición

+ +

Funciones simples como las hemos visto hasta ahora son una buena manera de construir abstracciones. Pero a veces se quedan cortas.

+ +

Es común que un programa haga algo una cantidad determinada de veces. Puedes escribir un for para eso, así:

for (let i = 0; i < 10; i++) {
   console.log(i);
@@ -69,17 +79,17 @@ 

¿Podemos abstraer “hacer algo N veces” como una función? Bueno, es fácil escribir una función que llame a console.log N veces:

-
function repeatLog(n) {
+
function repetirLog(n) {
   for (let i = 0; i < n; i++) {
     console.log(i);
   }
 }
-

¿Y si queremos hacer algo que no sea solo registrar los números? Dado que “hacer algo” se puede representar como una función y las funciones son solo valores, podemos pasar nuestra acción como un valor de función:

+

¿Y si queremos hacer algo que no sea solo pintar los los números? Dado que “hacer algo” se puede representar como una función y las funciones son solo valores, podemos pasar nuestra acción como un valor de función:

-
function repetir(n, action) {
+
function repetir(n, acción) {
   for (let i = 0; i < n; i++) {
-    action(i);
+    acción(i);
   }
 }
 
@@ -90,20 +100,23 @@ 

No tenemos que pasar una función predefinida a repetir. A menudo, es más fácil crear un valor de función en el momento:

-
let etiquetas = [];
-repetir(5, i => {
-  etiquetas.push(`Unidad ${i + 1}`);
+
let etiquetas = [];
+repetir(5, x => {
+  etiquetas.push(`Unidad ${x + 1}`);
 });
 console.log(etiquetas);
 // → ["Unidad 1", "Unidad 2", "Unidad 3", "Unidad 4", "Unidad 5"]
-

Esto está estructurado un poco como un for loop: primero describe el tipo de loop y luego proporciona un cuerpo. Sin embargo, el cuerpo ahora está escrito como un valor de función, que está envuelto entre los paréntesis de la llamada a repetir. Por eso tiene que cerrarse con el corchete de cierre y el paréntesis de cierre. En casos como este ejemplo donde el cuerpo es una sola expresión pequeña, también podrías omitir los corchetes y escribir el bucle en una sola línea.

+

N. del T.: Con respecto a la versión original del texto, se ha cambiado el nombre del parámetro en la función flecha de i a x para enfatizar la no necesidad de que el parámetro de dicha función se llame como el parámetro contador del bucle for de la implementación de la función repetir.

+
+ +

Esto está estructurado un poco como un bucle for: primero describe el tipo de bucle y luego proporciona un cuerpo. Sin embargo, el cuerpo ahora está escrito como un valor de función, que está envuelto entre los paréntesis de la llamada a repetir. Por eso tiene que cerrarse con el corchete de cierre y el paréntesis de cierre. En casos como este ejemplo donde el cuerpo es una sola expresión pequeña, también podrías omitir los corchetes y escribir el bucle en una sola línea.

-

Funciones de orden superior

+

Funciones de orden superior

-

Las funciones que operan en otras funciones, ya sea tomandolas como argumentos o devolviéndolas, se llaman funciones de orden superior. Dado que ya hemos visto que las funciones son valores regulares, no hay nada particularmente notable sobre el hecho de que existan tales funciones. El término proviene de las matemáticas, donde se toma más en serio la distinción entre funciones y otros valores.

+

Las funciones que operan sobre otras funciones, ya sea tomándolas como argumentos o devolviéndolas, se llaman funciones de orden superior. Dado que ya hemos visto que las funciones son valores como cualquier otro, no hay nada particularmente notable en el hecho de que existan tales funciones. El término proviene de las matemáticas, donde se toma más en serio la distinción entre funciones y otros valores.

-

Las funciones de orden superior nos permiten abstraer sobre acciones, no solo sobre valores. Vienen en varias formas. Por ejemplo, podemos tener funciones que crean nuevas funciones:

+

Las funciones de orden superior nos permiten abstraer acciones, no solo valores. Las hay de muchas formas. Por ejemplo, podemos tener funciones que crean nuevas funciones:

function mayorQue(n) {
   return m => m > n;
@@ -140,276 +153,276 @@ 

// → 0 es par // → 2 es par

-

Existe un método incorporado de arrays, forEach, que proporciona algo similar a un bucle for/of como una función de orden superior:

+

Existe un método ya incorporado en los arrays, forEach, que proporciona algo similar a un bucle for/of como una función de orden superior:

["A", "B"].forEach(l => console.log(l));
 // → A
 // → B
-

Conjunto de datos de script

+

Conjunto de datos de sistemas de escritura

-

Un área donde las funciones de orden superior destacan es en el procesamiento de datos. Para procesar datos, necesitaremos algunos ejemplos de datos reales. Este capítulo utilizará un conjunto de datos sobre scripts—sistemas de escritura tales como el latín, cirílico o árabe.

+

Un área donde las funciones de orden superior destacan es en el procesamiento de datos. Para procesar datos, vamos a necesitar algunos datos de ejemplo. Este capítulo utilizará un conjunto de datos sobre sistemas de escritura tales como el latín, cirílico o árabe.

-

¿Recuerdas Unicode del Capítulo 1, el sistema que asigna un número a cada carácter en lenguaje escrito? La mayoría de estos caracteres están asociados con un script específico. El estándar contiene 140 scripts diferentes, de los cuales 81 aún se utilizan hoy en día y 59 son históricos.

+

¿Recuerdas Unicode del Capítulo 1, el sistema que asigna un número a cada carácter en lenguaje escrito? La mayoría de estos caracteres están asociados con un sistema de escritura concreto. El estándar contiene 140 sistemas diferentes, de los cuales 81 aún se utilizan hoy en día y 59 son históricos.

Aunque solo puedo leer con fluidez caracteres latinos, aprecio el hecho de que las personas estén escribiendo textos en al menos otros 80 sistemas de escritura, muchos de los cuales ni siquiera reconocería. Por ejemplo, aquí tienes una muestra de escritura Tamil:

Una línea de verso en escritura Tamil. Los caracteres son relativamente simples y separados ordenadamente, pero completamente diferentes de los caracteres latinos.
-

El ejemplo del conjunto de datos contiene algunas piezas de información sobre los 140 scripts definidos en Unicode. Está disponible en el sandbox de código para este capítulo como el enlace SCRIPTS. El enlace contiene un array de objetos, cada uno describe un script:

+

El conjunto de datos de ejemplo contiene información sobre los 140 sistemas de escritura definidos en Unicode. Está disponible en el sandbox de código para este capítulo como la asociación de nombre SCRIPTS. La variable contiene un array de objetos, cada uno describiendo un sistema de escritura:

-
{
-  name: "Copto",
-  rangos: [[994, 1008], [11392, 11508], [11513, 11520]],
-  dirección: "ltr",
-  año: -200,
-  vivo: false,
-  enlace: "https://es.wikipedia.org/wiki/Alfabeto_copto"
+
{
+  name: "Coptic",
+  ranges: [[994, 1008], [11392, 11508], [11513, 11520]],
+  direction: "ltr",
+  year: -200,
+  living: false,
+  link: "https://en.wikipedia.org/wiki/Coptic_alphabet"
 }
-

Tal objeto nos informa sobre el nombre del script, los rangos Unicode asignados a él, la dirección en la que se escribe, el tiempo de origen (aproximado), si todavía se utiliza, y un enlace a más información. La dirección puede ser "ltr" para izquierda a derecha, "rtl" para derecha a izquierda (como se escribe el texto en árabe y hebreo) o "ttb" para arriba hacia abajo (como en la escritura mongola).

+

Tal objeto nos informa sobre el nombre del sistema de lenguaje, los rangos Unicode asignados a él, la dirección en la que se escribe, el momento de origen (aproximado), si todavía se utiliza, y un enlace a más información. La dirección puede ser "ltr" para izquierda a derecha, "rtl" para derecha a izquierda (como se escribe el texto en árabe y hebreo) o "ttb" para arriba hacia abajo (como en la escritura mongola).

-

La propiedad ranges contiene una matriz de rangos de caracteres Unicode, cada uno de los cuales es una matriz de dos elementos que contiene un límite inferior y un límite superior. Todos los códigos de caracteres dentro de estos rangos se asignan al guion. El límite inferior es inclusivo (el código 994 es un carácter copto) y el límite superior no es inclusivo (el código 1008 no lo es).

+

La propiedad ranges contiene un array de rangos de caracteres Unicode, cada uno de los cuales es un array de dos elementos que contiene un límite inferior y un límite superior. Todos los códigos de caracteres dentro de estos rangos se asignan al sistema de escritura en cuestión. El límite inferior es inclusivo (el código 994 es un carácter copto) y el límite superior es no inclusivo (el código 1008 no lo es).

Filtrado de arrays

-

Si queremos encontrar los guiones en el conjunto de datos que todavía se utilizan, la siguiente función puede ser útil. Filtra los elementos de una matriz que no pasan una prueba.

+

Si queremos encontrar en el conjunto de datos qué sistemas de escritura todavía se utilizan, la siguiente función puede ser útil. Deja fuera los elementos de un array que no cumplen una cierta comprobación.

-
function filter(array, test) {
-  let passed = [];
-  for (let element of array) {
-    if (test(element)) {
-      passed.push(element);
+
function filtrar(array, comprobación) {
+  let pasada = [];
+  for (let elemento of array) {
+    if (comprobación(elemento)) {
+      pasada.push(elemento);
     }
   }
-  return passed;
+  return pasada;
 }
 
-console.log(filter(SCRIPTS, script => script.living));
+console.log(filtrar(SCRIPTS, sistema => sistema.living));
 // → [{name: "Adlam", …}, …]
-

La función utiliza el argumento llamado test, un valor de función, para llenar un “vacío” en la computación, el proceso de decidir qué elementos recopilar.

+

La función utiliza el argumento llamado comprobación, un valor de función, para llenar un “hueco” en el procedimiento de filtrado: el proceso de decidir qué elementos recopilar.

-

Observa cómo la función filter, en lugar de eliminar elementos de la matriz existente, construye una nueva matriz con solo los elementos que pasan la prueba. Esta función es pura. No modifica la matriz que se le pasa.

+

Observa cómo la función filtrar, en lugar de eliminar elementos de la matriz existente, construye una nueva matriz con solo los elementos que pasan la prueba. Esta función es pura. No modifica la matriz que se le pasa.

-

Al igual que forEach, filter es un método de matriz estándar. El ejemplo definió la función solo para mostrar qué hace internamente. De ahora en adelante, lo usaremos de esta manera en su lugar:

+

Al igual que con forEach, hay un método estándar para filtrar en los arrays, el método filter. En el ejemplo se define la función solo para mostrar qué hace internamente. De ahora en adelante, lo usaremos de esta manera en su lugar:

console.log(SCRIPTS.filter(s => s.direction == "ttb"));
 // → [{name: "Mongolian", …}, …]

Transformación con map

-

Digamos que tenemos una matriz de objetos que representan guiones, producida al filtrar la matriz SCRIPTS de alguna manera. Queremos una matriz de nombres en su lugar, que es más fácil de inspeccionar.

+

Digamos que tenemos un array de objetos que representan sistemas de escritura, producido al filtrar el array SCRIPTS de alguna manera. En su lugar, queremos un array de nombres, que es más fácil de inspeccionar.

-

El método map transforma una matriz aplicando una función a todos sus elementos y construyendo una nueva matriz a partir de los valores devueltos. La nueva matriz tendrá la misma longitud que la matriz de entrada, pero su contenido habrá sido mapeado a una nueva forma por la función:

+

El método map transforma un array aplicando una función a todos sus elementos y construyendo un nuevo array a partir de los valores devueltos. El nuevo array tendrá la misma longitud que el de entrada, pero su contenido habrá sido mapeado a una nueva forma por la función:

-
function map(array, transform) {
-  let mapped = [];
-  for (let element of array) {
-    mapped.push(transform(element));
+
function mapear(array, transformación) {
+  let mapeados = [];
+  for (let elemento of array) {
+    mapeados.push(transformación(elemento));
   }
-  return mapped;
+  return mapeados;
 }
 
 let rtlScripts = SCRIPTS.filter(s => s.direction == "rtl");
-console.log(map(rtlScripts, s => s.name));
+console.log(mapear(rtlScripts, s => s.name));
 // → ["Adlam", "Arabic", "Imperial Aramaic", …]
-

Al igual que forEach y filter, map es un método de matriz estándar.

+

Al igual que forEach y filter, hay un método estándar para mapear en los arrays, el método map.

-

Resumen con reduce

+

Resumiendo con reduce

-

Otra cosa común que hacer con matrices es calcular un único valor a partir de ellas. Nuestro ejemplo recurrente, sumar una colección de números, es una instancia de esto. Otro ejemplo es encontrar el guion con más caracteres.

+

Otra cosa común que hacer con arrays es calcular un único valor a partir de ellos. Nuestro ejemplo de siempre, sumar una colección de números, es una ejemplo de esto. Otro ejemplo es encontrar el sistema de escritura con más caracteres.

-

La operación de orden superior que representa este patrón se llama reduce (a veces también llamada fold). Construye un valor tomando repetidamente un único elemento del array y combinándolo con el valor actual. Al sumar números, comenzarías con el número cero y, para cada elemento, lo sumarías al total.

+

La operación de orden superior que representa esta idea se llama reduce (a veces también llamada fold). Construye un valor tomando repetidamente un único elemento del array y combinándolo con el valor actual. Al sumar números empezarías con el número cero y añadirías cada elemento a la suma.

-

Los parámetros de reduce son, además del array, una función de combinación y un valor inicial. Esta función es un poco menos directa que filter y map, así que obsérvala detenidamente:

+

Los parámetros de reduce son, además del array, una función de combinación y un valor inicial. Esta función es un poco menos directa que filter y map, así que obsérva detenidamente:

-
function reduce(array, combine, start) {
-  let current = start;
-  for (let element of array) {
-    current = combine(current, element);
+
function reducir(array, combinación, principio) {
+  let actual = inicio;
+  for (let elemento of array) {
+    actual = combinación(actual, elemento);
   }
-  return current;
+  return actual;
 }
 
-console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0));
+console.log(reducir([1, 2, 3, 4], (a, b) => a + b, 0));
 // → 10
-

El método estándar de arrays reduce, que por supuesto corresponde a esta función, tiene una conveniencia adicional. Si tu array contiene al menos un elemento, puedes omitir el argumento start. El método tomará el primer elemento del array como su valor inicial y comenzará a reducir en el segundo elemento.

+

El método estándar de arrays, reduce —que por supuesto corresponde a esta función— tiene una ventaja adicional. Si tu array contiene al menos un elemento, puedes omitir el argumento start. El método tomará el primer elemento del array como su valor inicial y comenzará a reducir en el segundo elemento.

console.log([1, 2, 3, 4].reduce((a, b) => a + b));
 // → 10
-

Para usar reduce (dos veces) y encontrar el script con más caracteres, podemos escribir algo así:

+

Para usar reduce (dos veces) y encontrar el sistema de escritura con más caracteres, podemos escribir algo así:

-
function characterCount(script) {
-  return script.ranges.reduce((count, [from, to]) => {
-    return count + (to - from);
+
function contarCaracteres(sistema) {
+  return sistema.ranges.reduce((contador, [desde, hasta]) => {
+    return contador + (hasta - desde);
   }, 0);
 }
 
 console.log(SCRIPTS.reduce((a, b) => {
-  return characterCount(a) < characterCount(b) ? b : a;
+  return contarCaracteres(a) < contarCaracteres(b) ? b : a;
 }));
 // → {name: "Han", …}
-

La función characterCount reduce los rangos asignados a un script sumando sus tamaños. Observa el uso de la desestructuración en la lista de parámetros de la función reductora. La segunda llamada a reduce luego utiliza esto para encontrar el script más grande comparando repetidamente dos scripts y devolviendo el más grande.

+

La función contarCaracteres reduce los rangos asignados a un sistema de escritura sumando sus tamaños. Observa el uso de la desestructuración en la lista de parámetros de la función reductora. La segunda llamada a reduce luego utiliza esto para encontrar el sistema de escritura más grande comparando repetidamente dos sistemas y devolviendo el más grande.

-

El script Han tiene más de 89,000 caracteres asignados en el estándar Unicode, convirtiéndolo en el sistema de escritura más grande en el conjunto de datos. Han es un script a veces utilizado para texto en chino, japonés y coreano. Esos idiomas comparten muchos caracteres, aunque tienden a escribirlos de manera diferente. El Consorcio Unicode (con sede en EE. UU.) decidió tratarlos como un único sistema de escritura para ahorrar códigos de caracteres. Esto se llama unificación Han y todavía molesta a algunas personas.

+

El sistema de escritura Han (es decir, el sistema de escritura chino actual) tiene más de 89000 caracteres asignados en el estándar Unicode, convirtiéndolo en el sistema de escritura más grande del conjunto de datos. El sistema Han es un sistema a veces utilizado para texto en chino, japonés y coreano. Estos idiomas comparten muchos caracteres, aunque tienden a escribirlos de manera diferente. El Consorcio Unicode (con sede en EE. UU.) decidió tratarlos como un único sistema de escritura para ahorrar códigos de caracteres. Esto se llama unificación Han y aún hay gente que no está muy contenta con ella.

Composabilidad

-

Considera cómo hubiéramos escrito el ejemplo anterior (encontrando el script más grande) sin funciones de orden superior. El código no es mucho peor:

+

Considera cómo hubiéramos escrito el ejemplo anterior (encontrar el sistema más grande) sin funciones de orden superior. El código no es tan inferior al anterior.

-
let biggest = null;
-for (let script of SCRIPTS) {
-  if (biggest == null ||
-      characterCount(biggest) < characterCount(script)) {
-    biggest = script;
+
let másGrande = null;
+for (let sistema of SCRIPTS) {
+  if (másGrande == null ||
+      contarCaracteres(másGrande) < contarCaracteres(sistema)) {
+    másGrande = sistema;
   }
 }
-console.log(biggest);
+console.log(másGrande);
 // → {name: "Han", …}
-

Hay algunas variables adicionales y el programa tiene cuatro líneas más, pero sigue siendo muy legible.

+

Hay algunas variables más y el programa tiene cuatro líneas más, pero sigue siendo muy legible.

-

Las abstracciones proporcionadas por estas funciones brillan realmente cuando necesitas componer operaciones. Como ejemplo, escribamos un código que encuentre el año promedio de origen para scripts vivos y muertos en el conjunto de datos:

+

Las abstracciones proporcionadas por estas funciones brillan realmente cuando necesitas componer operaciones. Como ejemplo, escribamos un código que encuentre el año promedio de origen para sistemas vivos y muertos en el conjunto de datos:

-
function average(array) {
+
function promedio(array) {
   return array.reduce((a, b) => a + b) / array.length;
 }
 
-console.log(Math.round(average(
+console.log(Math.round(promedio(
   SCRIPTS.filter(s => s.living).map(s => s.year))));
 // → 1165
-console.log(Math.round(average(
+console.log(Math.round(promedio(
   SCRIPTS.filter(s => !s.living).map(s => s.year))));
 // → 204
-

Como puedes ver, los scripts muertos en Unicode son, en promedio, más antiguos que los vivos. Esta no es una estadística muy significativa o sorprendente. Pero espero que estés de acuerdo en que el código utilizado para calcularlo no es difícil de leer. Puedes verlo como un pipeline: empezamos con todos los scripts, filtramos los vivos (o muertos), tomamos los años de esos scripts, calculamos el promedio y redondeamos el resultado.

+

Como puedes ver, los sistemas de escritura muertos en Unicode son, en promedio, más antiguos que los vivos. Esta no es una estadística muy significativa o sorprendente. Pero espero que estés de acuerdo en que el código utilizado para calcularlo no es difícil de leer. Puedes verlo como una cadena de procesos (pipeline): empezamos con todos los sistemas, filtramos los vivos (o muertos), tomamos los años de esos sistemas, calculamos el promedio y redondeamos el resultado.

-

Definitivamente también podrías escribir este cálculo como un único loop grande:

+

Definitivamente también podrías escribir este cálculo como un único bucle grande:

-
let total = 0, count = 0;
-for (let script of SCRIPTS) {
-  if (script.living) {
-    total += script.year;
-    count += 1;
+
let total = 0, contador = 0;
+for (let sistema of SCRIPTS) {
+  if (sistema.living) {
+    total += sistema.year;
+    contador += 1;
   }
 }
-console.log(Math.round(total / count));
+console.log(Math.round(total / contador));
 // → 1165
-

Sin embargo, es más difícil ver qué se estaba calculando y cómo. Y debido a que los resultados intermedios no se representan como valores coherentes, sería mucho más trabajo extraer algo como average en una función separada.

+

Sin embargo, es más difícil ver qué se estaba calculando y cómo. Y como los resultados intermedios no se representan como valores coherentes, sería mucho más trabajo extraer algo como el promedio en una función separada.

-

En términos de lo que realmente está haciendo la computadora, estos dos enfoques también son bastante diferentes. El primero construirá nuevos arrays al ejecutar filter y map, mientras que el segundo calcula solo algunos números, haciendo menos trabajo. Por lo general, puedes permitirte el enfoque legible, pero si estás procesando matrices enormes y haciéndolo muchas veces, el estilo menos abstracto podría valer la pena por la velocidad adicional.

+

En términos de lo que realmente está haciendo la computadora, estos dos enfoques también son bastante distintos. El primero construirá nuevos arrays al ejecutar filter y map, mientras que el segundo calcula solo algunos números, haciendo menos trabajo. Por lo general, puedes permitirte el enfoque legible, pero si estás procesando arrays enormes y haciéndolo muchas veces, un estilo menos abstracto podría valer la pena a cambio de velocidad adicional.

Cadenas y códigos de caracteres

-

Un uso interesante de este conjunto de datos sería averiguar qué script está utilizando un fragmento de texto. Vamos a través de un programa que hace esto.

+

Un uso interesante de este conjunto de datos sería averiguar qué sistema de escritura está utilizando un fragmento de texto. Veamos un programa que hace esto.

-

Recuerda que cada script tiene asociado un array de intervalos de códigos de caracteres. Dado un código de carácter, podríamos usar una función como esta para encontrar el script correspondiente (si lo hay):

+

Recuerda que cada sistema de escritura tiene asociado un array de intervalos de códigos de caracteres. Dado un código de carácter, podríamos usar una función como esta para encontrar el sistema correspondiente (si lo hay):

-
function characterScript(code) {
-  for (let script of SCRIPTS) {
-    if (script.ranges.some(([from, to]) => {
-      return code >= from && code < to;
+
function sistemaCaracteres(código) {
+  for (let sistema of SCRIPTS) {
+    if (sistema.ranges.some(([desde, hasta]) => {
+      return código >= desde && código < hasta;
     })) {
-      return script;
+      return sistema;
     }
   }
   return null;
 }
 
-console.log(characterScript(121));
+console.log(sistemaCaracteres(121));
 // → {name: "Latin", …}
-

El método some es otra función de orden superior. Toma una función de prueba y te dice si esa función devuelve true para alguno de los elementos en el array.

+

El método some es otra función de orden superior. Toma una función de comprobación y te dice si esa función devuelve true para alguno de los elementos en el array.

Pero, ¿cómo obtenemos los códigos de caracteres en una cadena?

-

En Chapter 1 mencioné que las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estos se llaman unidades de código. Un código de carácter Unicode inicialmente se suponía que cabía dentro de tal unidad (lo que te da un poco más de 65,000 caracteres). Cuando quedó claro que eso no iba a ser suficiente, muchas personas se mostraron reacias a la necesidad de usar más memoria por carácter. Para abordar estas preocupaciones, se inventó UTF-16, el formato también utilizado por las cadenas de JavaScript. Describe la mayoría de los caracteres comunes usando una única unidad de código de 16 bits, pero usa un par de dos unidades de dicho tipo para otros.

+

En el Capítulo 1 mencioné que las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estos se llaman unidades de código. Al principio, se suponía que un código de caracter Unicode cabía dentro de tal unidad (lo que te da algo más de 65000 caracteres). Cuando quedó claro que eso no iba a ser suficiente, mucha gente se mostró reacia a la necesidad de usar más memoria por caracter. Para abordar estas preocupaciones, se inventó UTF-16, el formato que usan las cadenas de JavaScript. Describe la mayoría de los caracteres comunes usando una única unidad de código de 16 bits, pero usa un par de dos unidades de dicho tipo para otros.

-

UTF-16 generalmente se considera una mala idea hoy en día. Parece casi diseñado intencionalmente para invitar a errores. Es fácil escribir programas que pretendan que las unidades de código y los caracteres son lo mismo. Y si tu lenguaje no utiliza caracteres de dos unidades, eso parecerá funcionar perfectamente. Pero tan pronto como alguien intente usar dicho programa con algunos caracteres chinos menos comunes, fallará. Afortunadamente, con la llegada de los emoji, todo el mundo ha comenzado a usar caracteres de dos unidades, y la carga de tratar con tales problemas está más equitativamente distribuida.

+

UTF-16 generalmente se considera una mala idea hoy en día. Parece casi diseñado intencionalmente para provocar errores. Es fácil escribir programas que asuman que las unidades de código y los caracteres son lo mismo. Y si tu lenguaje no utiliza caracteres de dos unidades, eso parecerá funcionar perfectamente. Pero tan pronto como alguien intente usar dicho programa con algunos caracteres menos comunes como los chinos, fallará. Por suerte, con la llegada de los emoji, todo el mundo ha comenzado a usar caracteres de dos unidades, y tratar con tales problemas se está haciendo más llevadero.

Lamentablemente, las operaciones obvias en las cadenas de JavaScript, como obtener su longitud a través de la propiedad length y acceder a su contenido usando corchetes cuadrados, tratan solo con unidades de código.

-
// Dos caracteres emoji, caballo y zapato
-let horseShoe = "🐴👟";
-console.log(horseShoe.length);
+
// Dos caracteres emoji, caballo y zapato
+let caballoZapato = "🐴👟";
+console.log(caballoZapato.length);
 // → 4
-console.log(horseShoe[0]);
-// → (Mitad de carácter inválida)
-console.log(horseShoe.charCodeAt(0));
-// → 55357 (Código de la mitad de carácter)
-console.log(horseShoe.codePointAt(0));
+console.log(caballoZapato[0]);
+// → (Mitad de caracter inválida)
+console.log(caballoZapato.charCodeAt(0));
+// → 55357 (Código de la mitad de caracter)
+console.log(caballoZapato.codePointAt(0));
 // → 128052 (Código real para el emoji de caballo)

El método charCodeAt de JavaScript te da una unidad de código, no un código de carácter completo. El método codePointAt, añadido más tarde, sí da un carácter Unicode completo, por lo que podríamos usarlo para obtener caracteres de una cadena. Pero el argumento pasado a codePointAt sigue siendo un índice en la secuencia de unidades de código. Para recorrer todos los caracteres en una cadena, aún necesitaríamos abordar la cuestión de si un carácter ocupa una o dos unidades de código.

En el capítulo anterior, mencioné que un bucle for/of también se puede usar en cadenas. Al igual que codePointAt, este tipo de bucle se introdujo en un momento en que la gente era muy consciente de los problemas con UTF-16. Cuando lo usas para recorrer una cadena, te proporciona caracteres reales, no unidades de código:

-
let roseDragon = "🌹🐉";
-for (let char of roseDragon) {
-  console.log(char);
+
let rosaDragón = "🌹🐉";
+for (let caracter of rosaDragón) {
+  console.log(caracter);
 }
 // → 🌹
 // → 🐉
-

Si tienes un carácter (que será una cadena de una o dos unidades de código), puedes usar codePointAt(0) para obtener su código.

+

Si tienes un caracter (que será una cadena de una o dos unidades de código), puedes usar codePointAt(0) para obtener su código.

Reconociendo texto

-

Tenemos una función characterScript y una forma de recorrer correctamente los caracteres. El próximo paso es contar los caracteres que pertenecen a cada script. La siguiente abstracción de conteo será útil para eso:

+

Tenemos una función sistemaCaracteres y una forma de recorrer correctamente los caracteres. El próximo paso es contar los caracteres que pertenecen a cada sistema de escritura. La siguiente abstracción de recuento será útil para eso:

-
function countBy(items, groupName) {
-  let counts = [];
+
function contarPor(items, nombreGrupo) {
+  let recuentos = [];
   for (let item of items) {
-    let name = groupName(item);
-    let known = counts.find(c => c.name == name);
-    if (!known) {
-      counts.push({name, count: 1});
+    let nombre = nombreGrupo(item);
+    let conocido = recuentos.find(c => c.nombre == nombre);
+    if (!conocido) {
+      recuentos.push({nombre, recuento: 1});
     } else {
-      known.count++;
+      conocido.recuento++;
     }
   }
-  return counts;
+  return recuentos;
 }
 
-console.log(countBy([1, 2, 3, 4, 5], n => n > 2));
-// → [{name: false, count: 2}, {name: true, count: 3}]
+console.log(contarPor([1, 2, 3, 4, 5], n => n > 2)); +// → [{nombre: false, recuento: 2}, {nombre: true, recuento: 3}]
-

La función countBy espera una colección (cualquier cosa por la que podamos iterar con for/of) y una función que calcule un nombre de grupo para un elemento dado. Devuelve una matriz de objetos, cada uno de los cuales nombra un grupo y te dice el número de elementos que se encontraron en ese grupo.

+

La función contarPor espera una colección (cualquier cosa por la que podamos iterar con for/of) y una función que calcule un nombre de grupo para un elemento dado. Devuelve una matriz de objetos, cada uno de los cuales nombra un grupo y te dice el número de elementos que se encontraron en ese grupo.

Utiliza otro método de array, find, que recorre los elementos en el array y devuelve el primero para el cual una función devuelve true. Devuelve undefined cuando no se encuentra dicho elemento.

-

Usando countBy, podemos escribir la función que nos dice qué scripts se utilizan en un fragmento de texto:

+

Usando contarPor, podemos escribir la función que nos dice qué sistemas de escritura se utilizan en un fragmento de texto:

-
function textScripts(text) {
-  let scripts = countBy(text, char => {
-    let script = characterScript(char.codePointAt(0));
-    return script ? script.name : "ninguno";
-  }).filter(({name}) => name != "ninguno");
+
function sistemasTexto(texto) {
+  let sistemas = contarPor(texto, caracter => {
+    let sistema = sistemaCaracteres(caracter.codePointAt(0));
+    return sistema ? sistema.name : "ninguno";
+  }).filter(({nombre}) => nombre != "ninguno");
 
-  let total = scripts.reduce((n, {count}) => n + count, 0);
-  if (total == 0) return "No se encontraron scripts";
+  let total = sistemas.reduce((n, {recuento}) => n + recuento, 0);
+  if (total == 0) return "No se encontraron sistemas";
 
-  return scripts.map(({name, count}) => {
-    return `${Math.round(count * 100 / total)}% ${name}`;
+  return sistemas.map(({nombre, recuento}) => {
+    return `${Math.round(recuento * 100 / total)}% ${nombre}`;
   }).join(", ");
 }
 
-console.log(textScripts('英国的狗说"woof", 俄罗斯的狗说"тяв"'));
+console.log(sistemasTexto('英国的狗说"woof", 俄罗斯的狗说"тяв"'));
 // → 61% Han, 22% Latin, 17% Cyrillic
-

La función primero cuenta los caracteres por nombre, usando characterScript para asignarles un nombre y retrocediendo a la cadena "ninguno" para los caracteres que no forman parte de ningún script. La llamada a filter elimina la entrada de "ninguno" del array resultante, ya que no nos interesan esos caracteres.

+

La función primero recoge los nombres de los sistemas de escritura de los caracteres en el texto usando sistemaCaracteres para asignarles un nombre y recurriendo a la cadena "ninguno" para los caracteres que no forman parte de ningún sistema. La llamada a filter elimina la entrada correspondiente a "ninguno" del array resultante, ya que no nos interesan esos caracteres.

-

Para poder calcular porcentajes, primero necesitamos el número total de caracteres que pertenecen a un script, lo cual podemos calcular con reduce. Si no se encuentran dichos caracteres, la función devuelve una cadena específica. De lo contrario, transforma las entradas de conteo en cadenas legibles con map y luego las combina con join.

+

Para poder calcular porcentajes, primero necesitamos el número total de caracteres que pertenecen a un sistema dado, lo cual podemos calcular con reduce. Si no se encuentran dichos caracteres, la función devuelve una cadena específica. De lo contrario, transforma las entradas de conteo en cadenas legibles con map y luego las combina con join.

Resumen

-

Poder pasar valores de funciones a otras funciones es un aspecto muy útil de JavaScript. Nos permite escribir funciones que modelan cálculos con “vacíos”. El código que llama a estas funciones puede llenar los vacíos proporcionando valores de funciones.

+

Poder pasar valores de funciones a otras funciones es un aspecto muy útil de JavaScript. Nos permite escribir funciones que modelan cálculos con “huecos a rellenar” en ellas. El código que llama a estas funciones puede llenar los huecos proporcionando valores de funciones.

-

Los arrays proporcionan diversos métodos de orden superior útiles. Puedes usar forEach para recorrer los elementos de un array. El método filter devuelve un nuevo array que contiene solo los elementos que pasan la función de predicado. Transformar un array poniendo cada elemento en una función se hace con map. Puedes usar reduce para combinar todos los elementos de un array en un único valor. El método some comprueba si algún elemento coincide con una función de predicado dada, mientras que find encuentra el primer elemento que coincide con un predicado.

+

Los arrays proporcionan diversos métodos de orden superior muy útiles. Puedes usar forEach para recorrer los elementos de un array. El método filter devuelve un nuevo array que contiene solo los elementos que pasan la función de predicado. Transformar un array poniendo cada elemento en una función se hace con map. Puedes usar reduce para combinar todos los elementos de un array en un único valor. El método some comprueba si algún elemento satisface una función de predicado dada, mientras que find encuentra el primer elemento que satisface un predicado.

Ejercicios

@@ -423,9 +436,9 @@

Tu propio bucle

-

Escribe una función de orden superior loop que proporcione algo similar a una declaración for loop. Debería recibir un valor, una función de prueba, una función de actualización y una función de cuerpo. En cada iteración, primero debe ejecutar la función de prueba en el valor actual del bucle y detenerse si devuelve falso. Luego debe llamar a la función de cuerpo, dándole el valor actual, y finalmente llamar a la función de actualización para crear un nuevo valor y empezar de nuevo desde el principio.

+

Escribe una función de orden superior loop que proporcione algo similar a una declaración de bucle for. Debería recibir un valor, una función de comprobación, una función de actualización y una función de cuerpo. En cada iteración, primero debe ejecutar la función de comprobación en el valor actual del bucle y detenerse si devuelve falso. Luego debe llamar a la función de cuerpo, pasándole el valor actual, y finalmente llamar a la función de actualización para crear un nuevo valor y empezar de nuevo desde el principio.

-

Al definir la función, puedes usar un bucle regular para hacer el bucle real.

+

Al definir la función, puedes usar un bucle normal para hacer el bucle real.

// Your code here.
 
@@ -436,7 +449,7 @@ 

Everything

-

Los arrays también tienen un método every análogo al método some. Este método devuelve true cuando la función dada devuelve true para cada elemento en el array. En cierto modo, some es una versión del operador || que actúa en arrays, y every es como el operador &&.

+

Los arrays también tienen un método every análogo al método some. Este método devuelve true cuando la función dada devuelve true para todo elemento en el array. En cierto modo, some es una versión del operador || que actúa en arrays, y every es como el operador &&.

Implementa every como una función que recibe un array y una función de predicado como parámetros. Escribe dos versiones, una usando un bucle y otra usando el método some.

@@ -453,15 +466,15 @@

Mostrar pistas...
-

Como el operador &&, el método every puede dejar de evaluar más elementos tan pronto como encuentre uno que no coincida. Por lo tanto, la versión basada en bucle puede salir del bucle—con break o `return—tan pronto como encuentre un elemento para el que la función de predicado devuelva false. Si el bucle se ejecuta hasta el final sin encontrar dicho elemento, sabemos que todos los elementos coincidieron y deberíamos devolver true.

+

Al igual que el operador &&, el método every puede dejar de evaluar más elementos tan pronto como encuentre uno que no coincida. Por lo tanto, la versión basada en un bucle puede salir del bucle —con break o return— tan pronto como encuentre un elemento para el que la función de predicado devuelva false. Si el bucle se ejecuta hasta el final sin encontrar dicho elemento, sabemos que todos los elementos coincidieron y deberíamos devolver true.

-

Para construir every sobre some, podemos aplicar leyes de De Morgan, que establecen que a && b es igual a !(!a || !b). Esto se puede generalizar a arrays, donde todos los elementos en el array coinciden si no hay ningún elemento en el array que no coincida.

+

Para construir every sobre some, podemos aplicar leyes de De Morgan, que establecen que a && b tiene el mismo valor que !(!a || !b). Esto se puede generalizar a arrays, donde todos los elementos en el array coinciden si no hay ningún elemento en el array que no coincida.

Dirección de escritura dominante

-

Escribe una función que calcule la dirección de escritura dominante en una cadena de texto. Recuerda que cada objeto script tiene una propiedad direction que puede ser "ltr" (de izquierda a derecha), "rtl" (de derecha a izquierda) o "ttb" (de arriba a abajo).

+

Escribe una función que calcule la dirección de escritura dominante en una cadena de texto. Recuerda que cada objeto de sistema de escritura tiene una propiedad direction que puede ser "ltr" (de izquierda a derecha), "rtl" (de derecha a izquierda) o "ttb" (de arriba a abajo).

function dominantDirection(text) {
   // Your code here.
@@ -474,9 +487,9 @@ 

Mostrar pistas...
-

Tu solución podría parecerse mucho a la primera mitad del ejemplo de textScripts. De nuevo, debes contar caracteres según un criterio basado en characterScript y luego filtrar la parte del resultado que se refiere a caracteres no interesantes (sin script).

+

Tu solución podría parecerse mucho a la primera mitad del ejemplo de sistemasTexto. De nuevo, debes contar caracteres según un criterio basado en sistemaCaracteres y luego filtrar la parte del resultado que se refiere a caracteres no interesantes (sin sistema asociado).

-

Encontrar la dirección con el recuento de caracteres más alto se puede hacer con reduce. Si no está claro cómo hacerlo, consulta el ejemplo anterior en el capítulo, donde se usó reduce para encontrar el script con más caracteres.

+

Encontrar la dirección con el recuento de caracteres más alto es algo que se puede hacer con reduce. Si no está claro cómo hacerlo, consulta el ejemplo que vimos antes en el capítulo, donde se usó reduce para encontrar el script con más caracteres.

From 3fc061a148d0c80065facd8c791bf58dfea1641e Mon Sep 17 00:00:00 2001 From: ckdvk Date: Thu, 13 Feb 2025 18:55:18 +0800 Subject: [PATCH 16/36] =?UTF-8?q?revisado=20cap=C3=ADtulo=206=20a=20falta?= =?UTF-8?q?=20de=20resumen=20y=20ejercicios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 06_object.md | 414 +++++++++++++++++++++---------------------- html/06_object.html | 419 ++++++++++++++++++++++---------------------- 2 files changed, 419 insertions(+), 414 deletions(-) diff --git a/06_object.md b/06_object.md index 5f1a8fc7..1e2f7c4e 100644 --- a/06_object.md +++ b/06_object.md @@ -2,9 +2,9 @@ # La Vida Secreta de los Objetos -{{quote {author: "Barbara Liskov", title: "Programando con Tipos de Datos Abstractos", chapter: true} +{{quote {author: "Barbara Liskov", title: "Programming with Abstract Data Types", chapter: true} -Un tipo de dato abstracto se realiza escribiendo un tipo especial de programa [...] que define el tipo en términos de las operaciones que se pueden realizar en él. +Un tipo abstracto de datos se implementa escribiendo un tipo especial de programa [...] que define el tipo en función de las operaciones que se pueden realizar sobre él.. quote}} @@ -12,32 +12,30 @@ quote}} {{figure {url: "img/chapter_picture_6.jpg", alt: "Ilustración de un conejo junto a su prototipo, una representación esquemática de un conejo", chapter: framed}}} -[El Capítulo ?](data) introdujo los objetos de JavaScript, como contenedores que almacenan otros datos. - -En la cultura de la programación, tenemos algo llamado _((programación orientada a objetos))_, un conjunto de técnicas que utilizan objetos como principio central de la organización de programas. Aunque nadie realmente se pone de acuerdo en su definición precisa, la programación orientada a objetos ha dado forma al diseño de muchos lenguajes de programación, incluido JavaScript. Este capítulo describe la forma en que estas ideas se pueden aplicar en JavaScript. +En [el Capítulo ?](data) se introdujeron los objetos de JavaScript como contenedores que almacenan otros datos. En la cultura de la programación, la _((programación orientada a objetos))_ es un conjunto de técnicas que utilizan objetos como el principio central de la organización de programas. Aunque nadie realmente se pone de acuerdo en su definición precisa, la programación orientada a objetos ha dado forma al diseño de muchos lenguajes de programación, incluido JavaScript. Este capítulo describe la forma en que estas ideas se pueden aplicar en JavaScript. ## Tipos de Datos Abstractos {{index "tipo de dato abstracto", tipo, "ejemplo de batidora"}} -La idea principal en la programación orientada a objetos es utilizar objetos, o más bien _tipos_ de objetos, como la unidad de organización del programa. Configurar un programa como una serie de tipos de objetos estrictamente separados proporciona una forma de pensar en su estructura y, por lo tanto, de imponer algún tipo de disciplina para evitar que todo se entrelace. +La idea principal en la programación orientada a objetos es utilizar objetos (más bien _tipos_ de objetos) como la unidad de organización del programa. Configurar un programa como una serie de tipos de objetos estrictamente separados proporciona una forma de pensar en su estructura y, por lo tanto, de imponer algún tipo de disciplina, evitando que todo se convierta en un lío. -La forma de hacer esto es pensar en objetos de alguna manera similar a como pensarías en una batidora eléctrica u otro ((electrodoméstico)) para el consumidor. Hay personas que diseñaron y ensamblaron una batidora, y tienen que realizar un trabajo especializado que requiere ciencia de materiales y comprensión de la electricidad. Cubren todo eso con una carcasa de plástico suave, de modo que las personas que solo quieren mezclar masa para panqueques no tengan que preocuparse por todo eso, solo tienen que entender los pocos botones con los que se puede operar la batidora. +La forma de hacer esto es pensar en los objetos de alguna manera similar a como pensarías en una batidora eléctrica u otro ((electrodoméstico)). Las personas que diseñan y ensamblan una batidora deben realizar un trabajo especializado que requiere conocimientos de ciencia de materiales y electricidad. Cubren todo eso con una carcasa de plástico para que la gente que solo quiere mezclar masa para tortitas no tenga que preocuparse por todo eso, solo tienen que entender los pocos botones con los que se maneja la batidora. {{index "clase"}} -De manera similar, un tipo de dato abstracto, o clase de objeto, es un subprograma que puede contener un código arbitrariamente complicado, pero expone un conjunto limitado de métodos y propiedades que se supone que las personas que trabajan con él deben usar. Esto permite construir programas grandes a partir de varios tipos de electrodomésticos, limitando el grado en que estas diferentes partes están entrelazadas al requerir que solo interactúen entre sí de formas específicas. +De manera similar, un _tipo de dato abstracto_, o _clase de objeto_, es un subprograma que puede contener un código arbitrariamente complicado, pero que expone un conjunto limitado de métodos y propiedades que se espera que utilicen las personas que trabajan con él. Esto permite construir programas grandes a partir de varios tipos de "electrodomésticos", limitando el grado en que estas diferentes partes se relacionan al requerir que solo interactúen entre sí de formas específicas. {{index encapsulamiento, aislamiento, modularidad}} -Si se encuentra un problema en una clase de objeto como esta, a menudo se puede reparar, o incluso reescribir completamente, sin afectar el resto del programa. - -Incluso mejor, puede ser posible utilizar clases de objetos en varios programas diferentes, evitando la necesidad de recrear su funcionalidad desde cero. Puedes pensar en las estructuras de datos integradas de JavaScript, como arrays y strings, como tipos de datos abstractos reutilizables de este tipo. +Si se encuentra un problema en una clase de objeto como esta, a menudo se puede reparar, o incluso reescribir completamente, sin afectar el resto del programa. Aún mejor, se pueden utilizar clases de objetos en varios programas diferentes, evitando la necesidad de recrear su funcionalidad desde cero. Puedes pensar también en las estructuras de datos integradas de JavaScript, como arrays y strings, como tales tipos de datos abstractos reutilizables. {{id interfaz}} {{index [interfaz, objeto]}} -Cada tipo de dato abstracto tiene una _interfaz_, que es la colección de operaciones que el código externo puede realizar en él. Incluso cosas básicas como los números pueden considerarse un tipo de dato abstracto cuya interfaz nos permite sumarlos, multiplicarlos, compararlos, y así sucesivamente. De hecho, la fijación en objetos _individuales_ como la unidad principal de organización en la programación orientada a objetos clásica es un tanto desafortunada, ya que a menudo las piezas de funcionalidad útiles involucran un grupo de diferentes clases de objetos que trabajan estrechamente juntos. +Cada tipo de dato abstracto tiene una _interfaz_: la colección de operaciones que el código externo puede realizar en él. Cualquier detalle más allá de dicha interfaz queda _encapsulado_ al tratarse como interno al tipo y de no incumbencia para el resto del programa. + +Incluso algo tan básico como los números puede considerarse un tipo de dato abstracto, cuya interfaz nos permite sumarlos, multiplicarlos, compararlos, etc. Sin embargo, la programación orientada a objetos clásica suele poner demasiado énfasis en los _objetos_ individuales como unidad fundamental de organización, cuando en realidad muchas funcionalidades útiles surgen de la cooperación entre varias clases de objetos. {{id obj_methods}} @@ -45,82 +43,82 @@ Cada tipo de dato abstracto tiene una _interfaz_, que es la colección de operac {{index "ejemplo de conejo", "método", [propiedad, acceso]}} -En JavaScript, los métodos no son más que propiedades que contienen valores de función. Este es un método simple: +En JavaScript, los métodos no son más que propiedades que contienen valores de función. Aquí hay un método simple: ```{includeCode: "top_lines:6"} -function speak(line) { - console.log(`El conejo ${this.type} dice '${line}'`); +function hablar(frase) { + console.log(`El conejo ${this.tipo} dice '${frase}'`); } -let conejoBlanco = {type: "blanco", speak}; -let conejoHambriento = {type: "hambriento", speak}; +let conejoBlanco = {tipo: "blanco", hablar}; +let conejoHambriento = {tipo: "hambriento", hablar}; -conejoBlanco.speak("Oh, mi pelaje y mis bigotes"); +conejoBlanco.hablar("Oh, mi pelaje y mis bigotes"); // → El conejo blanco dice 'Oh, mi pelaje y mis bigotes' -conejoHambriento.speak("¿Tienes zanahorias?"); +conejoHambriento.hablar("¿Tienes zanahorias?"); // → El conejo hambriento dice '¿Tienes zanahorias?' ``` {{index "vinculación de this", "llamada de método"}} -Típicamente, un método necesita hacer algo con el objeto en el que fue invocado. Cuando una función es llamada como método—buscada como propiedad y llamada inmediatamente, como en `objeto.método()`—la vinculación llamada `this` en su cuerpo apunta automáticamente al objeto en el que fue llamada. +Normalmente, un método tiene que hacer algo con el objeto sobre el que se ha llamado. Cuando una función se llama como método —es decir, se buscada como una propiedad y se llama inmediatamente, como en `objeto.método()`— la asociación llamada `this` en el cuerpo de la misma apunta automáticamente al objeto sobre el que se hizo la llamada. {{id call_method}} {{index "llamar método"}} -Puedes pensar en `this` como un ((parámetro)) extra que se pasa a la función de una manera diferente a los parámetros regulares. Si deseas proveerlo explícitamente, puedes usar el método `call` de una función, el cual toma el valor de `this` como su primer argumento y trata los siguientes argumentos como parámetros normales. +Puedes pensar en `this` como un ((parámetro)) extra que se pasa a la función de una manera diferente a los parámetros normales. Si quieres darlo explícitamente coimo parámetro, puedes usar el método `call` de la función, que toma el valor de `this` como primer argumento y trata los siguientes argumentos como parámetros normales. ``` -speak.call(conejoBlanco, "Rápido"); +hablar.call(conejoBlanco, "Rápido"); // → El conejo blanco dice 'Rápido' ``` -Dado que cada función tiene su propia vinculación `this`, cuyo valor depende de la forma en que es llamada, no puedes hacer referencia al `this` del ámbito envolvente en una función regular definida con la palabra clave `function`. +Dado que cada función tiene su propia asociación `this`, cuyo valor depende de la forma en que es llamada, dentro una función normal definida con la palabra clave `function` no puedes hacer referencia al `this` del ámbito en el que esta se encuentra envuelta. {{index "vinculación de this", "función flecha"}} -Las funciones flecha son diferentes—no vinculan su propio `this` pero pueden ver la vinculación `this` del ámbito que las rodea. Por lo tanto, puedes hacer algo como el siguiente código, el cual hace referencia a `this` desde dentro de una función local: +Las funciones flecha son diferentes —no enlazan su propio `this` sino que pueden acceder a la asociación `this` del ámbito que las rodea. Por lo tanto, puedes hacer algo como el siguiente código, que hace referencia a `this` desde dentro de una función local: ``` let buscador = { - find(array) { - return array.some(v => v == this.value); + buscar(array) { + return array.some(v => v == this.valor); }, - value: 5 + valor: 5 }; -console.log(buscador.find([4, 5])); +console.log(buscador.buscar([4, 5])); // → true ``` -Una propiedad como `find(array)` en una expresión de objeto es una forma abreviada de definir un método. Crea una propiedad llamada `find` y le asigna una función como su valor. +Una propiedad como `buscar(array)` en una expresión de objeto es una forma abreviada de definir un método. Crea una propiedad llamada `buscar` y le asigna una función como valor de la misma. -Si hubiera escrito el argumento de `some` usando la palabra clave `function`, este código no funcionaría. +Si hubiera escrito el argumento de `some` usando la palabra clave `function`, este código no funcionaría, por lo mencionado más arriba. {{id prototypes}} ## Prototipos -Entonces, una forma de crear un tipo de conejo abstracto con un método `speak` sería crear una función de ayuda que tenga un tipo de conejo como parámetro, y devuelva un objeto que contenga eso como su propiedad `type` y nuestra función `speak` en su propiedad `speak`. +Una manera de crear un de objeto de tipo conejo con un método `hablar` sería crear una función auxiliar que tenga un tipo de conejo como su parámetro y devuelva un objeto que contenga dicho tipo como su propiedad `tipo` y nuestra función de hablar en su propiedad `hablar`. -Todos los conejos comparten ese mismo método. Especialmente para tipos con muchos métodos, sería conveniente tener una forma de mantener los métodos de un tipo en un solo lugar, en lugar de añadirlos a cada objeto individualmente. +Todos los conejos comparten ese mismo método. Especialmente para tipos con muchos métodos, estaría bien si hubiera una manera de guardar los métodos del tipo en un solo lugar, en vez de tener que añadirlos a cada objeto individualmente. {{index [propiedad, herencia], [objeto, propiedad], "Prototipo de objeto"}} -En JavaScript, los _((prototipos))_ son la forma de lograr eso. Los objetos pueden estar enlazados a otros objetos, para obtener mágicamente todas las propiedades que ese otro objeto tiene. Los simples objetos creados con la notación `{}` están enlazados a un objeto llamado `Object.prototype`. +En JavaScript, la manera de hacer eso son los _((prototipos))_. Los objetos pueden enlazarse a otros objetos para obtener mágicamente todas las propiedades que ese otro objeto tiene. Los objetos sencillos creados con la notación `{}` están enlazados a un objeto llamado `Object.prototype`. {{index "método toString"}} ``` -let empty = {}; -console.log(empty.toString); +let vacío = {}; +console.log(vacío.toString); // → function toString(){…} -console.log(empty.toString()); +console.log(vacío.toString()); // → [object Object] ``` -Parece que acabamos de extraer una propiedad de un objeto vacío. Pero de hecho, `toString` es un método almacenado en `Object.prototype`, lo que significa que está disponible en la mayoría de los objetos. +Parece que acabamos de extraer una propiedad de un objeto vacío. Pero resulta que `toString` es un método almacenado en `Object.prototype`, lo que significa que está disponible en la mayoría de los objetos. -Cuando a un objeto se le solicita una propiedad que no tiene, se buscará en su prototipo la propiedad. Si éste no la tiene, se buscará en _su_ prototipo, y así sucesivamente hasta llegar a un objeto que no tiene prototipo (`Object.prototype` es un objeto de este tipo). +Cuando a un objeto se le solicita una propiedad que no tiene, se buscará en su prototipo la propiedad. Si éste no la tiene, se buscará en _su_ prototipo, y así sucesivamente hasta llegar a un objeto que no tiene prototipo (`Object.prototype` es uno de estos objetos). ``` console.log(Object.getPrototypeOf({}) == Object.prototype); @@ -135,7 +133,7 @@ Como podrás imaginar, `Object.getPrototypeOf` devuelve el prototipo de un objet {{index herencia, "prototipo de Function", "prototipo de Array", "prototipo de Object"}} -Muchos objetos no tienen directamente `Object.prototype` como su ((prototipo)), sino que tienen otro objeto que proporciona un conjunto diferente de propiedades predeterminadas. Las funciones se derivan de `Function.prototype`, y los arreglos se derivan de `Array.prototype`. +Muchos objetos no tienen directamente `Object.prototype` como su ((prototipo)), sino que en su lugar tienen otro objeto que les proporciona un conjunto diferente de propiedades predeterminadas. Las funciones se derivan de `Function.prototype`, y los arrays se derivan de `Array.prototype`. ``` console.log(Object.getPrototypeOf(Math.max) == @@ -147,27 +145,27 @@ console.log(Object.getPrototypeOf([]) == Array.prototype); {{index "prototipo de Object"}} -Un objeto prototipo de este tipo tendrá a su vez un prototipo, a menudo `Object.prototype`, de modo que aún proporciona de forma indirecta métodos como `toString`. +Un objeto prototipo de este tipo tendrá a su vez un prototipo, a menudo `Object.prototype`, de modo que este aún proporciona de forma indirecta métodos como `toString`. {{index "ejemplo de conejo", "función Object.create"}} Puedes utilizar `Object.create` para crear un objeto con un ((prototipo)) específico. ```{includeCode: "top_lines: 7"} -let protoRabbit = { - speak(line) { - console.log(`El conejo ${this.type} dice '${line}'`); +let protoConejo = { + hablar(frase) { + console.log(`El conejo ${this.tipo} dice '${frase}'`); } }; -let blackRabbit = Object.create(protoRabbit); -blackRabbit.type = "negro"; -blackRabbit.speak("Soy el miedo y la oscuridad"); -// → El conejo negro dice 'Soy el miedo y la oscuridad' +let conejoNegro = Object.create(protoConejo); +conejoNegro.tipo = "negro"; +conejoNegro.hablar("Soy miedo y oscuridad"); +// → El conejo negro dice 'Soy miedo y oscuridad' ``` {{index "propiedad compartida"}} -El conejo "proto" actúa como un contenedor para las propiedades que son compartidas por todos los conejos. Un objeto de conejo individual, como el conejo negro, contiene propiedades que se aplican solo a él mismo, en este caso su tipo, y deriva propiedades compartidas de su prototipo. +El "proto" conejo actúa como un contenedor para las propiedades que comparten todos los conejos. Un objeto conejo individual, como el conejo negro, contiene propiedades que se aplican solo a él mismo —en este caso su tipo— y hereda las propiedades compartidas de su prototipo. {{id clases}} @@ -175,67 +173,67 @@ El conejo "proto" actúa como un contenedor para las propiedades que son compart {{index "programación orientada a objetos", "tipo de datos abstracto"}} -El sistema de ((prototipos)) de JavaScript puede interpretarse como una versión algo libre de los tipos de datos abstractos o ((clases)). Una clase define la forma de un tipo de objeto, los métodos y propiedades que tiene. A dicho objeto se le llama una _((instancia))_ de la clase. +El sistema de ((prototipos)) de JavaScript puede interpretarse como una versión algo libre de los tipos de datos abstractos o ((clases)). Una _clase_ define la forma de un tipo de objeto —los métodos y propiedades que tiene. A dicho objeto se le llama una _((instancia))_ de la clase. {{index [propiedad, herencia]}} -Los prototipos son útiles para definir propiedades cuyo valor es compartido por todas las instancias de una clase. Las propiedades que difieren por instancia, como la propiedad `type` de nuestros conejos, deben ser almacenadas directamente en los objetos mismos. +Los prototipos son útiles para definir propiedades cuyo valor es compartido por todas las instancias de una clase. Las propiedades que difieren por instancia, como nuestra propiedad `tipo` de los conejos, deben ser almacenadas directamente en los objetos mismos. {{id constructores}} -Así que para crear una instancia de una clase, debes hacer un objeto que se derive del prototipo adecuado, pero _también_ debes asegurarte de que él mismo tenga las propiedades que se supone que deben tener las instancias de esta clase. Esto es lo que hace una función _((constructor))_. +Para crear una instancia de una clase dada, debes hacer un objeto que herede del prototipo adecuado, pero _también_ debes asegurarte de que tenga las propiedades que se supone que deben tener las instancias de esta clase. Esto es lo que hace una función _((constructor))_. ``` -function makeRabbit(type) { - let rabbit = Object.create(protoRabbit); - rabbit.type = type; - return rabbit; +function hacerConejo(tipo) { + let conejo = Object.create(protoConejo); + conejo.tipo = tipo; + return conejo; } ``` -La notación de ((class)) de JavaScript facilita la definición de este tipo de función, junto con un objeto ((prototype)). +La notación de ((class)) de JavaScript facilita la definición de este tipo de funciones, junto con un objeto ((prototype)). {{index "ejemplo de conejo", constructor}} ```{includeCode: true} -class Rabbit { - constructor(type) { - this.type = type; +class Conejo { + constructor(tipo) { + this.tipo = tipo; } - speak(line) { - console.log(`El conejo ${this.type} dice '${line}'`); + hablar(frase) { + console.log(`El conejo ${this.tipo} dice '${frase}'`); } } ``` {{index "propiedad prototype", [llaves, clase]}} -La palabra clave `class` inicia una ((declaración de clase)), que nos permite definir un constructor y un conjunto de métodos juntos. Se pueden escribir cualquier cantidad de métodos dentro de las llaves de la declaración. Este código tiene el efecto de definir un enlace llamado `Rabbit`, que contiene una función que ejecuta el código en `constructor`, y tiene una propiedad `prototype` que contiene el método `speak`. +La palabra clave `class` inicia una ((declaración de clase)), que nos permite definir un constructor y un conjunto de métodos a la vez. Se puede escribir cualquier cantidad de métodos dentro de las llaves de la declaración. Este código tiene el efecto de definir una asociación llamada `Conejo`, que contiene una función que ejecuta el código en `constructor`, y tiene una propiedad `prototype` que contiene el método `hablar`. {{index "operador new", "enlace this", ["creación de objetos"]}} -Esta función no puede ser llamada normalmente. Los constructores, en JavaScript, se llaman colocando la palabra clave `new` delante de ellos. Al hacerlo, se crea un objeto nuevo con el objeto contenido en la propiedad `prototype` de la función como prototipo, luego se ejecuta la función con `this` vinculado al nuevo objeto, y finalmente se devuelve el objeto. +Esta función no se puede llamar como una función normal. Los constructores, en JavaScript, se llaman colocando la palabra clave `new` delante de ellos. Al hacerlo, se crea una nueva instancia de objeto cuyo prototipo es el objeto de la propiedad `prototype` de la función, luego se ejecuta la función con `this` enlazado al nuevo objeto, y finalmente se devuelve el objeto. ```{includeCode: true} -let killerRabbit = new Rabbit("asesino"); +let conejoAsesino = new Rabbit("asesino"); ``` -De hecho, la palabra clave `class` se introdujo solo en la edición de JavaScript de 2015. Cualquier función puede ser utilizada como constructor, y antes de 2015 la forma de definir una clase era escribir una función regular y luego manipular su propiedad `prototype`. +De hecho, la palabra clave `class` se introdujo recién en la edición de JavaScript de 2015. Cualquier función puede ser utilizada como constructor, y antes de 2015 la forma de definir una clase era escribir una función normal y luego manipular su propiedad `prototype`. ``` -function ConejoArcaico(type) { - this.type = type; +function ConejoArcaico(tipo) { + this.tipo = tipo; } -ConejoArcaico.prototype.speak = function(line) { - console.log(`El conejo ${this.type} dice '${line}'`); +ConejoArcaico.prototype.hablar = function(frase) { + console.log(`El conejo ${this.tipo} dice '${frase}'`); }; -let conejoEstiloAntiguo = new ConejoArcaico("estilo antiguo"); +let conejoViejaEscuela = new ConejoArcaico("de la vieja escuela"); ``` -Por esta razón, todas las funciones que no sean de flecha comienzan con una propiedad `prototype` que contiene un objeto vacío. +Por esta razón, todas las funciones que no sean funciones flecha comienzan teniendo una propiedad `prototype` que contiene un objeto vacío. {{index "mayúsculas"}} @@ -243,106 +241,110 @@ Por convención, los nombres de constructores se escriben con mayúscula inicial {{index "propiedad prototype", "función getPrototypeOf"}} -Es importante entender la distinción entre la forma en que un prototipo está asociado con un constructor (a través de su _propiedad_ `prototype`) y la forma en que los objetos _tienen_ un prototipo (que se puede encontrar con `Object.getPrototypeOf`). El prototipo real de un constructor es `Function.prototype` ya que los constructores son funciones. Su _propiedad_ `prototype` contiene el prototipo utilizado para las instancias creadas a través de él. +Es importante entender la distinción entre la forma en que un prototipo está asociado a un constructor (a través de su _propiedad_ `prototype`) y la forma en que los objetos _tienen_ un prototipo (que se puede encontrar con `Object.getPrototypeOf`). El prototipo real de un constructor es `Function.prototype` ya que los constructores son funciones. Su _propiedad_ `prototype` contiene el prototipo utilizado para las instancias creadas a través de él. ``` -console.log(Object.getPrototypeOf(Rabbit) == +console.log(Object.getPrototypeOf(Conejo) == Function.prototype); // → true -console.log(Object.getPrototypeOf(killerRabbit) == - Rabbit.prototype); +console.log(Object.getPrototypeOf(conejoAsesino) == + Conejo.prototype); // → true ``` {{index constructor}} -Por lo general, los constructores agregarán algunas propiedades específicas de instancia a `this`. También es posible declarar propiedades directamente en la ((declaración de clase)). A diferencia de los métodos, dichas propiedades se agregan a los objetos ((instancia)), no al prototipo. +Por lo general, los constructores añadirán algunas propiedades específicas por instancia a `this`. También es posible declarar propiedades directamente en la ((declaración de clase)). A diferencia de los métodos, dichas propiedades se agregan a objetos ((instancia)), y no al prototipo. ``` -class Particle { - speed = 0; - constructor(position) { - this.position = position; +class Partícula { + rapidez = 0; + constructor(posición) { + this.posición = posición; } } ``` -Al igual que `function`, `class` se puede utilizar tanto en declaraciones como en expresiones. Cuando se usa como una expresión, no define un enlace sino que simplemente produce el constructor como un valor. Se te permite omitir el nombre de la clase en una expresión de clase. +Al igual que `function`, `class` se puede utilizar tanto en declaraciones como en expresiones. Cuando se usa como una expresión, no define una asociación sino que simplemente produce el constructor como un valor. Puedes omitir el nombre de la clase en una expresión de clase. ``` -let object = new class { getWord() { return "hello"; } }; -console.log(object.getWord()); -// → hello +let objeto = new class { obtenerPalabra() { return "hola"; } }; +console.log(objeto.obtenerPalabra()); +// → hola ``` ## Propiedades privadas {{index [property, private], [property, public], "declaración de clase"}} -Es común que las clases definan algunas propiedades y métodos para uso interno, que no forman parte de su ((interfaz)). Estas se llaman propiedades _privadas_, en contraposición a las públicas, que son parte de la interfaz externa del objeto. +Es común que las clases definan algunas propiedades y métodos para uso interno que no forman parte de su ((interfaz)). Estas propiedades se llaman propiedades _privadas_, en contraposición a las _públicas_, que son parte de la interfaz externa del objeto. {{index ["método", privado]}} Para declarar un método privado, coloca un signo `#` delante de su nombre. Estos métodos solo pueden ser llamados desde dentro de la declaración de la `class` que los define. ``` -class SecretiveObject { - #getSecret() { +class ObjectoConfidencial { + #obtenerSecreto() { return "Me comí todas las ciruelas"; } - interrogate() { - let deboDecirlo = this.#getSecret(); + interrogar() { + let voyADecirlo = this.#obtenerSecreto(); return "nunca"; } } ``` -Si intentas llamar a `#getSecret` desde fuera de la clase, obtendrás un error. Su existencia está completamente oculta dentro de la declaración de la clase. +Cuando una clase no declara un constructor, automáticamente obtiene un constructor vacío. -Para usar propiedades de instancia privadas, debes declararlas. Las propiedades regulares se pueden crear simplemente asignándoles un valor, pero las propiedades privadas _deben_ declararse en la declaración de la clase para estar disponibles en absoluto. +Si intentas llamar a `#obtenerSecreto` desde fuera de la clase, obtendrás un error. Su existencia está completamente oculta dentro de la declaración de la clase. -Esta clase implementa un dispositivo para obtener un número entero aleatorio por debajo de un número máximo dado. Solo tiene una propiedad ((pública)): `getNumber`. +Para usar propiedades de instancia privadas, debes declararlas. Las propiedades normales se pueden crear simplemente asignándoles un valor, pero las propiedades privadas _deben_ declararse en la declaración de la clase para estar disponibles. + +Esta clase implementa un dispositivo para obtener un número entero aleatorio menor que un número máximo dado. Solo tiene una propiedad ((pública)): `obtenerNúmero`. ``` -class RandomSource { +class FuenteDeAzar { #max; constructor(max) { this.#max = max; } - getNumber() { + obtenerNúmero() { return Math.floor(Math.random() * this.#max); } } ``` -## Sobrescribiendo propiedades derivadas +## Sobrescribiendo propiedades heredadas {{index "propiedad compartida", sobrescribir, [property, herencia]}} -Cuando agregas una propiedad a un objeto, ya sea que esté presente en el prototipo o no, la propiedad se agrega al objeto _mismo_. Si ya existía una propiedad con el mismo nombre en el prototipo, esta propiedad ya no afectará al objeto, ya que ahora está oculta detrás de la propiedad propia del objeto. +Cuando agregas una propiedad a un objeto, esté presente en el prototipo o no, la propiedad se agrega al _propio_ objeto. Si ya existía una propiedad con el mismo nombre en el prototipo, esta propiedad ya no afectará al objeto, ya que quedará oculta tras la propia propiedad del objeto. ``` -Rabbit.prototype.teeth = "pequeñas"; -console.log(killerRabbit.teeth); -// → pequeñas -killerRabbit.teeth = "largos, afilados y sangrientos"; -console.log(killerRabbit.teeth); +Conejo.prototype.dientes = "pequeños"; +console.log(conejoAsesino.dientes); +// → pequeños +conejoAsesino.dientes = "largos, afilados y sangrientos"; +console.log(conejoAsesino.dientes); // → largos, afilados y sangrientos -console.log((new Rabbit("básico")).teeth); -// → pequeñas -console.log(Rabbit.prototype.teeth); -// → pequeñas +console.log((new Conejo("básico")).dientes); +// → pequeños +console.log(Conejo.prototype.dientes); +// → pequeños ``` {{index [prototipo, diagrama]}} -El siguiente diagrama esquematiza la situación después de que se ha ejecutado este código. Los prototipos `Rabbit` y `Object` están detrás de `killerRabbit` como un telón de fondo, donde se pueden buscar propiedades que no se encuentran en el objeto mismo. +El siguiente diagrama esquematiza la situación después de ejecutar este código. Los prototipos `Conejo` y `Object` están detrás de `conejoAsesino` como una especie telón de fondo, donde se pueden buscar propiedades que no se encuentran en el objeto mismo. {{figure {url: "img/rabbits.svg", alt: "Un diagrama que muestra la estructura de objetos de conejos y sus prototipos. Hay un cuadro para la instancia 'killerRabbit' (que tiene propiedades de instancia como 'tipo'), con sus dos prototipos, 'Rabbit.prototype' (que tiene el método 'hablar') y 'Object.prototype' (que tiene métodos como 'toString') apilados detrás de él.",width: "8cm"}}} +{{note "**N. del T.:** En esta traducción no se han traducido las figuras y, por tanto, los textos que aparecen en ellas son los originales. En la figura, `killerRabbit` es `conejoAsesino`, `teeth` es `dientes`, `type` es `tipo`, `speak` es `hablar` y `Rabbit` es `Conejo`."}} + {{index "propiedad compartida"}} -Sobrescribir propiedades que existen en un prototipo puede ser algo útil de hacer. Como muestra el ejemplo de los dientes del conejo, sobrescribir se puede utilizar para expresar propiedades excepcionales en instancias de una clase más genérica de objetos, mientras se permite que los objetos no excepcionales tomen un valor estándar de su prototipo. +Sobrescribir propiedades que existen en un prototipo puede ser algo útil. Como muestra el ejemplo de los dientes del conejo, se puede sobrescribir para expresar propiedades excepcionales en instancias de una clase más genérica de objetos, mientras se permite que los objetos no excepcionales adopten un valor estándar de su prototipo. {{index "método toString", "prototipo de Array", "prototipo de Function"}} @@ -358,7 +360,7 @@ console.log([1, 2].toString()); {{index "método toString", "método join", "método call"}} -Llamar a `toString` en un array produce un resultado similar a llamar a `.join(",")` en él—coloca comas entre los valores en el array. Llamar directamente a `Object.prototype.toString` con un array produce una cadena diferente. Esa función no conoce acerca de los arrays, por lo que simplemente coloca la palabra _object_ y el nombre del tipo entre corchetes. +Llamar a `toString` en un array produce un resultado similar a llamar a `.join(",")` en él —coloca comas entre los valores en el array. Llamar directamente a `Object.prototype.toString` con un array produce una cadena diferente. Esa función no conoce acerca de los arrays, por lo que simplemente coloca la palabra _object_ y el nombre del tipo entre corchetes. ``` console.log(Object.prototype.toString.call([1, 2])); @@ -369,11 +371,11 @@ console.log(Object.prototype.toString.call([1, 2])); {{index "método map"}} -Vimos la palabra _map_ utilizada en el [capítulo anterior](higher_order#map) para una operación que transforma una estructura de datos aplicando una función a sus elementos. Por confuso que sea, en programación la misma palabra también se utiliza para una cosa relacionada pero bastante diferente. +Vimos la palabra _map_ utilizada en el [capítulo anterior](higher_order#map) para una operación que transforma una estructura de datos aplicando una función a cada uno de sus elementos. Por confuso que sea, en programación la misma palabra también se utiliza para una cosa relacionada pero bastante diferente. {{index "map (estructura de datos)", "ejemplo de edades", ["estructura de datos", map]}} -Un _mapa_ (sustantivo) es una estructura de datos que asocia valores (las claves) con otros valores. Por ejemplo, podrías querer mapear nombres a edades. Es posible usar objetos para esto. +Un _mapa_ (conocido como diccionario en otros contextos) es una estructura de datos que asocia valores (las claves) con otros valores. Por ejemplo, podrías querer mapear nombres a edades. Es posible usar objetos para esto. ``` let edades = { @@ -392,11 +394,11 @@ console.log("¿Se conoce la edad de toString?", "toString" in edades); {{index "Object.prototype", "método toString"}} -Aquí, los nombres de propiedad del objeto son los nombres de las personas, y los valores de las propiedades son sus edades. Pero ciertamente no listamos a nadie con el nombre toString en nuestro mapa. Sin embargo, dado que los objetos simples derivan de `Object.prototype`, parece que la propiedad está allí. +Aquí, los nombres de propiedad del objeto son los nombres de las personas, y los valores de las propiedades son sus edades. Aunque está claro que no hemos incluido a nadie en la lista de nuestro mapa con el nombre toString, dado que los objetos sencillos derivan de `Object.prototype`, parece que la propiedad sí que está presente ahí. {{index "función Object.create", prototipo}} -Por lo tanto, usar objetos simples como mapas es peligroso. Hay varias formas posibles de evitar este problema. Primero, es posible crear objetos sin _ningún_ prototipo. Si pasas `null` a `Object.create`, el objeto resultante no derivará de `Object.prototype` y se puede usar de forma segura como un mapa. +Por lo tanto, usar objetos simples como mapas es peligroso. Hay varias formas posibles de evitar este problema. Primero, es posible crear objetos _sin_ prototipo. Si pasas `null` a `Object.create`, el objeto resultante no derivará de `Object.prototype` y se puede usar de forma segura como un mapa. ``` console.log("toString" in Object.create(null)); @@ -405,33 +407,33 @@ console.log("toString" in Object.create(null)); {{index [propiedad, nombrar]}} -Los nombres de las propiedades de los objetos deben ser cadenas. Si necesitas un mapa cuyas claves no puedan convertirse fácilmente en cadenas—como objetos—no puedes usar un objeto como tu mapa. +Los nombres de las propiedades de los objetos deben ser cadenas. Si necesitas un mapa cuyas claves no puedan convertirse fácilmente en cadenas —como por ejemplo, objetos— no puedes usar un objeto como tu mapa. {{index "clase Map"}} -Afortunadamente, JavaScript viene con una clase llamada `Map` que está escrita para este propósito exacto. Almacena un mapeo y permite cualquier tipo de claves. +Por suerte, JavaScript viene con una clase llamada `Map` que está escrita justo para esto. Almacena un mapeo y permite cualquier tipo de claves. ``` -let ages = new Map(); -ages.set("Boris", 39); -ages.set("Liang", 22); -ages.set("Júlia", 62); +let edades = new Map(); +edades.set("Boris", 39); +edades.set("Liang", 22); +edades.set("Júlia", 62); -console.log(`Júlia tiene ${ages.get("Júlia")}`); +console.log(`Júlia tiene ${edades.get("Júlia")}`); // → Júlia tiene 62 -console.log("¿Se conoce la edad de Jack?", ages.has("Jack")); +console.log("¿Se conoce la edad de Jack?", edades.has("Jack")); // → ¿Se conoce la edad de Jack? false -console.log(ages.has("toString")); +console.log(edades.has("toString")); // → false ``` {{index [interfaz, objeto], "método set", "método get", "método has", "encapsulación"}} -Los métodos `set`, `get` y `has` forman parte de la interfaz del objeto `Map`. Escribir una estructura de datos que pueda actualizar y buscar rápidamente un gran conjunto de valores no es fácil, pero no tenemos que preocuparnos por eso. Alguien más lo hizo por nosotros, y podemos utilizar su trabajo a través de esta interfaz sencilla. +Los métodos `set`, `get` y `has` forman parte de la interfaz del objeto `Map`. Escribir una estructura de datos que pueda actualizar y buscar rápidamente un gran conjunto de valores no es fácil, pero no tenemos que preocuparnos por eso. Otra persona lo ha hecho por nosotros, y podemos utilizar su trabajo a través de esta sencilla interfaz. {{index "función hasOwn", "operador in"}} -Si tienes un objeto simple que necesitas tratar como un mapa por alguna razón, es útil saber que `Object.keys` devuelve solo las claves _propias_ de un objeto, no las del prototipo. Como alternativa al operador `in`, puedes utilizar la función `Object.hasOwn`, que ignora el prototipo del objeto. +Si tienes un objeto simple que necesitas tratar como un mapa por algún motivo, es útil saber que `Object.keys` devuelve solo las claves _propias_ de un objeto, no las del prototipo. Como alternativa al operador `in`, puedes utilizar la función `Object.hasOwn`, que ignora el prototipo del objeto. ``` console.log(Object.hasOwn({x: 1}, "x")); @@ -444,33 +446,33 @@ console.log(Object.hasOwn({x: 1}, "toString")); {{index "método toString", "función String", polimorfismo, "anulación", "programación orientada a objetos"}} -Cuando llamas a la función `String` (que convierte un valor a una cadena) en un objeto, llamará al método `toString` en ese objeto para intentar crear una cadena significativa a partir de él. Mencioné que algunos de los prototipos estándar definen su propia versión de `toString` para poder crear una cadena que contenga información más útil que `"[object Object]"`. También puedes hacerlo tú mismo. +Cuando llamas a la función `String` (que convierte un valor a una cadena) en un objeto, llamará al método `toString` en ese objeto para intentar crear una cadena significativa a partir de él. Antes mencioné que algunos de los prototipos estándar definen su propia versión de `toString` para poder crear una cadena que contenga información más útil que `"[object Object]"`. También puedes hacerlo tú mismo. ```{includeCode: "top_lines: 3"} -Rabbit.prototype.toString = function() { - return `un conejo ${this.type}`; +Conejo.prototype.toString = function() { + return `un conejo ${this.tipo}`; }; -console.log(String(killerRabbit)); +console.log(String(conejoAsesino)); // → un conejo asesino ``` {{index "programación orientada a objetos", [interfaz, objeto]}} -Este es un ejemplo simple de una idea poderosa. Cuando se escribe un código para trabajar con objetos que tienen una determinada interfaz, en este caso, un método `toString`, cualquier tipo de objeto que accidentalmente admita esta interfaz puede ser enchufado en el código, y este podrá funcionar con él. +Este es un ejemplo simple de una idea poderosa. Cuando se escribe un código para trabajar con objetos que tienen una determinada interfaz (en este caso, un método `toString`), cualquier tipo de objeto que cumpla con esta interfaz puede integrarse en el código y funcionará correctamente. -Esta técnica se llama _polimorfismo_. El código polimórfico puede trabajar con valores de diferentes formas, siempre y cuando admitan la interfaz que espera. +Esta técnica se llama _polimorfismo_. El código polimórfico puede trabajar con valores de diferentes formas, siempre y cuando admitan la interfaz que este espera. {{index "método forEach"}} -Un ejemplo de una interfaz ampliamente utilizada es la de los ((objeto similar a un array)) que tiene una propiedad `length` que contiene un número, y propiedades numeradas para cada uno de sus elementos. Tanto los arreglos como las cadenas admiten esta interfaz, al igual que varios otros objetos, algunos de los cuales veremos más adelante en los capítulos sobre el navegador. Nuestra implementación de `forEach` en el [Capítulo ?](higher_order) funciona en cualquier cosa que proporcione esta interfaz. De hecho, también lo hace `Array.prototype.forEach`. +Un ejemplo de una interfaz ampliamente utilizada es la de los ((objetos similares a un array)), que tienen una propiedad `length` que contiene un número, y propiedades numeradas para cada uno de sus elementos. Tanto los arrays como las cadenas admiten esta interfaz, al igual que otros objetos, algunos de los cuales veremos más adelante en los capítulos sobre el navegador. Nuestra implementación de `forEach` en el [Capítulo ?](higher_order) funciona en cualquier cosa que proporcione esta interfaz. De hecho, también lo hace `Array.prototype.forEach`. ``` Array.prototype.forEach.call({ length: 2, 0: "A", 1: "B" -}, elt => console.log(elt)); +}, elemento => console.log(elemento)); // → A // → B ``` @@ -479,45 +481,45 @@ Array.prototype.forEach.call({ {{index [interfaz, objeto], [propiedad, "definición"], "clase Map"}} -Las interfaces a menudo contienen propiedades simples, no solo métodos. Por ejemplo, los objetos `Map` tienen una propiedad `size` que te dice cuántas claves están almacenadas en ellos. +Las interfaces a menudo contienen propiedades simples, no solo métodos. Por ejemplo, los objetos `Map` tienen una propiedad `size` que te dice cuántas claves almacenan. No es necesario que dicho objeto calcule y almacene directamente esa propiedad en la instancia. Incluso las propiedades que se acceden directamente pueden ocultar una llamada a un método. Dichos métodos se llaman _((getter))_ y se definen escribiendo `get` delante del nombre del método en una expresión de objeto o declaración de clase. ```{test: no} -let varyingSize = { - get size() { +let tamañoCambiante = { + get tamaño() { return Math.floor(Math.random() * 100); } }; -console.log(varyingSize.size); +console.log(tamañoCambiante.tamaño); // → 73 -console.log(varyingSize.size); +console.log(tamañoCambiante.tamaño); // → 49 ``` {{index "ejemplo temperatura"}} -Cada vez que alguien lee la propiedad `size` de este objeto, se llama al método asociado. Puedes hacer algo similar cuando se escribe en una propiedad, utilizando un _((setter))_. +Cada vez que alguien lee la propiedad `tamaño` de este objeto, se llama al método asociado. Puedes hacer algo similar cuando se escribe en una propiedad, utilizando un _((setter))_. ```{test: no, startCode: true} -class Temperature { +class Temperatura { constructor(celsius) { this.celsius = celsius; } get fahrenheit() { return this.celsius * 1.8 + 32; } - set fahrenheit(value) { - this.celsius = (value - 32) / 1.8; + set fahrenheit(valor) { + this.celsius = (valor - 32) / 1.8; } - static fromFahrenheit(value) { - return new Temperature((value - 32) / 1.8); + static fromFahrenheit(valor) { + return new Temperatura((valor - 32) / 1.8); } } -let temp = new Temperature(22); +let temp = new Temperatura(22); console.log(temp.fahrenheit); // → 71.6 temp.fahrenheit = 86; @@ -525,54 +527,54 @@ console.log(temp.celsius); // → 30 ``` -La clase `Temperature` te permite leer y escribir la temperatura en grados ((Celsius)) o grados ((Fahrenheit)), pero internamente solo almacena Celsius y convierte automáticamente de y a Celsius en el _getter_ y _setter_ de `fahrenheit`. +La clase `Temperatura` te permite leer y escribir la temperatura en grados ((Celsius)) o grados ((Fahrenheit)), pero internamente solo almacena Celsius y convierte automáticamente de y a Celsius en el _getter_ y _setter_ de `fahrenheit`. {{index "método estático", "propiedad estática"}} A veces quieres adjuntar algunas propiedades directamente a tu función constructora, en lugar de al prototipo. Estos métodos no tendrán acceso a una instancia de clase, pero pueden, por ejemplo, usarse para proporcionar formas adicionales de crear instancias. -Dentro de una declaración de clase, los métodos o propiedades que tienen `static` escrito antes de su nombre se almacenan en el constructor. Por lo tanto, la clase `Temperature` te permite escribir `Temperature.fromFahrenheit(100)` para crear una temperatura usando grados Fahrenheit. +Dentro de una declaración de clase, los métodos o propiedades que tienen `static` escrito antes de su nombre se almacenan en el constructor. Por lo tanto, la clase `Temperatura` te permite escribir `Temperatura.fromFahrenheit(100)` para crear una temperatura usando grados Fahrenheit. ## Símbolos {{index "bucle for/of", "interfaz iteradora"}} -Mencioné en el [Capítulo ?](data#for_of_loop) que un bucle `for`/`of` puede recorrer varios tipos de estructuras de datos. Este es otro caso de polimorfismo: tales bucles esperan que la estructura de datos exponga una interfaz específica, la cual hacen los arrays y las cadenas. ¡Y también podemos agregar esta interfaz a nuestros propios objetos! Pero antes de hacerlo, debemos echar un vistazo breve al tipo de símbolo. +Mencioné en el [Capítulo ?](data#for_of_loop) que un bucle `for`/`of` puede recorrer varios tipos de estructuras de datos. Este es otro caso de polimorfismo: tales bucles esperan que la estructura de datos exponga una interfaz específica, lo cual hacen por ejemplo los arrays y las cadenas. ¡Y también podemos agregar esta interfaz a nuestros propios objetos! Pero antes de hacerlo, debemos echar un vistazo breve al tipo símbolo. Es posible que múltiples interfaces utilicen el mismo nombre de propiedad para diferentes cosas. Por ejemplo, en objetos similares a arrays, `length` se refiere a la cantidad de elementos en la colección. Pero una interfaz de objeto que describa una ruta de senderismo podría usar `length` para proporcionar la longitud de la ruta en metros. No sería posible que un objeto cumpla con ambas interfaces. -Un objeto que intente ser una ruta y similar a un array (quizás para enumerar sus puntos de referencia) es algo un tanto improbable, y este tipo de problema no es tan común en la práctica. Pero para cosas como el protocolo de iteración, los diseñadores del lenguaje necesitaban un tipo de propiedad que _realmente_ no entrara en conflicto con ninguna otra. Por lo tanto, en 2015, se agregaron los _((símbolos))_ al lenguaje. +Un objeto que intente ser una ruta y similar a un array (quizás para enumerar sus puntos de referencia) es algo un tanto improbable, y este tipo de problema no es tan común en la práctica. Sin embargo, para cosas como el protocolo de iteración, los diseñadores del lenguaje necesitaban un tipo de propiedad que _realmente_ no entrara en conflicto con ninguna otra. Por lo tanto, en 2015, se agregaron los _((símbolos))_ al lenguaje. {{index "Función de símbolo", [propiedad, "denominación"]}} -La mayoría de las propiedades, incluidas todas las propiedades que hemos visto hasta ahora, se nombran con cadenas. Pero también es posible usar símbolos como nombres de propiedades. Los símbolos son valores creados con la función `Symbol`. A diferencia de las cadenas, los símbolos recién creados son únicos: no puedes crear el mismo símbolo dos veces. +La mayoría de las propiedades, incluidas todas las propiedades que hemos visto hasta ahora, se nombran con cadenas. Pero también es posible usar símbolos como nombres de propiedades. Los símbolos son valores creados con la función `Symbol`. A diferencia de las cadenas, un símbolo recién creado es único: no puedes crear el mismo símbolo dos veces. ``` -let sym = Symbol("nombre"); -console.log(sym == Symbol("nombre")); +let símbolo = Symbol("nombre"); +console.log(símbolo == Symbol("nombre")); // → false -Rabbit.prototype[sym] = 55; -console.log(killerRabbit[sym]); +Conejo.prototype[símbolo] = 55; +console.log(conejoAsesino[símbolo]); // → 55 ``` -La cadena que pasas a `Symbol` se incluye cuando la conviertes en una cadena y puede facilitar reconocer un símbolo cuando, por ejemplo, se muestra en la consola. Pero no tiene otro significado más allá de eso: varios símbolos pueden tener el mismo nombre. +La cadena que pasas a `Symbol` se incluye cuando la conviertes en una cadena y puede facilitar reconocer un símbolo cuando, por ejemplo, se muestra en la consola. Pero no tiene otro significado más allá de eso — puede haber varios símbolos con el mismo nombre. Ser tanto únicos como utilizables como nombres de propiedades hace que los símbolos sean adecuados para definir interfaces que pueden convivir pacíficamente junto a otras propiedades, independientemente de cuáles sean sus nombres. ```{includeCode: "líneas_superiores: 1"} -const longitud = Symbol("longitud"); -Array.prototype[longitud] = 0; +const length = Symbol("length"); +Array.prototype[length] = 0; console.log([1, 2].length); // → 2 -console.log([1, 2][longitud]); +console.log([1, 2][length]); // → 0 ``` {{index [propiedad, "denominación"]}} -Es posible incluir propiedades de símbolos en expresiones de objetos y clases mediante el uso de ((corchetes)). Esto hace que la expresión entre los corchetes se evalúe para producir el nombre de la propiedad, análogo a la notación de acceso a propiedades mediante corchetes cuadrados. +Es posible incluir propiedades que sean símbolos en expresiones de objetos y clases mediante el uso de ((corchetes)). Esto hace que la expresión entre los corchetes se evalúe para producir el nombre de la propiedad, análogo a la notación de acceso a propiedades mediante corchetes. ``` let miViaje = { @@ -585,7 +587,7 @@ console.log(miViaje[longitud], miViaje.longitud); // → 21500 2 ``` -## La interfaz del iterador +## La interfaz iterador {{index "interfaz iterable", "símbolo Symbol.iterator", "bucle for/of"}} @@ -593,19 +595,19 @@ Se espera que el objeto proporcionado a un bucle `for`/`of` sea _iterable_. Esto {{index "interfaz del iterador", "método próximo"}} -Cuando se llama, ese método debería devolver un objeto que proporcione una segunda interfaz, _iterador_. Este es lo que realmente itera. Tiende un método `next` que devuelve el próximo resultado. Ese resultado debería ser un objeto con una propiedad `value` que proporciona el siguiente valor, si lo hay, y una propiedad `done`, que debería ser `true` cuando no hay más resultados y `false` en caso contrario. +Cuando se llama, ese método debería devolver un objeto que proporcione una segunda interfaz, _iterador_. Esto es lo que realmente se itera. Tiene un método `next` que devuelve el siguiente resultado. Ese resultado debería ser un objeto con una propiedad `value` que proporciona el siguiente valor, si lo hay, y una propiedad `done`, que debería ser `true` cuando no hay más resultados y `false` en caso contrario. -Ten en cuenta que los nombres de propiedad `next`, `value` y `done` son simples cadenas, no símbolos. Solo `Symbol.iterator`, que probablemente se agregará a _muchos_ objetos diferentes, es un símbolo real. +Ten en cuenta que los nombres de propiedad `next`, `value` y `done` son simples cadenas, no símbolos. Solo `Symbol.iterator`, que probablemente se agregará a _muchos_ objetos diferentes, es realmente un símbolo. Podemos usar esta interfaz directamente nosotros mismos. ``` -let okIterador = "OK"[Symbol.iterator](); -console.log(okIterador.next()); +let iteradorOk = "OK"[Symbol.iterator](); +console.log(iteradorOk.next()); // → {value: "O", done: false} -console.log(okIterador.next()); +console.log(iteradorOk.next()); // → {value: "K", done: false} -console.log(okIterador.next()); +console.log(iteradorOk.next()); // → {value: undefined, done: true} ``` @@ -614,56 +616,56 @@ console.log(okIterador.next()); Implementemos una estructura de datos iterable similar a la lista enlazada del ejercicio en el [Capítulo ?](data). Esta vez escribiremos la lista como una clase. ```{includeCode: true} -class List { - constructor(value, rest) { - this.value = value; - this.rest = rest; +class Lista { + constructor(valor, resto) { + this.valor = valor; + this.resto = resto; } - get length() { - return 1 + (this.rest ? this.rest.length : 0); + get longitud() { + return 1 + (this.resto ? this.resto.longitud : 0); } - static fromArray(array) { - let result = null; + static desdeArray(array) { + let resultado = null; for (let i = array.length - 1; i >= 0; i--) { - result = new this(array[i], result); + resultado = new this(array[i], resultado); } return result; } } ``` -Toma en cuenta que `this`, en un método estático, apunta al constructor de la clase, no a una instancia, ya que no hay una instancia disponible cuando se llama a un método estático. +Ten en cuenta que `this`, en un método estático, apunta al constructor de la clase, no a una instancia, ya que no hay una instancia disponible cuando se llama a un método estático. -Iterar sobre una lista debería devolver todos los elementos de la lista desde el principio hasta el final. Escribiremos una clase separada para el iterador. +Iterar sobre una lista debería devolver todos los elementos de la lista desde el principio hasta el final. Vamos a escribir una clase separada para el iterador. {{index "Clase ListIterator"}} ```{includeCode: true} -class ListIterator { - constructor(list) { - this.list = list; +class iteradorDeLista { + constructor(lista) { + this.lista = lista; } next() { - if (this.list == null) { + if (this.lista == null) { return { done: true }; } - let value = this.list.value; - this.list = this.list.rest; + let value = this.lista.valor; + this.lista = this.lista.resto; return { value, done: false }; } } ``` -La clase realiza un seguimiento del progreso de la iteración a través de la lista actualizando su propiedad `list` para moverse al siguiente objeto de lista cada vez que se devuelve un valor, y reporta que ha terminado cuando esa lista está vacía (null). +La clase realiza un seguimiento del progreso de la iteración a través de la lista actualizando su propiedad `lista` para moverse al siguiente objeto de lista cada vez que se devuelve un valor, y reporta que ha terminado cuando esa lista está vacía (null). -Ahora configuraremos la clase `List` para que sea iterable. A lo largo de este libro, ocasionalmente utilizaré la manipulación de prototipos posterior al hecho para agregar métodos a las clases de modo que las piezas individuales de código se mantengan pequeñas y autónomas. En un programa regular, donde no hay necesidad de dividir el código en piezas pequeñas, declararías estos métodos directamente en la clase en su lugar. +Ahora configuraremos la clase `Lista` para que sea iterable. A lo largo de este libro, en ocasiones utilizaré la manipulación de prototipos después de la definición de la clase para añadir métodos, de moco que cada fragmento de código se mantenga pequeño y autónomo. En un programa convencional, donde no hay necesidad de dividir el código en partes pequeñas, estos métodos se declararían directamente dentro de la clase. ```{includeCode: true} -List.prototype[Symbol.iterator] = function() { - return new ListIterator(this); +Lista.prototype[Symbol.iterator] = function() { + return new iteradorDeLista(this); }; ``` @@ -672,7 +674,7 @@ List.prototype[Symbol.iterator] = function() { Ahora podemos iterar sobre una lista con `for`/`of`. ``` -let lista = List.fromArray([1, 2, 3]); +let lista = Lista.desdeArray([1, 2, 3]); for (let elemento of lista) { console.log(elemento); } @@ -683,7 +685,7 @@ for (let elemento of lista) { {{index "spread"}} -La sintaxis `...` en notación de arrays y en llamadas a funciones funciona de forma similar con cualquier objeto iterable. Por ejemplo, puedes usar `[...valor]` para crear un array que contenga los elementos de un objeto iterable arbitrario. +La sintaxis `...` en notación de arrays y en llamadas a funciones funciona de menaera similar con cualquier objeto iterable. Por ejemplo, puedes usar `[...valor]` para crear un array que contenga los elementos de un objeto iterable arbitrario. ``` console.log([... "PCI"]); @@ -694,43 +696,43 @@ console.log([... "PCI"]); {{index "herencia", "lista enlazada", "programación orientada a objetos", "Clase LengthList"}} -Imaginemos que necesitamos un tipo de lista, bastante parecido a la clase `List` que vimos anteriormente, pero como siempre estaremos preguntando por su longitud, no queremos tener que recorrer su `rest` cada vez, en su lugar, queremos almacenar la longitud en cada instancia para un acceso eficiente. +Imaginemos que necesitamos un tipo de lista, bastante parecido a la clase `Lista` que vimos anteriormente, pero como siempre estaremos preguntando por su longitud, no queremos tener que recorrer su `resto` cada vez, en su lugar, queremos almacenar la longitud en cada instancia para un acceso eficiente. {{index "anulación, prototipo"}} -El sistema de prototipos de JavaScript permite crear una _nueva_ clase, muy similar a la clase antigua, pero con nuevas definiciones para algunas de sus propiedades. El prototipo de la nueva clase se deriva del prototipo antiguo pero agrega una nueva definición, por ejemplo, para el `getter` de `length`. +El sistema de prototipos de JavaScript permite crear una _nueva_ clase, muy similar a la clase antigua, pero con nuevas definiciones para algunas de sus propiedades. El prototipo de la nueva clase se deriva del prototipo antiguo pero agrega una nueva definición, por ejemplo, para el `getter` de `longitud`. En términos de programación orientada a objetos, esto se llama _((herencia))_. La nueva clase hereda propiedades y comportamientos de la clase antigua. ```{includeCode: "top_lines: 17"} -class LengthList extends List { - #length; +class ListaLongitud extends Lista { + #longitud; - constructor(valor, rest) { - super(valor, rest); - this.#length = super.length; + constructor(valor, resto) { + super(valor, resto); + this.#longitud = super.longitud; } - get length() { - return this.#length; + get longitud() { + return this.#longitud; } } -console.log(LengthList.fromArray([1, 2, 3]).length); +console.log(ListaLongitud.fromArray([1, 2, 3]).length); // → 3 ``` -El uso de la palabra `extends` indica que esta clase no debería basarse directamente en el prototipo predeterminado de `Object`, sino en alguna otra clase. Esta se llama la _((superclase))_. La clase derivada es la _((subclase))_. +El uso de la palabra `extends` indica que esta clase no debería basarse directamente en el prototipo predeterminado de `Object`, sino en alguna otra clase. A esta se le llama la _((superclase))_. La clase derivada es la _((subclase))_. -Para inicializar una instancia de `LengthList`, el constructor llama al constructor de su superclase a través de la palabra clave `super`. Esto es necesario porque si este nuevo objeto se va a comportar (aproximadamente) como una `List`, va a necesitar las propiedades de instancia que tienen las listas. +Para inicializar una instancia de `ListaLongitud`, el constructor llama al constructor de su superclase a través de la palabra clave `super`. Esto es necesario porque si este nuevo objeto se va a comportar (aproximadamente) como una `Lista`, va a necesitar las propiedades de instancia que tienen las listas. -Luego, el constructor almacena la longitud de la lista en una propiedad privada. Si hubiéramos escrito `this.longitud` ahí, se habría llamado al getter de la propia clase, lo cual no funciona aún, ya que `#longitud` aún no ha sido completado. Podemos usar `super.algo` para llamar a métodos y getters en el prototipo de la superclase, lo cual a menudo es útil. +Luego, el constructor almacena la longitud de la lista en una propiedad privada. Si hubiéramos escrito `this.longitud` ahí, se habría llamado al getter de la propia clase, lo cual no funciona aún, ya que `#longitud` aún no se ha rellenado. Podemos usar `super.algo` para llamar a métodos y getters en el prototipo de la superclase, lo cual a menudo es útil. -La herencia nos permite construir tipos de datos ligeramente diferentes a partir de tipos de datos existentes con relativamente poco trabajo. Es una parte fundamental de la tradición orientada a objetos, junto con la encapsulación y la polimorfismo. Pero, mientras que los dos últimos se consideran generalmente ideas maravillosas, la herencia es más controvertida. +La herencia nos permite construir tipos de datos ligeramente diferentes a partir de tipos de datos existentes con relativamente poco trabajo. Es una parte fundamental de la tradición en la programación orientada a objetos, junto con la encapsulación y la polimorfismo. Pero, mientras que los dos últimos se consideran generalmente ideas fantásticas, la herencia es más controvertida. {{index complejidad, "reutilización", "jerarquía de clases"}} -Mientras que ((encapsulación)) y polimorfismo se pueden utilizar para _separar_ las piezas de código unas de otras, reduciendo el enredo del programa en general, ((herencia)) fundamentalmente ata clases juntas, creando _más_ enredo. Al heredar de una clase, generalmente tienes que saber más sobre cómo funciona que cuando simplemente la usas. La herencia puede ser una herramienta útil para hacer que algunos tipos de programas sean más concisos, pero no debería ser la primera herramienta a la que recurras, y probablemente no deberías buscar activamente oportunidades para construir jerarquías de clases (árboles genealógicos de clases). +Mientras que ((encapsulación)) y polimorfismo se pueden utilizar para _separar_ las piezas de código unas de otras, reduciendo el enredo del programa en general, la ((herencia)) fundamentalmente ata las clases, creando _más_ enredo. Al heredar de una clase, generalmente tienes que saber más sobre cómo funciona que cuando simplemente la usas. La herencia puede ser una herramienta útil para hacer que algunos tipos de programas sean más concisos, pero no debería ser la primera herramienta a la que recurras, y probablemente no deberías buscar activamente oportunidades para construir jerarquías de clases (árboles genealógicos de clases). ## El operador instanceof @@ -740,11 +742,11 @@ A veces es útil saber si un objeto se derivó de una clase específica. Para es ``` console.log( - new LengthList(1, null) instanceof LengthList); + new ListaLongitud(1, null) instanceof ListaLongitud); // → true -console.log(new LengthList(2, null) instanceof List); +console.log(new ListaLongitud(2, null) instanceof Lista); // → true -console.log(new List(3, null) instanceof LengthList); +console.log(new Lista(3, null) instanceof ListaLongitud); // → false console.log([1] instanceof Array); // → true @@ -752,7 +754,7 @@ console.log([1] instanceof Array); {{index herencia}} -El operador podrá ver a través de tipos heredados, por lo que un `LengthList` es una instancia de `List`. El operador también se puede aplicar a constructores estándar como `Array`. Casi todo objeto es una instancia de `Object`. +El operador podrá ver a través de tipos heredados, por lo que un `ListaLongitud` es una instancia de `Lista`. El operador también se puede aplicar a constructores estándar como `Array`. Casi todo objeto es una instancia de `Object`. ## Resumen diff --git a/html/06_object.html b/html/06_object.html index ff0512ea..3815929b 100644 --- a/html/06_object.html +++ b/html/06_object.html @@ -14,86 +14,84 @@

La Vida Secreta de los Objetos

-

Un tipo de dato abstracto se realiza escribiendo un tipo especial de programa [...] que define el tipo en términos de las operaciones que se pueden realizar en él.

+

Un tipo abstracto de datos se implementa escribiendo un tipo especial de programa [...] que define el tipo en función de las operaciones que se pueden realizar sobre él..

-
Barbara Liskov, Programando con Tipos de Datos Abstractos
+
Barbara Liskov, Programming with Abstract Data Types
Ilustración de un conejo junto a su prototipo, una representación esquemática de un conejo
-

El Capítulo 4 introdujo los objetos de JavaScript, como contenedores que almacenan otros datos.

- -

En la cultura de la programación, tenemos algo llamado programación orientada a objetos, un conjunto de técnicas que utilizan objetos como principio central de la organización de programas. Aunque nadie realmente se pone de acuerdo en su definición precisa, la programación orientada a objetos ha dado forma al diseño de muchos lenguajes de programación, incluido JavaScript. Este capítulo describe la forma en que estas ideas se pueden aplicar en JavaScript.

+

En el Capítulo 4 se introdujeron los objetos de JavaScript como contenedores que almacenan otros datos. En la cultura de la programación, la programación orientada a objetos es un conjunto de técnicas que utilizan objetos como el principio central de la organización de programas. Aunque nadie realmente se pone de acuerdo en su definición precisa, la programación orientada a objetos ha dado forma al diseño de muchos lenguajes de programación, incluido JavaScript. Este capítulo describe la forma en que estas ideas se pueden aplicar en JavaScript.

Tipos de Datos Abstractos

-

La idea principal en la programación orientada a objetos es utilizar objetos, o más bien tipos de objetos, como la unidad de organización del programa. Configurar un programa como una serie de tipos de objetos estrictamente separados proporciona una forma de pensar en su estructura y, por lo tanto, de imponer algún tipo de disciplina para evitar que todo se entrelace.

+

La idea principal en la programación orientada a objetos es utilizar objetos (más bien tipos de objetos) como la unidad de organización del programa. Configurar un programa como una serie de tipos de objetos estrictamente separados proporciona una forma de pensar en su estructura y, por lo tanto, de imponer algún tipo de disciplina, evitando que todo se convierta en un lío.

-

La forma de hacer esto es pensar en objetos de alguna manera similar a como pensarías en una batidora eléctrica u otro electrodoméstico para el consumidor. Hay personas que diseñaron y ensamblaron una batidora, y tienen que realizar un trabajo especializado que requiere ciencia de materiales y comprensión de la electricidad. Cubren todo eso con una carcasa de plástico suave, de modo que las personas que solo quieren mezclar masa para panqueques no tengan que preocuparse por todo eso, solo tienen que entender los pocos botones con los que se puede operar la batidora.

+

La forma de hacer esto es pensar en los objetos de alguna manera similar a como pensarías en una batidora eléctrica u otro electrodoméstico. Las personas que diseñan y ensamblan una batidora deben realizar un trabajo especializado que requiere conocimientos de ciencia de materiales y electricidad. Cubren todo eso con una carcasa de plástico para que la gente que solo quiere mezclar masa para tortitas no tenga que preocuparse por todo eso, solo tienen que entender los pocos botones con los que se maneja la batidora.

-

De manera similar, un tipo de dato abstracto, o clase de objeto, es un subprograma que puede contener un código arbitrariamente complicado, pero expone un conjunto limitado de métodos y propiedades que se supone que las personas que trabajan con él deben usar. Esto permite construir programas grandes a partir de varios tipos de electrodomésticos, limitando el grado en que estas diferentes partes están entrelazadas al requerir que solo interactúen entre sí de formas específicas.

+

De manera similar, un tipo de dato abstracto, o clase de objeto, es un subprograma que puede contener un código arbitrariamente complicado, pero que expone un conjunto limitado de métodos y propiedades que se espera que utilicen las personas que trabajan con él. Esto permite construir programas grandes a partir de varios tipos de “electrodomésticos”, limitando el grado en que estas diferentes partes se relacionan al requerir que solo interactúen entre sí de formas específicas.

-

Si se encuentra un problema en una clase de objeto como esta, a menudo se puede reparar, o incluso reescribir completamente, sin afectar el resto del programa.

+

Si se encuentra un problema en una clase de objeto como esta, a menudo se puede reparar, o incluso reescribir completamente, sin afectar el resto del programa. Aún mejor, se pueden utilizar clases de objetos en varios programas diferentes, evitando la necesidad de recrear su funcionalidad desde cero. Puedes pensar también en las estructuras de datos integradas de JavaScript, como arrays y strings, como tales tipos de datos abstractos reutilizables.

-

Incluso mejor, puede ser posible utilizar clases de objetos en varios programas diferentes, evitando la necesidad de recrear su funcionalidad desde cero. Puedes pensar en las estructuras de datos integradas de JavaScript, como arrays y strings, como tipos de datos abstractos reutilizables de este tipo.

+

Cada tipo de dato abstracto tiene una interfaz: la colección de operaciones que el código externo puede realizar en él. Cualquier detalle más allá de dicha interfaz queda encapsulado al tratarse como interno al tipo y de no incumbencia para el resto del programa.

-

Cada tipo de dato abstracto tiene una interfaz, que es la colección de operaciones que el código externo puede realizar en él. Incluso cosas básicas como los números pueden considerarse un tipo de dato abstracto cuya interfaz nos permite sumarlos, multiplicarlos, compararlos, y así sucesivamente. De hecho, la fijación en objetos individuales como la unidad principal de organización en la programación orientada a objetos clásica es un tanto desafortunada, ya que a menudo las piezas de funcionalidad útiles involucran un grupo de diferentes clases de objetos que trabajan estrechamente juntos.

+

Incluso algo tan básico como los números puede considerarse un tipo de dato abstracto, cuya interfaz nos permite sumarlos, multiplicarlos, compararlos, etc. Sin embargo, la programación orientada a objetos clásica suele poner demasiado énfasis en los objetos individuales como unidad fundamental de organización, cuando en realidad muchas funcionalidades útiles surgen de la cooperación entre varias clases de objetos.

Métodos

-

En JavaScript, los métodos no son más que propiedades que contienen valores de función. Este es un método simple:

+

En JavaScript, los métodos no son más que propiedades que contienen valores de función. Aquí hay un método simple:

-
function speak(line) {
-  console.log(`El conejo ${this.type} dice '${line}'`);
+
function hablar(frase) {
+  console.log(`El conejo ${this.tipo} dice '${frase}'`);
 }
-let conejoBlanco = {type: "blanco", speak};
-let conejoHambriento = {type: "hambriento", speak};
+let conejoBlanco = {tipo: "blanco", hablar};
+let conejoHambriento = {tipo: "hambriento", hablar};
 
-conejoBlanco.speak("Oh, mi pelaje y mis bigotes");
+conejoBlanco.hablar("Oh, mi pelaje y mis bigotes");
 // → El conejo blanco dice 'Oh, mi pelaje y mis bigotes'
-conejoHambriento.speak("¿Tienes zanahorias?");
+conejoHambriento.hablar("¿Tienes zanahorias?");
 // → El conejo hambriento dice '¿Tienes zanahorias?'
-

Típicamente, un método necesita hacer algo con el objeto en el que fue invocado. Cuando una función es llamada como método—buscada como propiedad y llamada inmediatamente, como en objeto.método()—la vinculación llamada this en su cuerpo apunta automáticamente al objeto en el que fue llamada.

+

Normalmente, un método tiene que hacer algo con el objeto sobre el que se ha llamado. Cuando una función se llama como método —es decir, se buscada como una propiedad y se llama inmediatamente, como en objeto.método()— la asociación llamada this en el cuerpo de la misma apunta automáticamente al objeto sobre el que se hizo la llamada.

-

Puedes pensar en this como un parámetro extra que se pasa a la función de una manera diferente a los parámetros regulares. Si deseas proveerlo explícitamente, puedes usar el método call de una función, el cual toma el valor de this como su primer argumento y trata los siguientes argumentos como parámetros normales.

+

Puedes pensar en this como un parámetro extra que se pasa a la función de una manera diferente a los parámetros normales. Si quieres darlo explícitamente coimo parámetro, puedes usar el método call de la función, que toma el valor de this como primer argumento y trata los siguientes argumentos como parámetros normales.

-
speak.call(conejoBlanco, "Rápido");
+
hablar.call(conejoBlanco, "Rápido");
 // → El conejo blanco dice 'Rápido'
-

Dado que cada función tiene su propia vinculación this, cuyo valor depende de la forma en que es llamada, no puedes hacer referencia al this del ámbito envolvente en una función regular definida con la palabra clave function.

+

Dado que cada función tiene su propia asociación this, cuyo valor depende de la forma en que es llamada, dentro una función normal definida con la palabra clave function no puedes hacer referencia al this del ámbito en el que esta se encuentra envuelta.

-

Las funciones flecha son diferentes—no vinculan su propio this pero pueden ver la vinculación this del ámbito que las rodea. Por lo tanto, puedes hacer algo como el siguiente código, el cual hace referencia a this desde dentro de una función local:

+

Las funciones flecha son diferentes —no enlazan su propio this sino que pueden acceder a la asociación this del ámbito que las rodea. Por lo tanto, puedes hacer algo como el siguiente código, que hace referencia a this desde dentro de una función local:

-
let buscador = {
-  find(array) {
-    return array.some(v => v == this.value);
+
let buscador = {
+  buscar(array) {
+    return array.some(v => v == this.valor);
   },
-  value: 5
+  valor: 5
 };
-console.log(buscador.find([4, 5]));
+console.log(buscador.buscar([4, 5]));
 // → true
-

Una propiedad como find(array) en una expresión de objeto es una forma abreviada de definir un método. Crea una propiedad llamada find y le asigna una función como su valor.

+

Una propiedad como buscar(array) en una expresión de objeto es una forma abreviada de definir un método. Crea una propiedad llamada buscar y le asigna una función como valor de la misma.

-

Si hubiera escrito el argumento de some usando la palabra clave function, este código no funcionaría.

+

Si hubiera escrito el argumento de some usando la palabra clave function, este código no funcionaría, por lo mencionado más arriba.

Prototipos

-

Entonces, una forma de crear un tipo de conejo abstracto con un método speak sería crear una función de ayuda que tenga un tipo de conejo como parámetro, y devuelva un objeto que contenga eso como su propiedad type y nuestra función speak en su propiedad speak.

+

Una manera de crear un de objeto de tipo conejo con un método hablar sería crear una función auxiliar que tenga un tipo de conejo como su parámetro y devuelva un objeto que contenga dicho tipo como su propiedad tipo y nuestra función de hablar en su propiedad hablar.

-

Todos los conejos comparten ese mismo método. Especialmente para tipos con muchos métodos, sería conveniente tener una forma de mantener los métodos de un tipo en un solo lugar, en lugar de añadirlos a cada objeto individualmente.

+

Todos los conejos comparten ese mismo método. Especialmente para tipos con muchos métodos, estaría bien si hubiera una manera de guardar los métodos del tipo en un solo lugar, en vez de tener que añadirlos a cada objeto individualmente.

-

En JavaScript, los prototipos son la forma de lograr eso. Los objetos pueden estar enlazados a otros objetos, para obtener mágicamente todas las propiedades que ese otro objeto tiene. Los simples objetos creados con la notación {} están enlazados a un objeto llamado Object.prototype.

+

En JavaScript, la manera de hacer eso son los prototipos. Los objetos pueden enlazarse a otros objetos para obtener mágicamente todas las propiedades que ese otro objeto tiene. Los objetos sencillos creados con la notación {} están enlazados a un objeto llamado Object.prototype.

-
let empty = {};
-console.log(empty.toString);
+
let vacío = {};
+console.log(vacío.toString);
 // → function toString(){…}
-console.log(empty.toString());
+console.log(vacío.toString());
 // → [object Object]
-

Parece que acabamos de extraer una propiedad de un objeto vacío. Pero de hecho, toString es un método almacenado en Object.prototype, lo que significa que está disponible en la mayoría de los objetos.

+

Parece que acabamos de extraer una propiedad de un objeto vacío. Pero resulta que toString es un método almacenado en Object.prototype, lo que significa que está disponible en la mayoría de los objetos.

-

Cuando a un objeto se le solicita una propiedad que no tiene, se buscará en su prototipo la propiedad. Si éste no la tiene, se buscará en su prototipo, y así sucesivamente hasta llegar a un objeto que no tiene prototipo (Object.prototype es un objeto de este tipo).

+

Cuando a un objeto se le solicita una propiedad que no tiene, se buscará en su prototipo la propiedad. Si éste no la tiene, se buscará en su prototipo, y así sucesivamente hasta llegar a un objeto que no tiene prototipo (Object.prototype es uno de estos objetos).

console.log(Object.getPrototypeOf({}) == Object.prototype);
 // → true
@@ -102,7 +100,7 @@ 

Como podrás imaginar, Object.getPrototypeOf devuelve el prototipo de un objeto.

-

Muchos objetos no tienen directamente Object.prototype como su prototipo, sino que tienen otro objeto que proporciona un conjunto diferente de propiedades predeterminadas. Las funciones se derivan de Function.prototype, y los arreglos se derivan de Array.prototype.

+

Muchos objetos no tienen directamente Object.prototype como su prototipo, sino que en su lugar tienen otro objeto que les proporciona un conjunto diferente de propiedades predeterminadas. Las funciones se derivan de Function.prototype, y los arrays se derivan de Array.prototype.

console.log(Object.getPrototypeOf(Math.max) ==
             Function.prototype);
@@ -110,143 +108,148 @@ 

// → true

-

Un objeto prototipo de este tipo tendrá a su vez un prototipo, a menudo Object.prototype, de modo que aún proporciona de forma indirecta métodos como toString.

+

Un objeto prototipo de este tipo tendrá a su vez un prototipo, a menudo Object.prototype, de modo que este aún proporciona de forma indirecta métodos como toString.

Puedes utilizar Object.create para crear un objeto con un prototipo específico.

-
let protoRabbit = {
-  speak(line) {
-    console.log(`El conejo ${this.type} dice '${line}'`);
+
let protoConejo = {
+  hablar(frase) {
+    console.log(`El conejo ${this.tipo} dice '${frase}'`);
   }
 };
-let blackRabbit = Object.create(protoRabbit);
-blackRabbit.type = "negro";
-blackRabbit.speak("Soy el miedo y la oscuridad");
-// → El conejo negro dice 'Soy el miedo y la oscuridad'
+let conejoNegro = Object.create(protoConejo); +conejoNegro.tipo = "negro"; +conejoNegro.hablar("Soy miedo y oscuridad"); +// → El conejo negro dice 'Soy miedo y oscuridad'
-

El conejo “proto” actúa como un contenedor para las propiedades que son compartidas por todos los conejos. Un objeto de conejo individual, como el conejo negro, contiene propiedades que se aplican solo a él mismo, en este caso su tipo, y deriva propiedades compartidas de su prototipo.

+

El “proto” conejo actúa como un contenedor para las propiedades que comparten todos los conejos. Un objeto conejo individual, como el conejo negro, contiene propiedades que se aplican solo a él mismo —en este caso su tipo— y hereda las propiedades compartidas de su prototipo.

Clases

-

El sistema de prototipos de JavaScript puede interpretarse como una versión algo libre de los tipos de datos abstractos o clases. Una clase define la forma de un tipo de objeto, los métodos y propiedades que tiene. A dicho objeto se le llama una instancia de la clase.

+

El sistema de prototipos de JavaScript puede interpretarse como una versión algo libre de los tipos de datos abstractos o clases. Una clase define la forma de un tipo de objeto —los métodos y propiedades que tiene. A dicho objeto se le llama una instancia de la clase.

-

Los prototipos son útiles para definir propiedades cuyo valor es compartido por todas las instancias de una clase. Las propiedades que difieren por instancia, como la propiedad type de nuestros conejos, deben ser almacenadas directamente en los objetos mismos.

+

Los prototipos son útiles para definir propiedades cuyo valor es compartido por todas las instancias de una clase. Las propiedades que difieren por instancia, como nuestra propiedad tipo de los conejos, deben ser almacenadas directamente en los objetos mismos.

-

Así que para crear una instancia de una clase, debes hacer un objeto que se derive del prototipo adecuado, pero también debes asegurarte de que él mismo tenga las propiedades que se supone que deben tener las instancias de esta clase. Esto es lo que hace una función constructor.

+

Para crear una instancia de una clase dada, debes hacer un objeto que herede del prototipo adecuado, pero también debes asegurarte de que tenga las propiedades que se supone que deben tener las instancias de esta clase. Esto es lo que hace una función constructor.

-
function makeRabbit(type) {
-  let rabbit = Object.create(protoRabbit);
-  rabbit.type = type;
-  return rabbit;
+
function hacerConejo(tipo) {
+  let conejo = Object.create(protoConejo);
+  conejo.tipo = tipo;
+  return conejo;
 }
-

La notación de class de JavaScript facilita la definición de este tipo de función, junto con un objeto prototype.

+

La notación de class de JavaScript facilita la definición de este tipo de funciones, junto con un objeto prototype.

-
class Rabbit {
-  constructor(type) {
-    this.type = type;
+
class Conejo {
+  constructor(tipo) {
+    this.tipo = tipo;
   }
-  speak(line) {
-    console.log(`El conejo ${this.type} dice '${line}'`);
+  hablar(frase) {
+    console.log(`El conejo ${this.tipo} dice '${frase}'`);
   }
 }
-

La palabra clave class inicia una declaración de clase, que nos permite definir un constructor y un conjunto de métodos juntos. Se pueden escribir cualquier cantidad de métodos dentro de las llaves de la declaración. Este código tiene el efecto de definir un enlace llamado Rabbit, que contiene una función que ejecuta el código en constructor, y tiene una propiedad prototype que contiene el método speak.

+

La palabra clave class inicia una declaración de clase, que nos permite definir un constructor y un conjunto de métodos a la vez. Se puede escribir cualquier cantidad de métodos dentro de las llaves de la declaración. Este código tiene el efecto de definir una asociación llamada Conejo, que contiene una función que ejecuta el código en constructor, y tiene una propiedad prototype que contiene el método hablar.

-

Esta función no puede ser llamada normalmente. Los constructores, en JavaScript, se llaman colocando la palabra clave new delante de ellos. Al hacerlo, se crea un objeto nuevo con el objeto contenido en la propiedad prototype de la función como prototipo, luego se ejecuta la función con this vinculado al nuevo objeto, y finalmente se devuelve el objeto.

+

Esta función no se puede llamar como una función normal. Los constructores, en JavaScript, se llaman colocando la palabra clave new delante de ellos. Al hacerlo, se crea una nueva instancia de objeto cuyo prototipo es el objeto de la propiedad prototype de la función, luego se ejecuta la función con this enlazado al nuevo objeto, y finalmente se devuelve el objeto.

-
let killerRabbit = new Rabbit("asesino");
+
let conejoAsesino = new Rabbit("asesino");
-

De hecho, la palabra clave class se introdujo solo en la edición de JavaScript de 2015. Cualquier función puede ser utilizada como constructor, y antes de 2015 la forma de definir una clase era escribir una función regular y luego manipular su propiedad prototype.

+

De hecho, la palabra clave class se introdujo recién en la edición de JavaScript de 2015. Cualquier función puede ser utilizada como constructor, y antes de 2015 la forma de definir una clase era escribir una función normal y luego manipular su propiedad prototype.

-
function ConejoArcaico(type) {
-  this.type = type;
+
function ConejoArcaico(tipo) {
+  this.tipo = tipo;
 }
 
-ConejoArcaico.prototype.speak = function(line) {
-  console.log(`El conejo ${this.type} dice '${line}'`);
+ConejoArcaico.prototype.hablar = function(frase) {
+  console.log(`El conejo ${this.tipo} dice '${frase}'`);
 };
 
-let conejoEstiloAntiguo = new ConejoArcaico("estilo antiguo");
+let conejoViejaEscuela = new ConejoArcaico("de la vieja escuela");
-

Por esta razón, todas las funciones que no sean de flecha comienzan con una propiedad prototype que contiene un objeto vacío.

+

Por esta razón, todas las funciones que no sean funciones flecha comienzan teniendo una propiedad prototype que contiene un objeto vacío.

Por convención, los nombres de constructores se escriben con mayúscula inicial para que puedan distinguirse fácilmente de otras funciones.

-

Es importante entender la distinción entre la forma en que un prototipo está asociado con un constructor (a través de su propiedad prototype) y la forma en que los objetos tienen un prototipo (que se puede encontrar con Object.getPrototypeOf). El prototipo real de un constructor es Function.prototype ya que los constructores son funciones. Su propiedad prototype contiene el prototipo utilizado para las instancias creadas a través de él.

+

Es importante entender la distinción entre la forma en que un prototipo está asociado a un constructor (a través de su propiedad prototype) y la forma en que los objetos tienen un prototipo (que se puede encontrar con Object.getPrototypeOf). El prototipo real de un constructor es Function.prototype ya que los constructores son funciones. Su propiedad prototype contiene el prototipo utilizado para las instancias creadas a través de él.

-
console.log(Object.getPrototypeOf(Rabbit) ==
+
console.log(Object.getPrototypeOf(Conejo) ==
             Function.prototype);
 // → true
-console.log(Object.getPrototypeOf(killerRabbit) ==
-            Rabbit.prototype);
+console.log(Object.getPrototypeOf(conejoAsesino) ==
+            Conejo.prototype);
 // → true
-

Por lo general, los constructores agregarán algunas propiedades específicas de instancia a this. También es posible declarar propiedades directamente en la declaración de clase. A diferencia de los métodos, dichas propiedades se agregan a los objetos instancia, no al prototipo.

+

Por lo general, los constructores añadirán algunas propiedades específicas por instancia a this. También es posible declarar propiedades directamente en la declaración de clase. A diferencia de los métodos, dichas propiedades se agregan a objetos instancia, y no al prototipo.

-
class Particle {
-  speed = 0;
-  constructor(position) {
-    this.position = position;
+
class Partícula {
+  rapidez = 0;
+  constructor(posición) {
+    this.posición = posición;
   }
 }
-

Al igual que function, class se puede utilizar tanto en declaraciones como en expresiones. Cuando se usa como una expresión, no define un enlace sino que simplemente produce el constructor como un valor. Se te permite omitir el nombre de la clase en una expresión de clase.

+

Al igual que function, class se puede utilizar tanto en declaraciones como en expresiones. Cuando se usa como una expresión, no define una asociación sino que simplemente produce el constructor como un valor. Puedes omitir el nombre de la clase en una expresión de clase.

-
let object = new class { getWord() { return "hello"; } };
-console.log(object.getWord());
-// → hello
+
let objeto = new class { obtenerPalabra() { return "hola"; } };
+console.log(objeto.obtenerPalabra());
+// → hola

Propiedades privadas

-

Es común que las clases definan algunas propiedades y métodos para uso interno, que no forman parte de su interfaz. Estas se llaman propiedades privadas, en contraposición a las públicas, que son parte de la interfaz externa del objeto.

+

Es común que las clases definan algunas propiedades y métodos para uso interno que no forman parte de su interfaz. Estas propiedades se llaman propiedades privadas, en contraposición a las públicas, que son parte de la interfaz externa del objeto.

Para declarar un método privado, coloca un signo # delante de su nombre. Estos métodos solo pueden ser llamados desde dentro de la declaración de la class que los define.

-
class SecretiveObject {
-  #getSecret() {
+
class ObjectoConfidencial {
+  #obtenerSecreto() {
     return "Me comí todas las ciruelas";
   }
-  interrogate() {
-    let deboDecirlo = this.#getSecret();
+  interrogar() {
+    let voyADecirlo = this.#obtenerSecreto();
     return "nunca";
   }
 }
-

Si intentas llamar a #getSecret desde fuera de la clase, obtendrás un error. Su existencia está completamente oculta dentro de la declaración de la clase.

+

Cuando una clase no declara un constructor, automáticamente obtiene un constructor vacío.

+ +

Si intentas llamar a #obtenerSecreto desde fuera de la clase, obtendrás un error. Su existencia está completamente oculta dentro de la declaración de la clase.

-

Para usar propiedades de instancia privadas, debes declararlas. Las propiedades regulares se pueden crear simplemente asignándoles un valor, pero las propiedades privadas deben declararse en la declaración de la clase para estar disponibles en absoluto.

+

Para usar propiedades de instancia privadas, debes declararlas. Las propiedades normales se pueden crear simplemente asignándoles un valor, pero las propiedades privadas deben declararse en la declaración de la clase para estar disponibles.

-

Esta clase implementa un dispositivo para obtener un número entero aleatorio por debajo de un número máximo dado. Solo tiene una propiedad pública: getNumber.

+

Esta clase implementa un dispositivo para obtener un número entero aleatorio menor que un número máximo dado. Solo tiene una propiedad pública: obtenerNúmero.

-
class RandomSource {
+
class FuenteDeAzar {
   #max;
   constructor(max) {
     this.#max = max;
   }
-  getNumber() {
+  obtenerNúmero() {
     return Math.floor(Math.random() * this.#max);
   }
 }
-

Sobrescribiendo propiedades derivadas

+

Sobrescribiendo propiedades heredadas

-

Cuando agregas una propiedad a un objeto, ya sea que esté presente en el prototipo o no, la propiedad se agrega al objeto mismo. Si ya existía una propiedad con el mismo nombre en el prototipo, esta propiedad ya no afectará al objeto, ya que ahora está oculta detrás de la propiedad propia del objeto.

+

Cuando agregas una propiedad a un objeto, esté presente en el prototipo o no, la propiedad se agrega al propio objeto. Si ya existía una propiedad con el mismo nombre en el prototipo, esta propiedad ya no afectará al objeto, ya que quedará oculta tras la propia propiedad del objeto.

-
Rabbit.prototype.teeth = "pequeñas";
-console.log(killerRabbit.teeth);
-// → pequeñas
-killerRabbit.teeth = "largos, afilados y sangrientos";
-console.log(killerRabbit.teeth);
+
Conejo.prototype.dientes = "pequeños";
+console.log(conejoAsesino.dientes);
+// → pequeños
+conejoAsesino.dientes = "largos, afilados y sangrientos";
+console.log(conejoAsesino.dientes);
 // → largos, afilados y sangrientos
-console.log((new Rabbit("básico")).teeth);
-// → pequeñas
-console.log(Rabbit.prototype.teeth);
-// → pequeñas
+console.log((new Conejo("básico")).dientes); +// → pequeños +console.log(Conejo.prototype.dientes); +// → pequeños
+ +

El siguiente diagrama esquematiza la situación después de ejecutar este código. Los prototipos Conejo y Object están detrás de conejoAsesino como una especie telón de fondo, donde se pueden buscar propiedades que no se encuentran en el objeto mismo.

Un diagrama que muestra la estructura de objetos de conejos y sus prototipos. Hay un cuadro para la instancia 'killerRabbit' (que tiene propiedades de instancia como 'tipo'), con sus dos prototipos, 'Rabbit.prototype' (que tiene el método 'hablar') y 'Object.prototype' (que tiene métodos como 'toString') apilados detrás de él.
-

El siguiente diagrama esquematiza la situación después de que se ha ejecutado este código. Los prototipos Rabbit y Object están detrás de killerRabbit como un telón de fondo, donde se pueden buscar propiedades que no se encuentran en el objeto mismo.

Un diagrama que muestra la estructura de objetos de conejos y sus prototipos. Hay un cuadro para la instancia 'killerRabbit' (que tiene propiedades de instancia como 'tipo'), con sus dos prototipos, 'Rabbit.prototype' (que tiene el método 'hablar') y 'Object.prototype' (que tiene métodos como 'toString') apilados detrás de él.
+

N. del T.: En esta traducción no se han traducido las figuras y, por tanto, los textos que aparecen en ellas son los originales. En la figura, killerRabbit es conejoAsesino, teeth es dientes, type es tipo, speak es hablar y Rabbit es Conejo.

+
-

Sobrescribir propiedades que existen en un prototipo puede ser algo útil de hacer. Como muestra el ejemplo de los dientes del conejo, sobrescribir se puede utilizar para expresar propiedades excepcionales en instancias de una clase más genérica de objetos, mientras se permite que los objetos no excepcionales tomen un valor estándar de su prototipo.

+

Sobrescribir propiedades que existen en un prototipo puede ser algo útil. Como muestra el ejemplo de los dientes del conejo, se puede sobrescribir para expresar propiedades excepcionales en instancias de una clase más genérica de objetos, mientras se permite que los objetos no excepcionales adopten un valor estándar de su prototipo.

También se utiliza la sobrescritura para dar a los prototipos estándar de funciones y arrays un método toString diferente al del prototipo básico de objeto.

@@ -256,16 +259,16 @@

1, 2].toString()); // → 1,2

-

Llamar a toString en un array produce un resultado similar a llamar a .join(",") en él—coloca comas entre los valores en el array. Llamar directamente a Object.prototype.toString con un array produce una cadena diferente. Esa función no conoce acerca de los arrays, por lo que simplemente coloca la palabra object y el nombre del tipo entre corchetes.

+

Llamar a toString en un array produce un resultado similar a llamar a .join(",") en él —coloca comas entre los valores en el array. Llamar directamente a Object.prototype.toString con un array produce una cadena diferente. Esa función no conoce acerca de los arrays, por lo que simplemente coloca la palabra object y el nombre del tipo entre corchetes.

console.log(Object.prototype.toString.call([1, 2]));
 // → [object Array]

Mapas

-

Vimos la palabra map utilizada en el capítulo anterior para una operación que transforma una estructura de datos aplicando una función a sus elementos. Por confuso que sea, en programación la misma palabra también se utiliza para una cosa relacionada pero bastante diferente.

+

Vimos la palabra map utilizada en el capítulo anterior para una operación que transforma una estructura de datos aplicando una función a cada uno de sus elementos. Por confuso que sea, en programación la misma palabra también se utiliza para una cosa relacionada pero bastante diferente.

-

Un mapa (sustantivo) es una estructura de datos que asocia valores (las claves) con otros valores. Por ejemplo, podrías querer mapear nombres a edades. Es posible usar objetos para esto.

+

Un mapa (conocido como diccionario en otros contextos) es una estructura de datos que asocia valores (las claves) con otros valores. Por ejemplo, podrías querer mapear nombres a edades. Es posible usar objetos para esto.

let edades = {
   Boris: 39,
@@ -280,32 +283,32 @@ 

"¿Se conoce la edad de toString?", "toString" in edades); // → ¿Se conoce la edad de toString? true

-

Aquí, los nombres de propiedad del objeto son los nombres de las personas, y los valores de las propiedades son sus edades. Pero ciertamente no listamos a nadie con el nombre toString en nuestro mapa. Sin embargo, dado que los objetos simples derivan de Object.prototype, parece que la propiedad está allí.

+

Aquí, los nombres de propiedad del objeto son los nombres de las personas, y los valores de las propiedades son sus edades. Aunque está claro que no hemos incluido a nadie en la lista de nuestro mapa con el nombre toString, dado que los objetos sencillos derivan de Object.prototype, parece que la propiedad sí que está presente ahí.

-

Por lo tanto, usar objetos simples como mapas es peligroso. Hay varias formas posibles de evitar este problema. Primero, es posible crear objetos sin ningún prototipo. Si pasas null a Object.create, el objeto resultante no derivará de Object.prototype y se puede usar de forma segura como un mapa.

+

Por lo tanto, usar objetos simples como mapas es peligroso. Hay varias formas posibles de evitar este problema. Primero, es posible crear objetos sin prototipo. Si pasas null a Object.create, el objeto resultante no derivará de Object.prototype y se puede usar de forma segura como un mapa.

console.log("toString" in Object.create(null));
 // → false
-

Los nombres de las propiedades de los objetos deben ser cadenas. Si necesitas un mapa cuyas claves no puedan convertirse fácilmente en cadenas—como objetos—no puedes usar un objeto como tu mapa.

+

Los nombres de las propiedades de los objetos deben ser cadenas. Si necesitas un mapa cuyas claves no puedan convertirse fácilmente en cadenas —como por ejemplo, objetos— no puedes usar un objeto como tu mapa.

-

Afortunadamente, JavaScript viene con una clase llamada Map que está escrita para este propósito exacto. Almacena un mapeo y permite cualquier tipo de claves.

+

Por suerte, JavaScript viene con una clase llamada Map que está escrita justo para esto. Almacena un mapeo y permite cualquier tipo de claves.

-
let ages = new Map();
-ages.set("Boris", 39);
-ages.set("Liang", 22);
-ages.set("Júlia", 62);
+
let edades = new Map();
+edades.set("Boris", 39);
+edades.set("Liang", 22);
+edades.set("Júlia", 62);
 
-console.log(`Júlia tiene ${ages.get("Júlia")}`);
+console.log(`Júlia tiene ${edades.get("Júlia")}`);
 // → Júlia tiene 62
-console.log("¿Se conoce la edad de Jack?", ages.has("Jack"));
+console.log("¿Se conoce la edad de Jack?", edades.has("Jack"));
 // → ¿Se conoce la edad de Jack? false
-console.log(ages.has("toString"));
+console.log(edades.has("toString"));
 // → false
-

Los métodos set, get y has forman parte de la interfaz del objeto Map. Escribir una estructura de datos que pueda actualizar y buscar rápidamente un gran conjunto de valores no es fácil, pero no tenemos que preocuparnos por eso. Alguien más lo hizo por nosotros, y podemos utilizar su trabajo a través de esta interfaz sencilla.

+

Los métodos set, get y has forman parte de la interfaz del objeto Map. Escribir una estructura de datos que pueda actualizar y buscar rápidamente un gran conjunto de valores no es fácil, pero no tenemos que preocuparnos por eso. Otra persona lo ha hecho por nosotros, y podemos utilizar su trabajo a través de esta sencilla interfaz.

-

Si tienes un objeto simple que necesitas tratar como un mapa por alguna razón, es útil saber que Object.keys devuelve solo las claves propias de un objeto, no las del prototipo. Como alternativa al operador in, puedes utilizar la función Object.hasOwn, que ignora el prototipo del objeto.

+

Si tienes un objeto simple que necesitas tratar como un mapa por algún motivo, es útil saber que Object.keys devuelve solo las claves propias de un objeto, no las del prototipo. Como alternativa al operador in, puedes utilizar la función Object.hasOwn, que ignora el prototipo del objeto.

console.log(Object.hasOwn({x: 1}, "x"));
 // → true
@@ -314,107 +317,107 @@ 

Polimorfismo

-

Cuando llamas a la función String (que convierte un valor a una cadena) en un objeto, llamará al método toString en ese objeto para intentar crear una cadena significativa a partir de él. Mencioné que algunos de los prototipos estándar definen su propia versión de toString para poder crear una cadena que contenga información más útil que "[object Object]". También puedes hacerlo tú mismo.

+

Cuando llamas a la función String (que convierte un valor a una cadena) en un objeto, llamará al método toString en ese objeto para intentar crear una cadena significativa a partir de él. Antes mencioné que algunos de los prototipos estándar definen su propia versión de toString para poder crear una cadena que contenga información más útil que "[object Object]". También puedes hacerlo tú mismo.

-
Rabbit.prototype.toString = function() {
-  return `un conejo ${this.type}`;
+
Conejo.prototype.toString = function() {
+  return `un conejo ${this.tipo}`;
 };
 
-console.log(String(killerRabbit));
+console.log(String(conejoAsesino));
 // → un conejo asesino
-

Este es un ejemplo simple de una idea poderosa. Cuando se escribe un código para trabajar con objetos que tienen una determinada interfaz, en este caso, un método toString, cualquier tipo de objeto que accidentalmente admita esta interfaz puede ser enchufado en el código, y este podrá funcionar con él.

+

Este es un ejemplo simple de una idea poderosa. Cuando se escribe un código para trabajar con objetos que tienen una determinada interfaz (en este caso, un método toString), cualquier tipo de objeto que cumpla con esta interfaz puede integrarse en el código y funcionará correctamente.

-

Esta técnica se llama polimorfismo. El código polimórfico puede trabajar con valores de diferentes formas, siempre y cuando admitan la interfaz que espera.

+

Esta técnica se llama polimorfismo. El código polimórfico puede trabajar con valores de diferentes formas, siempre y cuando admitan la interfaz que este espera.

-

Un ejemplo de una interfaz ampliamente utilizada es la de los objeto similar a un array que tiene una propiedad length que contiene un número, y propiedades numeradas para cada uno de sus elementos. Tanto los arreglos como las cadenas admiten esta interfaz, al igual que varios otros objetos, algunos de los cuales veremos más adelante en los capítulos sobre el navegador. Nuestra implementación de forEach en el Capítulo 5 funciona en cualquier cosa que proporcione esta interfaz. De hecho, también lo hace Array.prototype.forEach.

+

Un ejemplo de una interfaz ampliamente utilizada es la de los objetos similares a un array, que tienen una propiedad length que contiene un número, y propiedades numeradas para cada uno de sus elementos. Tanto los arrays como las cadenas admiten esta interfaz, al igual que otros objetos, algunos de los cuales veremos más adelante en los capítulos sobre el navegador. Nuestra implementación de forEach en el Capítulo 5 funciona en cualquier cosa que proporcione esta interfaz. De hecho, también lo hace Array.prototype.forEach.

-
Array.prototype.forEach.call({
+
Array.prototype.forEach.call({
   length: 2,
   0: "A",
   1: "B"
-}, elt => console.log(elt));
+}, elemento => console.log(elemento));
 // → A
 // → B

Getters, setters y estáticos

-

Las interfaces a menudo contienen propiedades simples, no solo métodos. Por ejemplo, los objetos Map tienen una propiedad size que te dice cuántas claves están almacenadas en ellos.

+

Las interfaces a menudo contienen propiedades simples, no solo métodos. Por ejemplo, los objetos Map tienen una propiedad size que te dice cuántas claves almacenan.

No es necesario que dicho objeto calcule y almacene directamente esa propiedad en la instancia. Incluso las propiedades que se acceden directamente pueden ocultar una llamada a un método. Dichos métodos se llaman getter y se definen escribiendo get delante del nombre del método en una expresión de objeto o declaración de clase.

-
let varyingSize = {
-  get size() {
+
let tamañoCambiante = {
+  get tamaño() {
     return Math.floor(Math.random() * 100);
   }
 };
 
-console.log(varyingSize.size);
+console.log(tamañoCambiante.tamaño);
 // → 73
-console.log(varyingSize.size);
+console.log(tamañoCambiante.tamaño);
 // → 49
-

Cada vez que alguien lee la propiedad size de este objeto, se llama al método asociado. Puedes hacer algo similar cuando se escribe en una propiedad, utilizando un setter.

+

Cada vez que alguien lee la propiedad tamaño de este objeto, se llama al método asociado. Puedes hacer algo similar cuando se escribe en una propiedad, utilizando un setter.

-
class Temperature {
+
class Temperatura {
   constructor(celsius) {
     this.celsius = celsius;
   }
   get fahrenheit() {
     return this.celsius * 1.8 + 32;
   }
-  set fahrenheit(value) {
-    this.celsius = (value - 32) / 1.8;
+  set fahrenheit(valor) {
+    this.celsius = (valor - 32) / 1.8;
   }
 
-  static fromFahrenheit(value) {
-    return new Temperature((value - 32) / 1.8);
+  static fromFahrenheit(valor) {
+    return new Temperatura((valor - 32) / 1.8);
   }
 }
 
-let temp = new Temperature(22);
+let temp = new Temperatura(22);
 console.log(temp.fahrenheit);
 // → 71.6
 temp.fahrenheit = 86;
 console.log(temp.celsius);
 // → 30
-

La clase Temperature te permite leer y escribir la temperatura en grados Celsius o grados Fahrenheit, pero internamente solo almacena Celsius y convierte automáticamente de y a Celsius en el getter y setter de fahrenheit.

+

La clase Temperatura te permite leer y escribir la temperatura en grados Celsius o grados Fahrenheit, pero internamente solo almacena Celsius y convierte automáticamente de y a Celsius en el getter y setter de fahrenheit.

A veces quieres adjuntar algunas propiedades directamente a tu función constructora, en lugar de al prototipo. Estos métodos no tendrán acceso a una instancia de clase, pero pueden, por ejemplo, usarse para proporcionar formas adicionales de crear instancias.

-

Dentro de una declaración de clase, los métodos o propiedades que tienen static escrito antes de su nombre se almacenan en el constructor. Por lo tanto, la clase Temperature te permite escribir Temperature.fromFahrenheit(100) para crear una temperatura usando grados Fahrenheit.

+

Dentro de una declaración de clase, los métodos o propiedades que tienen static escrito antes de su nombre se almacenan en el constructor. Por lo tanto, la clase Temperatura te permite escribir Temperatura.fromFahrenheit(100) para crear una temperatura usando grados Fahrenheit.

Símbolos

-

Mencioné en el Capítulo 4 que un bucle for/of puede recorrer varios tipos de estructuras de datos. Este es otro caso de polimorfismo: tales bucles esperan que la estructura de datos exponga una interfaz específica, la cual hacen los arrays y las cadenas. ¡Y también podemos agregar esta interfaz a nuestros propios objetos! Pero antes de hacerlo, debemos echar un vistazo breve al tipo de símbolo.

+

Mencioné en el Capítulo 4 que un bucle for/of puede recorrer varios tipos de estructuras de datos. Este es otro caso de polimorfismo: tales bucles esperan que la estructura de datos exponga una interfaz específica, lo cual hacen por ejemplo los arrays y las cadenas. ¡Y también podemos agregar esta interfaz a nuestros propios objetos! Pero antes de hacerlo, debemos echar un vistazo breve al tipo símbolo.

Es posible que múltiples interfaces utilicen el mismo nombre de propiedad para diferentes cosas. Por ejemplo, en objetos similares a arrays, length se refiere a la cantidad de elementos en la colección. Pero una interfaz de objeto que describa una ruta de senderismo podría usar length para proporcionar la longitud de la ruta en metros. No sería posible que un objeto cumpla con ambas interfaces.

-

Un objeto que intente ser una ruta y similar a un array (quizás para enumerar sus puntos de referencia) es algo un tanto improbable, y este tipo de problema no es tan común en la práctica. Pero para cosas como el protocolo de iteración, los diseñadores del lenguaje necesitaban un tipo de propiedad que realmente no entrara en conflicto con ninguna otra. Por lo tanto, en 2015, se agregaron los símbolos al lenguaje.

+

Un objeto que intente ser una ruta y similar a un array (quizás para enumerar sus puntos de referencia) es algo un tanto improbable, y este tipo de problema no es tan común en la práctica. Sin embargo, para cosas como el protocolo de iteración, los diseñadores del lenguaje necesitaban un tipo de propiedad que realmente no entrara en conflicto con ninguna otra. Por lo tanto, en 2015, se agregaron los símbolos al lenguaje.

-

La mayoría de las propiedades, incluidas todas las propiedades que hemos visto hasta ahora, se nombran con cadenas. Pero también es posible usar símbolos como nombres de propiedades. Los símbolos son valores creados con la función Symbol. A diferencia de las cadenas, los símbolos recién creados son únicos: no puedes crear el mismo símbolo dos veces.

+

La mayoría de las propiedades, incluidas todas las propiedades que hemos visto hasta ahora, se nombran con cadenas. Pero también es posible usar símbolos como nombres de propiedades. Los símbolos son valores creados con la función Symbol. A diferencia de las cadenas, un símbolo recién creado es único: no puedes crear el mismo símbolo dos veces.

-
let sym = Symbol("nombre");
-console.log(sym == Symbol("nombre"));
+
let símbolo = Symbol("nombre");
+console.log(símbolo == Symbol("nombre"));
 // → false
-Rabbit.prototype[sym] = 55;
-console.log(killerRabbit[sym]);
+Conejo.prototype[símbolo] = 55;
+console.log(conejoAsesino[símbolo]);
 // → 55
-

La cadena que pasas a Symbol se incluye cuando la conviertes en una cadena y puede facilitar reconocer un símbolo cuando, por ejemplo, se muestra en la consola. Pero no tiene otro significado más allá de eso: varios símbolos pueden tener el mismo nombre.

+

La cadena que pasas a Symbol se incluye cuando la conviertes en una cadena y puede facilitar reconocer un símbolo cuando, por ejemplo, se muestra en la consola. Pero no tiene otro significado más allá de eso — puede haber varios símbolos con el mismo nombre.

Ser tanto únicos como utilizables como nombres de propiedades hace que los símbolos sean adecuados para definir interfaces que pueden convivir pacíficamente junto a otras propiedades, independientemente de cuáles sean sus nombres.

-
const longitud = Symbol("longitud");
-Array.prototype[longitud] = 0;
+
const length = Symbol("length");
+Array.prototype[length] = 0;
 
 console.log([1, 2].length);
 // → 2
-console.log([1, 2][longitud]);
+console.log([1, 2][length]);
 // → 0
-

Es posible incluir propiedades de símbolos en expresiones de objetos y clases mediante el uso de corchetes. Esto hace que la expresión entre los corchetes se evalúe para producir el nombre de la propiedad, análogo a la notación de acceso a propiedades mediante corchetes cuadrados.

+

Es posible incluir propiedades que sean símbolos en expresiones de objetos y clases mediante el uso de corchetes. Esto hace que la expresión entre los corchetes se evalúe para producir el nombre de la propiedad, análogo a la notación de acceso a propiedades mediante corchetes.

let miViaje = {
   longitud: 2,
@@ -425,75 +428,75 @@ 

// → 21500 2

-

La interfaz del iterador

+

La interfaz iterador

Se espera que el objeto proporcionado a un bucle for/of sea iterable. Esto significa que tiene un método nombrado con el símbolo Symbol.iterator (un valor de símbolo definido por el lenguaje, almacenado como una propiedad de la función Symbol).

-

Cuando se llama, ese método debería devolver un objeto que proporcione una segunda interfaz, iterador. Este es lo que realmente itera. Tiende un método next que devuelve el próximo resultado. Ese resultado debería ser un objeto con una propiedad value que proporciona el siguiente valor, si lo hay, y una propiedad done, que debería ser true cuando no hay más resultados y false en caso contrario.

+

Cuando se llama, ese método debería devolver un objeto que proporcione una segunda interfaz, iterador. Esto es lo que realmente se itera. Tiene un método next que devuelve el siguiente resultado. Ese resultado debería ser un objeto con una propiedad value que proporciona el siguiente valor, si lo hay, y una propiedad done, que debería ser true cuando no hay más resultados y false en caso contrario.

-

Ten en cuenta que los nombres de propiedad next, value y done son simples cadenas, no símbolos. Solo Symbol.iterator, que probablemente se agregará a muchos objetos diferentes, es un símbolo real.

+

Ten en cuenta que los nombres de propiedad next, value y done son simples cadenas, no símbolos. Solo Symbol.iterator, que probablemente se agregará a muchos objetos diferentes, es realmente un símbolo.

Podemos usar esta interfaz directamente nosotros mismos.

-
let okIterador = "OK"[Symbol.iterator]();
-console.log(okIterador.next());
+
let iteradorOk = "OK"[Symbol.iterator]();
+console.log(iteradorOk.next());
 // → {value: "O", done: false}
-console.log(okIterador.next());
+console.log(iteradorOk.next());
 // → {value: "K", done: false}
-console.log(okIterador.next());
+console.log(iteradorOk.next());
 // → {value: undefined, done: true}

Implementemos una estructura de datos iterable similar a la lista enlazada del ejercicio en el Capítulo 4. Esta vez escribiremos la lista como una clase.

-
class List {
-  constructor(value, rest) {
-    this.value = value;
-    this.rest = rest;
+
class Lista {
+  constructor(valor, resto) {
+    this.valor = valor;
+    this.resto = resto;
   }
 
-  get length() {
-    return 1 + (this.rest ? this.rest.length : 0);
+  get longitud() {
+    return 1 + (this.resto ? this.resto.longitud : 0);
   }
 
-  static fromArray(array) {
-    let result = null;
+  static desdeArray(array) {
+    let resultado = null;
     for (let i = array.length - 1; i >= 0; i--) {
-      result = new this(array[i], result);
+      resultado = new this(array[i], resultado);
     }
     return result;
   }
 }
-

Toma en cuenta que this, en un método estático, apunta al constructor de la clase, no a una instancia, ya que no hay una instancia disponible cuando se llama a un método estático.

+

Ten en cuenta que this, en un método estático, apunta al constructor de la clase, no a una instancia, ya que no hay una instancia disponible cuando se llama a un método estático.

-

Iterar sobre una lista debería devolver todos los elementos de la lista desde el principio hasta el final. Escribiremos una clase separada para el iterador.

+

Iterar sobre una lista debería devolver todos los elementos de la lista desde el principio hasta el final. Vamos a escribir una clase separada para el iterador.

-
class ListIterator {
-  constructor(list) {
-    this.list = list;
+
class iteradorDeLista {
+  constructor(lista) {
+    this.lista = lista;
   }
 
   next() {
-    if (this.list == null) {
+    if (this.lista == null) {
       return { done: true };
     }
-    let value = this.list.value;
-    this.list = this.list.rest;
+    let value = this.lista.valor;
+    this.lista = this.lista.resto;
     return { value, done: false };
   }
 }
-

La clase realiza un seguimiento del progreso de la iteración a través de la lista actualizando su propiedad list para moverse al siguiente objeto de lista cada vez que se devuelve un valor, y reporta que ha terminado cuando esa lista está vacía (null).

+

La clase realiza un seguimiento del progreso de la iteración a través de la lista actualizando su propiedad lista para moverse al siguiente objeto de lista cada vez que se devuelve un valor, y reporta que ha terminado cuando esa lista está vacía (null).

-

Ahora configuraremos la clase List para que sea iterable. A lo largo de este libro, ocasionalmente utilizaré la manipulación de prototipos posterior al hecho para agregar métodos a las clases de modo que las piezas individuales de código se mantengan pequeñas y autónomas. En un programa regular, donde no hay necesidad de dividir el código en piezas pequeñas, declararías estos métodos directamente en la clase en su lugar.

+

Ahora configuraremos la clase Lista para que sea iterable. A lo largo de este libro, en ocasiones utilizaré la manipulación de prototipos después de la definición de la clase para añadir métodos, de moco que cada fragmento de código se mantenga pequeño y autónomo. En un programa convencional, donde no hay necesidad de dividir el código en partes pequeñas, estos métodos se declararían directamente dentro de la clase.

-
List.prototype[Symbol.iterator] = function() {
-  return new ListIterator(this);
+
Lista.prototype[Symbol.iterator] = function() {
+  return new iteradorDeLista(this);
 };

Ahora podemos iterar sobre una lista con for/of.

-
let lista = List.fromArray([1, 2, 3]);
+
let lista = Lista.desdeArray([1, 2, 3]);
 for (let elemento of lista) {
   console.log(elemento);
 }
@@ -501,60 +504,60 @@ 

// → 2 // → 3

-

La sintaxis ... en notación de arrays y en llamadas a funciones funciona de forma similar con cualquier objeto iterable. Por ejemplo, puedes usar [...valor] para crear un array que contenga los elementos de un objeto iterable arbitrario.

+

La sintaxis ... en notación de arrays y en llamadas a funciones funciona de menaera similar con cualquier objeto iterable. Por ejemplo, puedes usar [...valor] para crear un array que contenga los elementos de un objeto iterable arbitrario.

console.log([... "PCI"]);
 // → ["P", "C", "I"]

Herencia

-

Imaginemos que necesitamos un tipo de lista, bastante parecido a la clase List que vimos anteriormente, pero como siempre estaremos preguntando por su longitud, no queremos tener que recorrer su rest cada vez, en su lugar, queremos almacenar la longitud en cada instancia para un acceso eficiente.

+

Imaginemos que necesitamos un tipo de lista, bastante parecido a la clase Lista que vimos anteriormente, pero como siempre estaremos preguntando por su longitud, no queremos tener que recorrer su resto cada vez, en su lugar, queremos almacenar la longitud en cada instancia para un acceso eficiente.

-

El sistema de prototipos de JavaScript permite crear una nueva clase, muy similar a la clase antigua, pero con nuevas definiciones para algunas de sus propiedades. El prototipo de la nueva clase se deriva del prototipo antiguo pero agrega una nueva definición, por ejemplo, para el getter de length.

+

El sistema de prototipos de JavaScript permite crear una nueva clase, muy similar a la clase antigua, pero con nuevas definiciones para algunas de sus propiedades. El prototipo de la nueva clase se deriva del prototipo antiguo pero agrega una nueva definición, por ejemplo, para el getter de longitud.

En términos de programación orientada a objetos, esto se llama herencia. La nueva clase hereda propiedades y comportamientos de la clase antigua.

-
class LengthList extends List {
-  #length;
+
class ListaLongitud extends Lista {
+  #longitud;
 
-  constructor(valor, rest) {
-    super(valor, rest);
-    this.#length = super.length;
+  constructor(valor, resto) {
+    super(valor, resto);
+    this.#longitud = super.longitud;
   }
 
-  get length() {
-    return this.#length;
+  get longitud() {
+    return this.#longitud;
   }
 }
 
-console.log(LengthList.fromArray([1, 2, 3]).length);
+console.log(ListaLongitud.fromArray([1, 2, 3]).length);
 // → 3
-

El uso de la palabra extends indica que esta clase no debería basarse directamente en el prototipo predeterminado de Object, sino en alguna otra clase. Esta se llama la superclase. La clase derivada es la subclase.

+

El uso de la palabra extends indica que esta clase no debería basarse directamente en el prototipo predeterminado de Object, sino en alguna otra clase. A esta se le llama la superclase. La clase derivada es la subclase.

-

Para inicializar una instancia de LengthList, el constructor llama al constructor de su superclase a través de la palabra clave super. Esto es necesario porque si este nuevo objeto se va a comportar (aproximadamente) como una List, va a necesitar las propiedades de instancia que tienen las listas.

+

Para inicializar una instancia de ListaLongitud, el constructor llama al constructor de su superclase a través de la palabra clave super. Esto es necesario porque si este nuevo objeto se va a comportar (aproximadamente) como una Lista, va a necesitar las propiedades de instancia que tienen las listas.

-

Luego, el constructor almacena la longitud de la lista en una propiedad privada. Si hubiéramos escrito this.longitud ahí, se habría llamado al getter de la propia clase, lo cual no funciona aún, ya que #longitud aún no ha sido completado. Podemos usar super.algo para llamar a métodos y getters en el prototipo de la superclase, lo cual a menudo es útil.

+

Luego, el constructor almacena la longitud de la lista en una propiedad privada. Si hubiéramos escrito this.longitud ahí, se habría llamado al getter de la propia clase, lo cual no funciona aún, ya que #longitud aún no se ha rellenado. Podemos usar super.algo para llamar a métodos y getters en el prototipo de la superclase, lo cual a menudo es útil.

-

La herencia nos permite construir tipos de datos ligeramente diferentes a partir de tipos de datos existentes con relativamente poco trabajo. Es una parte fundamental de la tradición orientada a objetos, junto con la encapsulación y la polimorfismo. Pero, mientras que los dos últimos se consideran generalmente ideas maravillosas, la herencia es más controvertida.

+

La herencia nos permite construir tipos de datos ligeramente diferentes a partir de tipos de datos existentes con relativamente poco trabajo. Es una parte fundamental de la tradición en la programación orientada a objetos, junto con la encapsulación y la polimorfismo. Pero, mientras que los dos últimos se consideran generalmente ideas fantásticas, la herencia es más controvertida.

-

Mientras que encapsulación y polimorfismo se pueden utilizar para separar las piezas de código unas de otras, reduciendo el enredo del programa en general, herencia fundamentalmente ata clases juntas, creando más enredo. Al heredar de una clase, generalmente tienes que saber más sobre cómo funciona que cuando simplemente la usas. La herencia puede ser una herramienta útil para hacer que algunos tipos de programas sean más concisos, pero no debería ser la primera herramienta a la que recurras, y probablemente no deberías buscar activamente oportunidades para construir jerarquías de clases (árboles genealógicos de clases).

+

Mientras que encapsulación y polimorfismo se pueden utilizar para separar las piezas de código unas de otras, reduciendo el enredo del programa en general, la herencia fundamentalmente ata las clases, creando más enredo. Al heredar de una clase, generalmente tienes que saber más sobre cómo funciona que cuando simplemente la usas. La herencia puede ser una herramienta útil para hacer que algunos tipos de programas sean más concisos, pero no debería ser la primera herramienta a la que recurras, y probablemente no deberías buscar activamente oportunidades para construir jerarquías de clases (árboles genealógicos de clases).

El operador instanceof

A veces es útil saber si un objeto se derivó de una clase específica. Para esto, JavaScript proporciona un operador binario llamado instanceof.

-
console.log(
-  new LengthList(1, null) instanceof LengthList);
+
console.log(
+  new ListaLongitud(1, null) instanceof ListaLongitud);
 // → true
-console.log(new LengthList(2, null) instanceof List);
+console.log(new ListaLongitud(2, null) instanceof Lista);
 // → true
-console.log(new List(3, null) instanceof LengthList);
+console.log(new Lista(3, null) instanceof ListaLongitud);
 // → false
 console.log([1] instanceof Array);
 // → true
-

El operador podrá ver a través de tipos heredados, por lo que un LengthList es una instancia de List. El operador también se puede aplicar a constructores estándar como Array. Casi todo objeto es una instancia de Object.

+

El operador podrá ver a través de tipos heredados, por lo que un ListaLongitud es una instancia de Lista. El operador también se puede aplicar a constructores estándar como Array. Casi todo objeto es una instancia de Object.

Resumen

From 059f9a2d0e2d9ce8243665e7f402b51a93601cc0 Mon Sep 17 00:00:00 2001 From: ckdvk Date: Thu, 13 Feb 2025 23:46:41 +0800 Subject: [PATCH 17/36] =?UTF-8?q?revisados=20ejercicios=20cap=C3=ADtulo=20?= =?UTF-8?q?6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 06_object.md | 34 +++++++++++++++++----------------- html/06_object.html | 34 +++++++++++++++++----------------- src/chapter_info.mjs | 2 +- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/06_object.md b/06_object.md index 1e2f7c4e..db5868d6 100644 --- a/06_object.md +++ b/06_object.md @@ -738,7 +738,7 @@ Mientras que ((encapsulación)) y polimorfismo se pueden utilizar para _separar_ {{index tipo, "operador instanceof", constructor, objeto}} -A veces es útil saber si un objeto se derivó de una clase específica. Para esto, JavaScript proporciona un operador binario llamado `instanceof`. +A veces es útil saber si un objeto hereda de una clase específica. Para esto, JavaScript proporciona un operador binario llamado `instanceof`. ``` console.log( @@ -758,17 +758,17 @@ El operador podrá ver a través de tipos heredados, por lo que un `ListaLongitu ## Resumen -Los objetos hacen más que simplemente contener sus propias propiedades. Tienen prototipos, que son otros objetos. Actuarán como si tuvieran propiedades que no tienen siempre y cuando su prototipo tenga esa propiedad. Los objetos simples tienen `Object.prototype` como su prototipo. +Los objetos son más que simples contenedores de sus propias propiedades. Tienen prototipos, que son otros objetos. Actuarán como si tuvieran propiedades que no tienen siempre y cuando su prototipo tenga esa propiedad. Los objetos simples tienen el prototipo `Object.prototype`. -Los constructores, que son funciones cuyos nombres generalmente comienzan con una letra mayúscula, se pueden usar con el operador `new` para crear nuevos objetos. El prototipo del nuevo objeto será el objeto encontrado en la propiedad `prototype` del constructor. Puedes sacar buen provecho de esto poniendo las propiedades que comparten todos los valores de un tipo dado en su prototipo. Existe una notación de `class` que proporciona una forma clara de definir un constructor y su prototipo. +Los constructores, que son funciones cuyos nombres generalmente comienzan con una letra mayúscula, se pueden usar con el operador `new` para crear nuevos objetos. El prototipo del nuevo objeto será el objeto encontrado en la propiedad `prototype` del constructor. Puedes aprovechar esto poniendo poniendo las propiedades que comparten todos los valores de un tipo dado en su prototipo. Existe una notación de `class` que proporciona una forma clara de definir un constructor y su prototipo. -Puedes definir getters y setters para llamar secretamente a métodos cada vez que se accede a una propiedad de un objeto. Los métodos estáticos son métodos almacenados en el constructor de una clase, en lugar de en su prototipo. +Puedes definir getters y setters para llamar implícitamente a métodos cada vez que se accede a una propiedad de un objeto. Los métodos estáticos son métodos almacenados en el constructor de una clase, en lugar de en su prototipo. El operador `instanceof` puede, dado un objeto y un constructor, decirte si ese objeto es una instancia de ese constructor. -Una cosa útil que se puede hacer con objetos es especificar una interfaz para ellos y decirle a todo el mundo que se supone que deben comunicarse con tu objeto solo a través de esa interfaz. El resto de los detalles que componen tu objeto están ahora _encapsulados_, escondidos detrás de la interfaz. Puedes usar propiedades privadas para ocultar una parte de tu objeto del mundo exterior. +Algo útil que se puede hacer con objetos es especificar una interfaz para ellos y decirle a todo el mundo que se supone que deben comunicarse con tu objeto solo a través de esa interfaz. El resto de los detalles que componen tu objeto están ahora _encapsulados_, escondidos detrás de la interfaz. Puedes usar propiedades privadas para ocultar una parte de tu objeto al mundo exterior. -Más de un tipo puede implementar la misma interfaz. El código escrito para usar una interfaz automáticamente sabe cómo trabajar con cualquier número de objetos diferentes que proporcionen la interfaz. Esto se llama _polimorfismo_. +Una interfaz puede ser implementada por más de un tipo. Un código escrito para utilizar una interfaz automáticamente sabe cómo tratar con cualquier objeto que implemente la misma interfaz. Esto se llama _polimorfismo_. Cuando se implementan múltiples clases que difieren solo en algunos detalles, puede ser útil escribir las nuevas clases como _subclases_ de una clase existente, _heredando_ parte de su comportamiento. @@ -776,7 +776,7 @@ Cuando se implementan múltiples clases que difieren solo en algunos detalles, p {{id exercise_vector}} -### Un tipo de vector +### Un tipo vector {{index dimensions, "Clase Vec", coordenadas, "vector (ejercicio)"}} @@ -784,7 +784,7 @@ Escribe una clase `Vec` que represente un vector en el espacio bidimensional. To {{index "adición", "sustracción"}} -Dale a la clase `Vec` dos métodos en su prototipo, `plus` y `minus`, que tomen otro vector como parámetro y devuelvan un nuevo vector que tenga la suma o la diferencia de los valores _x_ e _y_ de los dos vectores (`this` y el parámetro). +Dale al prototipo de `Vec` dos métodos, `plus` y `minus`, que tomen otro vector como parámetro y devuelvan un nuevo vector que tenga la suma o la diferencia de los valores _x_ e _y_ de los dos vectores (`this` y el parámetro). Agrega una propiedad ((getter)) `length` al prototipo que calcule la longitud del vector, es decir, la distancia del punto (_x_, _y_) desde el origen (0, 0). @@ -806,11 +806,11 @@ if}} {{index "vector (exercise)"}} -Mira de nuevo el ejemplo de la clase `Rabbit` si no estás seguro de cómo se ven las declaraciones de `class`. +Mira de nuevo el ejemplo de la clase `Conejo` si no estás seguro de cómo se ven las declaraciones de `class`. {{index "Pitágoras", "defineProperty function", "raíz cuadrada", "Math.sqrt function"}} -Agregar una propiedad getter al constructor se puede hacer poniendo la palabra `get` antes del nombre del método. Para calcular la distancia desde (0, 0) hasta (x, y), puedes usar el teorema de Pitágoras, que dice que el cuadrado de la distancia que estamos buscando es igual al cuadrado de la coordenada x más el cuadrado de la coordenada y. Por lo tanto, [√(x^2^ + y^2^)]{if html}[[$\sqrt{x^2 + y^2}$]{latex}]{if tex} es el número que buscas. `Math.sqrt` es la forma de calcular una raíz cuadrada en JavaScript y `x ** 2` se puede usar para elevar al cuadrado un número. +Agregar una propiedad getter al constructor se puede hacer poniendo la palabra `get` antes del nombre del método. Para calcular la distancia desde (0, 0) hasta (_x_, _y_), puedes usar el teorema de Pitágoras, que dice que el cuadrado de la distancia que estamos buscando es igual al cuadrado de la coordenada _x_ más el cuadrado de la coordenada _y_. Por lo tanto, [√(_x_^2^ + _y_^2^)]{if html}[[$\sqrt{x^2 + y^2}$]{latex}]{if tex} es el número que buscas. `Math.sqrt` es la forma de calcular una raíz cuadrada en JavaScript y `x ** 2` se puede usar para elevar al cuadrado un número. hint}} @@ -820,11 +820,11 @@ hint}} {{id groups}} -El entorno estándar de JavaScript proporciona otra estructura de datos llamada `Set`. Al igual que una instancia de `Map`, un conjunto contiene una colección de valores. A diferencia de `Map`, no asocia otros valores con esos, solo realiza un seguimiento de qué valores forman parte del conjunto. Un valor puede formar parte de un conjunto solo una vez: agregarlo nuevamente no tiene ningún efecto. +El entorno estándar de JavaScript proporciona otra estructura de datos llamada `Set`. Al igual que una instancia de `Map`, un conjunto contiene una colección de valores. A diferencia de `Map`, no asocia otros valores con ellos, solo realiza un seguimiento de qué valores forman parte del conjunto. Un valor puede formar parte de un conjunto solo una vez: agregarlo nuevamente no tiene ningún efecto. {{index "método add", "método delete", "método has"}} -Escribe una clase llamada `Group` (ya que `Set` está siendo utilizado). Al igual que `Set`, tiene los métodos `add`, `delete` y `has`. Su constructor crea un grupo vacío, `add` agrega un valor al grupo (pero solo si aún no es miembro), `delete` elimina su argumento del grupo (si era miembro), y `has` devuelve un valor booleano que indica si su argumento es miembro del grupo. +Escribe una clase llamada `Group` (ya que `Set` está siendo utilizado). Como `Set`, tiene que tener los métodos `add`, `delete` y `has`. Su constructor crea un grupo vacío, `add` agrega un valor al grupo (pero solo si aún no es miembro), `delete` elimina su argumento del grupo (si era miembro), y `has` devuelve un valor booleano que indica si su argumento es miembro del grupo. {{index "operador ===", "método indexOf"}} @@ -862,7 +862,7 @@ La forma más sencilla de hacer esto es almacenar un array de miembros del grupo {{index "método push"}} -El constructor de tu clase puede establecer la colección de miembros en un array vacío. Cuando se llama a `add`, debe verificar si el valor dado está en el array o agregarlo, por ejemplo con `push`, de lo contrario. +El constructor de tu clase puede establecer la colección de miembros en un array vacío. Cuando se llama a `add`, debe verificar si el valor dado está en el array y agregarlo, por ejemplo con `push`, de lo contrario. {{index "método filter"}} @@ -880,11 +880,11 @@ hint}} {{id group_iterator}} -Haz que la clase `Group` del ejercicio anterior sea iterable. Refiérete a la sección sobre la interfaz del iterador anteriormente en el capítulo si no tienes claro la forma exacta de la interfaz. +Haz que la clase `Group` del ejercicio anterior sea iterable. Mira la sección sobre la interfaz iterador anteriormente en el capítulo si no tienes claro la forma exacta de la interfaz. -Si utilizaste un array para representar los miembros del grupo, no devuelvas simplemente el iterador creado al llamar al método `Symbol.iterator` en el array. Eso funcionaría, pero va en contra del propósito de este ejercicio. +Si usaste un array para representar los miembros del grupo, no devuelvas simplemente el iterador creado al llamar al método `Symbol.iterator` en el array. Eso funcionaría, pero va en contra del propósito de este ejercicio. -Está bien si tu iterador se comporta de manera extraña cuando el grupo se modifica durante la iteración. +No pasa nada si tu iterador se comporta de manera extraña cuando el grupo se modifica durante la iteración. {{if interactive @@ -907,6 +907,6 @@ if}} Probablemente valga la pena definir una nueva clase `GroupIterator`. Las instancias del iterador deberían tener una propiedad que rastree la posición actual en el grupo. Cada vez que se llama a `next`, verifica si ha terminado y, si no, avanza más allá del valor actual y lo devuelve. -La clase `Group` en sí misma obtiene un método nombrado `Symbol.iterator` que, al ser llamado, devuelve una nueva instancia de la clase iteradora para ese grupo. +La clase `Group` en sí misma obtiene un método llamado `Symbol.iterator` que, al ser llamado, devuelve una nueva instancia de la clase iteradora para ese grupo. hint}} \ No newline at end of file diff --git a/html/06_object.html b/html/06_object.html index 3815929b..681360f8 100644 --- a/html/06_object.html +++ b/html/06_object.html @@ -545,7 +545,7 @@

El operador instanceof

-

A veces es útil saber si un objeto se derivó de una clase específica. Para esto, JavaScript proporciona un operador binario llamado instanceof.

+

A veces es útil saber si un objeto hereda de una clase específica. Para esto, JavaScript proporciona un operador binario llamado instanceof.

console.log(
   new ListaLongitud(1, null) instanceof ListaLongitud);
@@ -561,27 +561,27 @@ 

Resumen

-

Los objetos hacen más que simplemente contener sus propias propiedades. Tienen prototipos, que son otros objetos. Actuarán como si tuvieran propiedades que no tienen siempre y cuando su prototipo tenga esa propiedad. Los objetos simples tienen Object.prototype como su prototipo.

+

Los objetos son más que simples contenedores de sus propias propiedades. Tienen prototipos, que son otros objetos. Actuarán como si tuvieran propiedades que no tienen siempre y cuando su prototipo tenga esa propiedad. Los objetos simples tienen el prototipo Object.prototype.

-

Los constructores, que son funciones cuyos nombres generalmente comienzan con una letra mayúscula, se pueden usar con el operador new para crear nuevos objetos. El prototipo del nuevo objeto será el objeto encontrado en la propiedad prototype del constructor. Puedes sacar buen provecho de esto poniendo las propiedades que comparten todos los valores de un tipo dado en su prototipo. Existe una notación de class que proporciona una forma clara de definir un constructor y su prototipo.

+

Los constructores, que son funciones cuyos nombres generalmente comienzan con una letra mayúscula, se pueden usar con el operador new para crear nuevos objetos. El prototipo del nuevo objeto será el objeto encontrado en la propiedad prototype del constructor. Puedes aprovechar esto poniendo poniendo las propiedades que comparten todos los valores de un tipo dado en su prototipo. Existe una notación de class que proporciona una forma clara de definir un constructor y su prototipo.

-

Puedes definir getters y setters para llamar secretamente a métodos cada vez que se accede a una propiedad de un objeto. Los métodos estáticos son métodos almacenados en el constructor de una clase, en lugar de en su prototipo.

+

Puedes definir getters y setters para llamar implícitamente a métodos cada vez que se accede a una propiedad de un objeto. Los métodos estáticos son métodos almacenados en el constructor de una clase, en lugar de en su prototipo.

El operador instanceof puede, dado un objeto y un constructor, decirte si ese objeto es una instancia de ese constructor.

-

Una cosa útil que se puede hacer con objetos es especificar una interfaz para ellos y decirle a todo el mundo que se supone que deben comunicarse con tu objeto solo a través de esa interfaz. El resto de los detalles que componen tu objeto están ahora encapsulados, escondidos detrás de la interfaz. Puedes usar propiedades privadas para ocultar una parte de tu objeto del mundo exterior.

+

Algo útil que se puede hacer con objetos es especificar una interfaz para ellos y decirle a todo el mundo que se supone que deben comunicarse con tu objeto solo a través de esa interfaz. El resto de los detalles que componen tu objeto están ahora encapsulados, escondidos detrás de la interfaz. Puedes usar propiedades privadas para ocultar una parte de tu objeto al mundo exterior.

-

Más de un tipo puede implementar la misma interfaz. El código escrito para usar una interfaz automáticamente sabe cómo trabajar con cualquier número de objetos diferentes que proporcionen la interfaz. Esto se llama polimorfismo.

+

Una interfaz puede ser implementada por más de un tipo. Un código escrito para utilizar una interfaz automáticamente sabe cómo tratar con cualquier objeto que implemente la misma interfaz. Esto se llama polimorfismo.

Cuando se implementan múltiples clases que difieren solo en algunos detalles, puede ser útil escribir las nuevas clases como subclases de una clase existente, heredando parte de su comportamiento.

Ejercicios

-

Un tipo de vector

+

Un tipo vector

Escribe una clase Vec que represente un vector en el espacio bidimensional. Toma los parámetros x e y (números), que debería guardar en propiedades del mismo nombre.

-

Dale a la clase Vec dos métodos en su prototipo, plus y minus, que tomen otro vector como parámetro y devuelvan un nuevo vector que tenga la suma o la diferencia de los valores x e y de los dos vectores (this y el parámetro).

+

Dale al prototipo de Vec dos métodos, plus y minus, que tomen otro vector como parámetro y devuelvan un nuevo vector que tenga la suma o la diferencia de los valores x e y de los dos vectores (this y el parámetro).

Agrega una propiedad getter length al prototipo que calcule la longitud del vector, es decir, la distancia del punto (x, y) desde el origen (0, 0).

@@ -596,17 +596,17 @@

Mostrar pistas...
-

Mira de nuevo el ejemplo de la clase Rabbit si no estás seguro de cómo se ven las declaraciones de class.

+

Mira de nuevo el ejemplo de la clase Conejo si no estás seguro de cómo se ven las declaraciones de class.

-

Agregar una propiedad getter al constructor se puede hacer poniendo la palabra get antes del nombre del método. Para calcular la distancia desde (0, 0) hasta (x, y), puedes usar el teorema de Pitágoras, que dice que el cuadrado de la distancia que estamos buscando es igual al cuadrado de la coordenada x más el cuadrado de la coordenada y. Por lo tanto, √(x2 + y2) es el número que buscas. Math.sqrt es la forma de calcular una raíz cuadrada en JavaScript y x ** 2 se puede usar para elevar al cuadrado un número.

+

Agregar una propiedad getter al constructor se puede hacer poniendo la palabra get antes del nombre del método. Para calcular la distancia desde (0, 0) hasta (x, y), puedes usar el teorema de Pitágoras, que dice que el cuadrado de la distancia que estamos buscando es igual al cuadrado de la coordenada x más el cuadrado de la coordenada y. Por lo tanto, √(x2 + y2) es el número que buscas. Math.sqrt es la forma de calcular una raíz cuadrada en JavaScript y x ** 2 se puede usar para elevar al cuadrado un número.

Grupos

-

El entorno estándar de JavaScript proporciona otra estructura de datos llamada Set. Al igual que una instancia de Map, un conjunto contiene una colección de valores. A diferencia de Map, no asocia otros valores con esos, solo realiza un seguimiento de qué valores forman parte del conjunto. Un valor puede formar parte de un conjunto solo una vez: agregarlo nuevamente no tiene ningún efecto.

+

El entorno estándar de JavaScript proporciona otra estructura de datos llamada Set. Al igual que una instancia de Map, un conjunto contiene una colección de valores. A diferencia de Map, no asocia otros valores con ellos, solo realiza un seguimiento de qué valores forman parte del conjunto. Un valor puede formar parte de un conjunto solo una vez: agregarlo nuevamente no tiene ningún efecto.

-

Escribe una clase llamada Group (ya que Set está siendo utilizado). Al igual que Set, tiene los métodos add, delete y has. Su constructor crea un grupo vacío, add agrega un valor al grupo (pero solo si aún no es miembro), delete elimina su argumento del grupo (si era miembro), y has devuelve un valor booleano que indica si su argumento es miembro del grupo.

+

Escribe una clase llamada Group (ya que Set está siendo utilizado). Como Set, tiene que tener los métodos add, delete y has. Su constructor crea un grupo vacío, add agrega un valor al grupo (pero solo si aún no es miembro), delete elimina su argumento del grupo (si era miembro), y has devuelve un valor booleano que indica si su argumento es miembro del grupo.

Usa el operador ===, o algo equivalente como indexOf, para determinar si dos valores son iguales.

@@ -630,7 +630,7 @@

La forma más sencilla de hacer esto es almacenar un array de miembros del grupo en una propiedad de instancia. Los métodos includes o indexOf se pueden usar para verificar si un valor dado está en el array.

-

El constructor de tu clase puede establecer la colección de miembros en un array vacío. Cuando se llama a add, debe verificar si el valor dado está en el array o agregarlo, por ejemplo con push, de lo contrario.

+

El constructor de tu clase puede establecer la colección de miembros en un array vacío. Cuando se llama a add, debe verificar si el valor dado está en el array y agregarlo, por ejemplo con push, de lo contrario.

Eliminar un elemento de un array, en delete, es menos directo, pero puedes usar filter para crear un nuevo array sin el valor. No olvides sobrescribir la propiedad que contiene los miembros con la nueva versión filtrada del array.

@@ -640,11 +640,11 @@

Grupos iterables

-

Haz que la clase Group del ejercicio anterior sea iterable. Refiérete a la sección sobre la interfaz del iterador anteriormente en el capítulo si no tienes claro la forma exacta de la interfaz.

+

Haz que la clase Group del ejercicio anterior sea iterable. Mira la sección sobre la interfaz iterador anteriormente en el capítulo si no tienes claro la forma exacta de la interfaz.

-

Si utilizaste un array para representar los miembros del grupo, no devuelvas simplemente el iterador creado al llamar al método Symbol.iterator en el array. Eso funcionaría, pero va en contra del propósito de este ejercicio.

+

Si usaste un array para representar los miembros del grupo, no devuelvas simplemente el iterador creado al llamar al método Symbol.iterator en el array. Eso funcionaría, pero va en contra del propósito de este ejercicio.

-

Está bien si tu iterador se comporta de manera extraña cuando el grupo se modifica durante la iteración.

+

No pasa nada si tu iterador se comporta de manera extraña cuando el grupo se modifica durante la iteración.

// Tu código aquí (y el código del ejercicio anterior)
 
@@ -659,7 +659,7 @@ 

Probablemente valga la pena definir una nueva clase GroupIterator. Las instancias del iterador deberían tener una propiedad que rastree la posición actual en el grupo. Cada vez que se llama a next, verifica si ha terminado y, si no, avanza más allá del valor actual y lo devuelve.

-

La clase Group en sí misma obtiene un método nombrado Symbol.iterator que, al ser llamado, devuelve una nueva instancia de la clase iteradora para ese grupo.

+

La clase Group en sí misma obtiene un método llamado Symbol.iterator que, al ser llamado, devuelve una nueva instancia de la clase iteradora para ese grupo.

diff --git a/src/chapter_info.mjs b/src/chapter_info.mjs index 4c254cd6..1af6f159 100644 --- a/src/chapter_info.mjs +++ b/src/chapter_info.mjs @@ -20,7 +20,7 @@ const TRANSLATIONS_MAP = { '05_1_aplanamiento.js': '05_1_flattening.js', '05_2_tu_propio_bucle.js': '05_2_your_own_loop.js', '05_4_direccin_de_escritura_dominante.js': '05_4_dominant_writing_direction.js', - '06_1_un_tipo_de_vector.js': '06_1_a_vector_type.js', + '06_1_un_tipo_vector.js': '06_1_a_vector_type.js', '06_2_grupos.js': '06_2_groups.js', '06_3_grupos_iterables.js': '06_3_iterable_groups.js', '07_1_medicin_de_un_robot.js': '07_1_measuring_a_robot.js', From 91f1e29fde0ebcb0f4a98619c3859132259a41fb Mon Sep 17 00:00:00 2001 From: ckdvk Date: Fri, 14 Feb 2025 01:13:26 +0800 Subject: [PATCH 18/36] =?UTF-8?q?revisado=20cap=C3=ADtulo=207?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 07_robot.md | 80 +++++++++++++++++++++++----------------------- html/07_robot.html | 80 +++++++++++++++++++++++----------------------- 2 files changed, 80 insertions(+), 80 deletions(-) diff --git a/07_robot.md b/07_robot.md index 9b2fe132..93bf8585 100644 --- a/07_robot.md +++ b/07_robot.md @@ -1,9 +1,9 @@ {{meta {load_files: ["code/chapter/07_robot.js", "code/animatevillage.js"], zip: html}}} # Proyecto: Un Robot -{{quote {author: "Edsger Dijkstra", title: "Las amenazas a la ciencia informática", chapter: true} +{{quote {author: "Edsger Dijkstra", title: "The Threats to Computing Science", chapter: true} -[...] la pregunta de si las Máquinas Pueden Pensar [...] es tan relevante como la pregunta de si los Submarinos Pueden Nadar. +La cuestión de si las Máquinas Pueden Pensar [...] es tan relevante como la cuestión de si los Submarinos Pueden Nadar. quote}} @@ -13,7 +13,7 @@ quote}} {{index "capítulo del proyecto", "leyendo código", "escribiendo código"}} -En los capítulos del "proyecto", dejaré de golpearte con nueva teoría por un breve momento, y en su lugar trabajaremos en un programa juntos. La teoría es necesaria para aprender a programar, pero leer y entender programas reales es igual de importante. +En los capítulos "proyecto", dejaré de bombardearte con nueva teoría por un momento y, en su lugar, trabajaremos juntos en un programa. La teoría es necesaria para aprender a programar, pero leer y entender programas reales es igual de importante. Nuestro proyecto en este capítulo es construir un ((autómata)), un pequeño programa que realiza una tarea en un ((mundo virtual)). Nuestro autómata será un ((robot)) de entrega de correo que recoge y deja paquetes. @@ -21,7 +21,7 @@ Nuestro proyecto en este capítulo es construir un ((autómata)), un pequeño pr {{index "array de carreteras"}} -El pueblo de ((Meadowfield)) no es muy grande. Consiste en 11 lugares con 14 carreteras entre ellos. Se puede describir con este array de carreteras: +El pueblo de ((Meadowfield)) no es muy grande. Consiste en 11 lugares conectados por 14 caminos. Se puede describir con este array de caminos: ```{includeCode: true} const roads = [ @@ -35,13 +35,13 @@ const roads = [ ]; ``` -{{figure {url: "img/village2x.png", alt: "Ilustración de arte pixelado de un pequeño pueblo con 11 ubicaciones, etiquetadas con letras, y carreteras entre ellas"}}} +{{figure {url: "img/village2x.png", alt: "Ilustración de estilo pixel-art de un pequeño pueblo con 11 ubicaciones, etiquetadas con letras, y carreteras entre ellas"}}} -La red de carreteras en el pueblo forma un _((gráfico))_. Un gráfico es una colección de puntos (lugares en el pueblo) con líneas entre ellos (carreteras). Este gráfico será el mundo por el que se moverá nuestro robot. +La red de carreteras en el pueblo forma un _((grafo))_. Un grafo es una colección de puntos (lugares en el pueblo) con líneas entre ellos (caminos). Este grafo será el mundo por el que se moverá nuestro robot. {{index "objeto roadGraph"}} -El array de cadenas no es muy fácil de trabajar. Lo que nos interesa son los destinos a los que podemos llegar desde un lugar dado. Vamos a convertir la lista de carreteras en una estructura de datos que, para cada lugar, nos diga qué se puede alcanzar desde allí. +No es muy sencillo trabajar con el array de cadenas anterior. Lo que nos interesa son los destinos a los que podemos llegar desde un lugar dado. Vamos a convertir la lista de carreteras en una estructura de datos que, para cada lugar, nos diga qué se puede alcanzar desde allí. ```{includeCode: true} function buildGraph(edges) { @@ -67,7 +67,7 @@ Dado un array de aristas, `buildGraph` crea un objeto de mapa que, para cada nod {{index "método split"}} -Utiliza el método `split` para pasar de las cadenas de carreteras, que tienen la forma `"Inicio-Fin"`, a arrays de dos elementos que contienen el inicio y el fin como cadenas separadas. +Utiliza el método `split` para pasar de las cadenas representando caminos, que tienen la forma `"Inicio-Fin"`, a arrays de dos elementos que contienen el inicio y el fin como cadenas separadas. ## La tarea @@ -83,7 +83,7 @@ Para poder simular este proceso, debemos definir un mundo virtual que pueda desc Si estás pensando en términos de ((programación orientada a objetos)), tu primer impulso podría ser empezar a definir objetos para los diferentes elementos en el mundo: una ((clase)) para el robot, una para un paquete, tal vez una para lugares. Estos podrían tener propiedades que describen su ((estado)) actual, como la pila de paquetes en un lugar, que podríamos cambiar al actualizar el mundo. -Esto es incorrecto. Al menos, usualmente lo es. El hecho de que algo suene como un objeto no significa automáticamente que deba ser un objeto en tu programa. Escribir reflexivamente clases para cada concepto en tu aplicación tiende a dejarte con una colección de objetos interconectados que tienen su propio estado interno cambiable. Estos programas a menudo son difíciles de entender y, por lo tanto, fáciles de romper. +Esto es un error. O, al menos, suele serlo. El hecho de que algo suene como un objeto no significa automáticamente que deba representarse como un objeto en tu programa. Escribir clases de forma mecánica para cada concepto en una aplicación suele dar lugar a una colección de objetos interconectados, cada uno con su propio estado interno y cambiante. Este tipo de programas suelen ser difíciles de comprender y, por lo tanto, fáciles de romper. {{index [estado, en objetos]}} @@ -91,7 +91,7 @@ En lugar de eso, vamos a condensar el estado del pueblo en el conjunto mínimo d {{index "clase VillageState", "estructura de datos persistente"}} -Y mientras lo hacemos, hagamos que no _cambiemos_ este estado cuando el robot se mueve, sino que calculemos un _nuevo_ estado para la situación después del movimiento. +Ya que estamos, hagamos que este estado no _cambie_ cuando el robot se mueve, sino que en su lugar se calcule un _nuevo_ estado para la situación después del movimiento. ```{includeCode: true} class VillageState { @@ -114,13 +114,13 @@ class VillageState { } ``` -El método `move` es donde ocurre la acción. Primero verifica si hay un camino desde el lugar actual hasta el destino, y si no lo hay, devuelve el estado anterior ya que este no es un movimiento válido. +El método `move` es donde ocurre la acción. Primero verifica si hay un camino desde el lugar actual hasta el destino y, si no lo hay, devuelve el estado anterior ya que este no es un movimiento válido. {{index "método map", "método filter"}} -Luego crea un nuevo estado con el destino como el nuevo lugar del robot. Pero también necesita crear un nuevo conjunto de paquetes: los paquetes que lleva el robot (que están en el lugar actual del robot) deben ser trasladados al nuevo lugar. Y los paquetes dirigidos al nuevo lugar deben ser entregados, es decir, deben ser eliminados del conjunto de paquetes no entregados. La llamada a `map` se encarga del traslado y la llamada a `filter` de la entrega. +Si sí, crea un nuevo estado con el destino que se pasa como parámetro a `move` como nueva posición para el robot. Pero también necesita crear un nuevo conjunto de paquetes: los paquetes que lleva el robot (que están en el lugar actual del robot) deben ser trasladados al nuevo lugar. Y los paquetes dirigidos al nuevo lugar deben ser entregados, es decir, deben ser eliminados del conjunto de paquetes por entregar. La llamada a `map` se encarga del traslado y la llamada a `filter` de la entrega. -Los objetos de parcela no se modifican cuando se mueven, sino que se vuelven a crear. El método `move` nos proporciona un nuevo estado de aldea pero deja intacto por completo el anterior. +Los objetos que representan los paquetes (`parcels`) no se modifican cuando se mueven, sino que se vuelven a crear. El método `move` nos proporciona un nuevo estado del pueblo pero deja intacto por completo el anterior. ``` let first = new VillageState( @@ -137,7 +137,7 @@ console.log(first.place); // → Oficina de Correos ``` -El movimiento hace que la parcela se entregue, y esto se refleja en el siguiente estado. Pero el estado inicial sigue describiendo la situación en la que el robot está en la oficina de correos y la parcela no se ha entregado. +El movimiento hace que el paquete se entregue, y esto se refleja en el siguiente estado. Pero el estado inicial sigue describiendo la situación en la que el robot está en la oficina de correos y el paquete está aún por entregar. ## Datos persistentes @@ -145,7 +145,7 @@ El movimiento hace que la parcela se entregue, y esto se refleja en el siguiente Las estructuras de datos que no cambian se llaman _((inmutables))_ o _persistentes_. Se comportan de manera similar a las cadenas de texto y los números en el sentido de que son lo que son y se mantienen así, en lugar de contener cosas diferentes en momentos diferentes. -En JavaScript, casi todo _puede_ cambiarse, por lo que trabajar con valores que se supone que son persistentes requiere cierta moderación. Existe una función llamada `Object.freeze` que cambia un objeto para que la escritura en sus propiedades sea ignorada. Podrías usar esto para asegurarte de que tus objetos no se modifiquen, si así lo deseas. Congelar requiere que la computadora realice un trabajo adicional, y que las actualizaciones se ignoren es casi tan propenso a confundir a alguien como hacer que hagan lo incorrecto. Por lo tanto, suelo preferir simplemente decirle a las personas que un objeto dado no debe ser modificado y esperar que lo recuerden. +En JavaScript, casi todo _puede_ modificarse, por lo que trabajar con valores que deberían ser persistentes requiere cierta disciplina. Existe una función llamada `Object.freeze` que cambia un objeto para que la escritura en sus propiedades sea ignorada. Si quieres, puedes usar esto para asegurarte de que tus objetos no se modifiquen. Congelar requiere que la computadora realice un trabajo adicional, y que las actualizaciones se ignoren es casi tan propenso a confundir a alguien como hacer que hagan lo incorrecto. Por lo tanto, yo suelo preferir simplemente decirle a la gente que un objeto dado no debe ser modificado y esperar que lo recuerden. ``` let object = Object.freeze({value: 5}); @@ -154,19 +154,17 @@ console.log(object.value); // → 5 ``` -¿Por qué me estoy esforzando tanto en no cambiar los objetos cuando el lenguaje obviamente espera que lo haga? +¿Por qué me estoy esforzando tanto en no cambiar los objetos cuando el lenguaje obviamente espera que lo haga? Porque me ayuda a entender mis programas. Una vez más, se trata de gestionar la complejidad. Cuando los objetos en mi sistema son cosas fijas y estables, puedo considerar operaciones sobre ellos de forma aislada: moverse a la casa de Alice desde un estado inicial dado siempre produce el mismo nuevo estado. Cuando los objetos cambian con el tiempo se añade toda una nueva dimensión de complejidad a este tipo de razonamiento. -Porque me ayuda a entender mis programas. Una vez más, esto se trata de gestionar la complejidad. Cuando los objetos en mi sistema son cosas fijas y estables, puedo considerar operaciones sobre ellos de forma aislada: moverse a la casa de Alice desde un estado inicial dado siempre produce el mismo nuevo estado. Cuando los objetos cambian con el tiempo, eso añade toda una nueva dimensión de complejidad a este tipo de razonamiento. +Para un sistema pequeño como el que estamos construyendo en este capítulo, podríamos manejar este poquito de complejidad extra. Pero el límite más importante respecto a qué tipo de sistemas podemos construir es cuánto podemos entender. Cualquier cosa que haga que tu código sea más fácil de entender te permite construir un sistema más ambicioso. -Para un sistema pequeño como el que estamos construyendo en este capítulo, podríamos manejar ese poco de complejidad extra. Pero el límite más importante respecto a qué tipo de sistemas podemos construir es cuánto podemos entender. Cualquier cosa que haga que tu código sea más fácil de entender te permite construir un sistema más ambicioso. - -Desafortunadamente, aunque entender un sistema construido sobre estructuras de datos persistentes es más fácil, _diseñar_ uno, especialmente cuando tu lenguaje de programación no ayuda, puede ser un poco más difícil. Buscaremos oportunidades para usar estructuras de datos persistentes en este libro, pero también usaremos aquellas que pueden cambiar. +Por desgracia, aunque entender un sistema construido sobre estructuras de datos persistentes es más fácil, _diseñar_ uno, especialmente cuando tu lenguaje de programación no ayuda, puede ser un poco más difícil. En este libro, buscaremos oportunidades para usar estructuras de datos persistentes, pero también utilizaremos estructuras modificables. ## Simulación {{index "simulación", "mundo virtual"}} -Un ((robot)) de entrega observa el mundo y decide en qué dirección quiere moverse. Como tal, podríamos decir que un robot es una función que toma un objeto `VillageState` y devuelve el nombre de un lugar cercano. +Un ((robot)) de entrega observa el mundo y decide en qué dirección quiere moverse. O sea que podríamos decir que un robot es una función que toma un objeto `VillageState` y devuelve el nombre de un lugar cercano. {{index "función runRobot"}} @@ -193,7 +191,7 @@ Consideremos lo que un robot tiene que hacer para "resolver" un estado dado. Deb {{index "función randomPick", "función randomRobot"}} -Esto es cómo podría lucir eso: +Esta es la pinta que podría tener algo así: ```{includeCode: true} function randomPick(array) { @@ -208,7 +206,7 @@ function randomRobot(state) { {{index "función Math.random", "función Math.floor", [array, "elemento aleatorio"]}} -Recuerda que `Math.random()` devuelve un número entre cero y uno, pero siempre por debajo de uno. Multiplicar dicho número por la longitud de un array y luego aplicarle `Math.floor` nos da un índice aleatorio para el array. +Recuerda que `Math.random()` devuelve un número entre cero y uno, pero siempre por debajo de uno. Al multiplicar dicho número por la longitud de un array y luego aplicarle `Math.floor`, obtenemos un índice aleatorio para el array. Dado que este robot no necesita recordar nada, ignora su segundo argumento (recuerda que las funciones de JavaScript pueden ser llamadas con argumentos adicionales sin efectos adversos) y omite la propiedad `memory` en su objeto devuelto. @@ -274,7 +272,7 @@ const mailRoute = [ {{index "routeRobot function"}} -Para implementar el robot que sigue la ruta, necesitaremos hacer uso de la memoria del robot. El robot guarda el resto de su ruta en su memoria y deja caer el primer elemento en cada turno. +Para implementar el robot que sigue la ruta, necesitaremos hacer uso de la memoria del robot. El robot guarda el resto de su ruta en su memoria y se desprende del primer elemento de la ruta en cada turno. ```{includeCode: true} function routeRobot(state, memory) { @@ -297,21 +295,23 @@ if}} ## Búsqueda de caminos -Aún así, no llamaría a seguir ciegamente una ruta fija un comportamiento inteligente. Sería más eficiente si el ((robot)) ajustara su comportamiento a la tarea real que debe realizarse. +Aún así, no creo que sea muy inteligente seguir ciegamente una ruta fija. Sería más eficiente si el ((robot)) ajustara su comportamiento a la tarea real que debe realizarse. {{index pathfinding}} -Para hacer eso, tiene que poder moverse deliberadamente hacia un paquete dado o hacia la ubicación donde se debe entregar un paquete. Hacer eso, incluso cuando el objetivo está a más de un movimiento de distancia, requerirá algún tipo de función de búsqueda de ruta. +Para hacer eso, tiene que poder moverse deliberadamente hacia un destino dado o hacia la ubicación donde se debe entregar un paquete. Hacer eso, incluso cuando el objetivo está a más de un movimiento de distancia, requerirá algún tipo de función de búsqueda de ruta. + +El problema de encontrar una ruta a través de un ((grafo)) es un _((problema de búsqueda))_ típico. Podemos determinar si una solución dada (es decir, una ruta) es una solución válida, pero no podemos hacer un cálculo directo de la solución como podríamos hacerlo para 2 + 2. En su lugar, debemos seguir creando soluciones potenciales hasta encontrar una que funcione. -El problema de encontrar una ruta a través de un ((grafo)) es un _((problema de búsqueda))_ típico. Podemos determinar si una solución dada (una ruta) es una solución válida, pero no podemos calcular directamente la solución como podríamos hacerlo para 2 + 2. En su lugar, debemos seguir creando soluciones potenciales hasta encontrar una que funcione. +El número de rutas posibles a través de un grafo es enorme. Pero al buscar una ruta de _A_ a _B_, solo estamos interesados en aquellas que comienzan en _A_. Además, no nos importan las rutas que visiten el mismo lugar dos veces —esas claramente no son las rutas más eficientes hacia ningún lugar. Así que eso reduce la cantidad de rutas que el buscador de rutas debe considerar. -El número de rutas posibles a través de un grafo es infinito. Pero al buscar una ruta de _A_ a _B_, solo estamos interesados en aquellas que comienzan en _A_. Además, no nos importan las rutas que visiten el mismo lugar dos veces, esas definitivamente no son las rutas más eficientes en ningún lugar. Así que eso reduce la cantidad de rutas que el buscador de rutas debe considerar.De hecho, estamos mayormente interesados en la ruta _más corta_. Por lo tanto, queremos asegurarnos de buscar rutas cortas antes de mirar las más largas. Un buen enfoque sería "expandir" rutas desde el punto de inicio, explorando cada lugar alcanzable que aún no haya sido visitado, hasta que una ruta llegue al objetivo. De esta manera, solo exploraremos rutas que sean potencialmente interesantes, y sabremos que la primera ruta que encontremos es la ruta más corta (o una de las rutas más cortas, si hay más de una). +De hecho, estamos sobre todo interesados en la ruta _más corta_. Por lo tanto, queremos asegurarnos de buscar rutas cortas antes de mirar las más largas. Un buen enfoque sería "expandir" rutas desde el punto de inicio, explorando cada lugar alcanzable que aún no haya sido visitado, hasta que una ruta llegue al objetivo. De esta manera, solo exploraremos rutas que sean potencialmente interesantes, y sabremos que la primera ruta que encontremos es la ruta más corta (o una de las rutas más cortas, si hay más de una). {{index "findRoute function"}} {{id findRoute}} -Aquí hay una función que hace esto: +Aquí, una función que hace esto: ```{includeCode: true} function findRoute(graph, from, to) { @@ -330,15 +330,15 @@ function findRoute(graph, from, to) { La exploración debe realizarse en el orden correcto: los lugares que se alcanzaron primero deben explorarse primero. No podemos explorar de inmediato un lugar tan pronto como lleguemos a él porque eso significaría que los lugares alcanzados _desde allí_ también se explorarían de inmediato, y así sucesivamente, incluso si puede haber otros caminos más cortos que aún no se han explorado. -Por lo tanto, la función mantiene una _((lista de trabajo))_. Esta es una matriz de lugares que deben ser explorados a continuación, junto con la ruta que nos llevó allí. Comienza con solo la posición de inicio y una ruta vacía. +Por lo tanto, la función mantiene una _((lista de trabajo))_: un array de lugares que deben ser explorados a continuación, junto con la ruta que nos llevó allí. Comienza con solo la posición de inicio y una ruta vacía. La búsqueda luego opera tomando el siguiente elemento en la lista y explorándolo, lo que significa que se ven todas las rutas que salen de ese lugar. Si una de ellas es el objetivo, se puede devolver una ruta terminada. De lo contrario, si no hemos mirado este lugar antes, se agrega un nuevo elemento a la lista. Si lo hemos mirado antes, dado que estamos buscando rutas cortas primero, hemos encontrado o bien una ruta más larga a ese lugar o una exactamente tan larga como la existente, y no necesitamos explorarla. -Puedes imaginar visualmente esto como una red de rutas conocidas que se extienden desde la ubicación de inicio, creciendo de manera uniforme en todos los lados (pero nunca enredándose de nuevo en sí misma). Tan pronto como el primer hilo alcance la ubicación objetivo, ese hilo se rastrea de vuelta al inicio, dándonos nuestra ruta. +Puedes imaginar visualmente esto como una red de rutas conocidas que se extienden desde la ubicación de inicio, creciendo de manera uniforme hacia todas partes (pero nunca enredándose de nuevo en sí misma). Tan pronto como el primer hilo alcance la ubicación objetivo, ese hilo se rastrea de vuelta al inicio, dándonos nuestra ruta. {{index "grafo conectado"}} -Nuestro código no maneja la situación en la que no hay más elementos de trabajo en la lista de trabajo porque sabemos que nuestro gráfico está _conectado_, lo que significa que se puede llegar a cada ubicación desde todas las demás ubicaciones. Siempre podremos encontrar una ruta entre dos puntos, y la búsqueda no puede fallar. +Nuestro código no maneja la situación en la que no hay más elementos de trabajo en la lista de trabajo porque sabemos que nuestro grafo está _conectado_, lo que significa que se puede llegar a cada ubicación desde todas las demás ubicaciones. Siempre podremos encontrar una ruta entre dos puntos, y la búsqueda no puede fallar. ```{includeCode: true} function goalOrientedRobot({place, parcels}, route) { @@ -356,7 +356,7 @@ function goalOrientedRobot({place, parcels}, route) { {{index "goalOrientedRobot function"}} -Este robot utiliza el valor de su memoria como una lista de direcciones en las que moverse, al igual que el robot que sigue la ruta. Cuando esa lista está vacía, debe averiguar qué hacer a continuación. Toma el primer paquete no entregado del conjunto y, si ese paquete aún no ha sido recogido, traza una ruta hacia él. Si el paquete ya ha sido recogido, todavía necesita ser entregado, por lo que el robot crea una ruta hacia la dirección de entrega. +Este robot utiliza el valor de su memoria como una lista de direcciones a las que moverse, como con el robot que simplemente seguía rutas. Cuando esa lista está vacía, debe averiguar qué hacer a continuación. Toma el primer paquete no entregado del conjunto y, si ese paquete aún no ha sido recogido, traza una ruta hacia él. Si el paquete ya ha sido recogido, todavía necesita ser entregado, por lo que el robot crea una ruta hacia la dirección de entrega. {{if interactive @@ -369,7 +369,7 @@ runRobotAnimation(VillageState.random(), if}} -Este robot suele terminar la tarea de entregar 5 paquetes en aproximadamente 16 turnos. Eso es ligeramente mejor que `routeRobot` pero definitivamente no es óptimo. +Este robot suele terminar la tarea de entregar 5 paquetes en aproximadamente 16 turnos. Eso es ligeramente mejor que `routeRobot` pero está claro que no es óptimo. ## Ejercicios @@ -381,7 +381,7 @@ Es difícil comparar de manera objetiva los ((robot))s solo dejando que resuelva Escribe una función `compareRobots` que tome dos robots (y su memoria inicial). Debería generar 100 tareas y permitir que cada uno de los robots resuelva cada una de estas tareas. Cuando termine, debería mostrar el número promedio de pasos que cada robot dio por tarea. -Por el bien de la equidad, asegúrate de darle a cada tarea a ambos robots, en lugar de generar tareas diferentes por robot. +Para que sea una comparación justa, asegúrate de darle a cada tarea a ambos robots, en lugar de generar tareas diferentes por robot. {{if interactive @@ -400,7 +400,7 @@ if}} Tendrás que escribir una variante de la función `runRobot` que, en lugar de registrar los eventos en la consola, devuelva el número de pasos que el robot tomó para completar la tarea. -Tu función de medición puede, entonces, en un bucle, generar nuevos estados y contar los pasos que toma cada uno de los robots. Cuando haya generado suficientes mediciones, puede usar `console.log` para mostrar el promedio de cada robot, que es el número total de pasos tomados dividido por el número de mediciones. +Tu función de medición puede, entonces, en un bucle, generar nuevos estados y contar los pasos que toma cada uno de los robots. Cuando haya generado suficientes mediciones, puede usar `console.log` para mostrar el promedio de cada robot, que es el número total de pasos dados dividido por el número de mediciones. hint}} @@ -408,7 +408,7 @@ hint}} {{index "robot efficiency (exercise)"}} -¿Puedes escribir un robot que termine la tarea de entrega más rápido que `goalOrientedRobot`? Si observas el comportamiento de ese robot, ¿qué cosas claramente absurdas hace? ¿Cómo podrían mejorarse? +¿Puedes escribir un robot que termine la tarea de entrega más rápido que `goalOrientedRobot`? Si observas el comportamiento de ese robot, ¿qué cosas evidentemente absurdas está haciendo? ¿Cómo podrían mejorarse? Si resolviste el ejercicio anterior, es posible que desees utilizar tu función `compareRobots` para verificar si mejoraste el robot. @@ -426,9 +426,9 @@ if}} {{index "robot efficiency (exercise)"}} -La principal limitación de `goalOrientedRobot` es que solo considera un paquete a la vez. A menudo caminará de un lado a otro del pueblo porque el paquete en el que está centrando su atención sucede que está en el otro lado del mapa, incluso si hay otros mucho más cerca. +La principal limitación de `goalOrientedRobot` es que considera los paquetes de uno en uno. A menudo caminará de un lado a otro del pueblo porque el paquete en el que está centrando su atención sucede que está en el otro lado del mapa, incluso si hay otros mucho más cerca. -Una posible solución sería calcular rutas para todos paquetes y luego tomar la más corta. Se pueden obtener resultados aún mejores, si hay múltiples rutas más cortas, al preferir aquellas que van a recoger un paquete en lugar de entregarlo. +Una posible solución sería calcular rutas para todos paquetes y luego tomar la más corta. Se pueden obtener resultados aún mejores, si hay múltiples rutas más cortas, prefiriendo las que van a recoger un paquete en vez de entregarlo. hint}} @@ -442,7 +442,7 @@ Escribe una nueva clase `PGroup`, similar a la clase `Grupo` del [Capítulo ?](o Sin embargo, su método `add` debería devolver una _nueva_ instancia de `PGroup` con el miembro dado añadido y dejar la anterior sin cambios. De manera similar, `delete` crea una nueva instancia sin un miembro dado. -La clase debería funcionar para valores de cualquier tipo, no solo para strings. No tiene que ser eficiente cuando se utiliza con grandes cantidades de valores. +La clase debería funcionar para valores de cualquier tipo, no solo para strings. _No_ tiene que ser eficiente cuando se utiliza con grandes cantidades de valores. {{index [interfaz, objeto]}} diff --git a/html/07_robot.html b/html/07_robot.html index b2748ca4..168db359 100644 --- a/html/07_robot.html +++ b/html/07_robot.html @@ -14,19 +14,19 @@

Proyecto: Un Robot

-

[...] la pregunta de si las Máquinas Pueden Pensar [...] es tan relevante como la pregunta de si los Submarinos Pueden Nadar.

+

La cuestión de si las Máquinas Pueden Pensar [...] es tan relevante como la cuestión de si los Submarinos Pueden Nadar.

-
Edsger Dijkstra, Las amenazas a la ciencia informática
+
Edsger Dijkstra, The Threats to Computing Science
Ilustración de un robot sosteniendo una pila de paquetes
-

En los capítulos del “proyecto”, dejaré de golpearte con nueva teoría por un breve momento, y en su lugar trabajaremos en un programa juntos. La teoría es necesaria para aprender a programar, pero leer y entender programas reales es igual de importante.

+

En los capítulos “proyecto”, dejaré de bombardearte con nueva teoría por un momento y, en su lugar, trabajaremos juntos en un programa. La teoría es necesaria para aprender a programar, pero leer y entender programas reales es igual de importante.

Nuestro proyecto en este capítulo es construir un autómata, un pequeño programa que realiza una tarea en un mundo virtual. Nuestro autómata será un robot de entrega de correo que recoge y deja paquetes.

Meadowfield

-

El pueblo de Meadowfield no es muy grande. Consiste en 11 lugares con 14 carreteras entre ellos. Se puede describir con este array de carreteras:

+

El pueblo de Meadowfield no es muy grande. Consiste en 11 lugares conectados por 14 caminos. Se puede describir con este array de caminos:

const roads = [
   "Casa de Alice-Casa de Bob","Casa de Alice-Cabaña",
@@ -36,11 +36,11 @@ 

"Casa de Grete-Tienda","Plaza del Mercado-Granja", "Plaza del Mercado-Oficina de Correos","Plaza del Mercado-Tienda", "Plaza del Mercado-Ayuntamiento","Tienda-Ayuntamiento" -];

Ilustración de arte pixelado de un pequeño pueblo con 11 ubicaciones, etiquetadas con letras, y carreteras entre ellas
+];

Ilustración de estilo pixel-art de un pequeño pueblo con 11 ubicaciones, etiquetadas con letras, y carreteras entre ellas
-

La red de carreteras en el pueblo forma un gráfico. Un gráfico es una colección de puntos (lugares en el pueblo) con líneas entre ellos (carreteras). Este gráfico será el mundo por el que se moverá nuestro robot.

+

La red de carreteras en el pueblo forma un grafo. Un grafo es una colección de puntos (lugares en el pueblo) con líneas entre ellos (caminos). Este grafo será el mundo por el que se moverá nuestro robot.

-

El array de cadenas no es muy fácil de trabajar. Lo que nos interesa son los destinos a los que podemos llegar desde un lugar dado. Vamos a convertir la lista de carreteras en una estructura de datos que, para cada lugar, nos diga qué se puede alcanzar desde allí.

+

No es muy sencillo trabajar con el array de cadenas anterior. Lo que nos interesa son los destinos a los que podemos llegar desde un lugar dado. Vamos a convertir la lista de carreteras en una estructura de datos que, para cada lugar, nos diga qué se puede alcanzar desde allí.

function buildGraph(edges) {
   let graph = Object.create(null);
@@ -62,7 +62,7 @@ 

Dado un array de aristas, buildGraph crea un objeto de mapa que, para cada nodo, almacena un array de nodos conectados.

-

Utiliza el método split para pasar de las cadenas de carreteras, que tienen la forma "Inicio-Fin", a arrays de dos elementos que contienen el inicio y el fin como cadenas separadas.

+

Utiliza el método split para pasar de las cadenas representando caminos, que tienen la forma "Inicio-Fin", a arrays de dos elementos que contienen el inicio y el fin como cadenas separadas.

La tarea

@@ -74,11 +74,11 @@

Si estás pensando en términos de programación orientada a objetos, tu primer impulso podría ser empezar a definir objetos para los diferentes elementos en el mundo: una clase para el robot, una para un paquete, tal vez una para lugares. Estos podrían tener propiedades que describen su estado actual, como la pila de paquetes en un lugar, que podríamos cambiar al actualizar el mundo.

-

Esto es incorrecto. Al menos, usualmente lo es. El hecho de que algo suene como un objeto no significa automáticamente que deba ser un objeto en tu programa. Escribir reflexivamente clases para cada concepto en tu aplicación tiende a dejarte con una colección de objetos interconectados que tienen su propio estado interno cambiable. Estos programas a menudo son difíciles de entender y, por lo tanto, fáciles de romper.

+

Esto es un error. O, al menos, suele serlo. El hecho de que algo suene como un objeto no significa automáticamente que deba representarse como un objeto en tu programa. Escribir clases de forma mecánica para cada concepto en una aplicación suele dar lugar a una colección de objetos interconectados, cada uno con su propio estado interno y cambiante. Este tipo de programas suelen ser difíciles de comprender y, por lo tanto, fáciles de romper.

En lugar de eso, vamos a condensar el estado del pueblo en el conjunto mínimo de valores que lo define. Está la ubicación actual del robot y la colección de paquetes no entregados, cada uno de los cuales tiene una ubicación actual y una dirección de destino. Eso es todo.

-

Y mientras lo hacemos, hagamos que no cambiemos este estado cuando el robot se mueve, sino que calculemos un nuevo estado para la situación después del movimiento.

+

Ya que estamos, hagamos que este estado no cambie cuando el robot se mueve, sino que en su lugar se calcule un nuevo estado para la situación después del movimiento.

class VillageState {
   constructor(place, parcels) {
@@ -99,11 +99,11 @@ 

-

El método move es donde ocurre la acción. Primero verifica si hay un camino desde el lugar actual hasta el destino, y si no lo hay, devuelve el estado anterior ya que este no es un movimiento válido.

+

El método move es donde ocurre la acción. Primero verifica si hay un camino desde el lugar actual hasta el destino y, si no lo hay, devuelve el estado anterior ya que este no es un movimiento válido.

-

Luego crea un nuevo estado con el destino como el nuevo lugar del robot. Pero también necesita crear un nuevo conjunto de paquetes: los paquetes que lleva el robot (que están en el lugar actual del robot) deben ser trasladados al nuevo lugar. Y los paquetes dirigidos al nuevo lugar deben ser entregados, es decir, deben ser eliminados del conjunto de paquetes no entregados. La llamada a map se encarga del traslado y la llamada a filter de la entrega.

+

Si sí, crea un nuevo estado con el destino que se pasa como parámetro a move como nueva posición para el robot. Pero también necesita crear un nuevo conjunto de paquetes: los paquetes que lleva el robot (que están en el lugar actual del robot) deben ser trasladados al nuevo lugar. Y los paquetes dirigidos al nuevo lugar deben ser entregados, es decir, deben ser eliminados del conjunto de paquetes por entregar. La llamada a map se encarga del traslado y la llamada a filter de la entrega.

-

Los objetos de parcela no se modifican cuando se mueven, sino que se vuelven a crear. El método move nos proporciona un nuevo estado de aldea pero deja intacto por completo el anterior.

+

Los objetos que representan los paquetes (parcels) no se modifican cuando se mueven, sino que se vuelven a crear. El método move nos proporciona un nuevo estado del pueblo pero deja intacto por completo el anterior.

let first = new VillageState(
   "Oficina de Correos",
@@ -118,30 +118,28 @@ 

// → Oficina de Correos

-

El movimiento hace que la parcela se entregue, y esto se refleja en el siguiente estado. Pero el estado inicial sigue describiendo la situación en la que el robot está en la oficina de correos y la parcela no se ha entregado.

+

El movimiento hace que el paquete se entregue, y esto se refleja en el siguiente estado. Pero el estado inicial sigue describiendo la situación en la que el robot está en la oficina de correos y el paquete está aún por entregar.

Datos persistentes

Las estructuras de datos que no cambian se llaman inmutables o persistentes. Se comportan de manera similar a las cadenas de texto y los números en el sentido de que son lo que son y se mantienen así, en lugar de contener cosas diferentes en momentos diferentes.

-

En JavaScript, casi todo puede cambiarse, por lo que trabajar con valores que se supone que son persistentes requiere cierta moderación. Existe una función llamada Object.freeze que cambia un objeto para que la escritura en sus propiedades sea ignorada. Podrías usar esto para asegurarte de que tus objetos no se modifiquen, si así lo deseas. Congelar requiere que la computadora realice un trabajo adicional, y que las actualizaciones se ignoren es casi tan propenso a confundir a alguien como hacer que hagan lo incorrecto. Por lo tanto, suelo preferir simplemente decirle a las personas que un objeto dado no debe ser modificado y esperar que lo recuerden.

+

En JavaScript, casi todo puede modificarse, por lo que trabajar con valores que deberían ser persistentes requiere cierta disciplina. Existe una función llamada Object.freeze que cambia un objeto para que la escritura en sus propiedades sea ignorada. Si quieres, puedes usar esto para asegurarte de que tus objetos no se modifiquen. Congelar requiere que la computadora realice un trabajo adicional, y que las actualizaciones se ignoren es casi tan propenso a confundir a alguien como hacer que hagan lo incorrecto. Por lo tanto, yo suelo preferir simplemente decirle a la gente que un objeto dado no debe ser modificado y esperar que lo recuerden.

let object = Object.freeze({value: 5});
 object.value = 10;
 console.log(object.value);
 // → 5
-

¿Por qué me estoy esforzando tanto en no cambiar los objetos cuando el lenguaje obviamente espera que lo haga?

+

¿Por qué me estoy esforzando tanto en no cambiar los objetos cuando el lenguaje obviamente espera que lo haga? Porque me ayuda a entender mis programas. Una vez más, se trata de gestionar la complejidad. Cuando los objetos en mi sistema son cosas fijas y estables, puedo considerar operaciones sobre ellos de forma aislada: moverse a la casa de Alice desde un estado inicial dado siempre produce el mismo nuevo estado. Cuando los objetos cambian con el tiempo se añade toda una nueva dimensión de complejidad a este tipo de razonamiento.

-

Porque me ayuda a entender mis programas. Una vez más, esto se trata de gestionar la complejidad. Cuando los objetos en mi sistema son cosas fijas y estables, puedo considerar operaciones sobre ellos de forma aislada: moverse a la casa de Alice desde un estado inicial dado siempre produce el mismo nuevo estado. Cuando los objetos cambian con el tiempo, eso añade toda una nueva dimensión de complejidad a este tipo de razonamiento.

+

Para un sistema pequeño como el que estamos construyendo en este capítulo, podríamos manejar este poquito de complejidad extra. Pero el límite más importante respecto a qué tipo de sistemas podemos construir es cuánto podemos entender. Cualquier cosa que haga que tu código sea más fácil de entender te permite construir un sistema más ambicioso.

-

Para un sistema pequeño como el que estamos construyendo en este capítulo, podríamos manejar ese poco de complejidad extra. Pero el límite más importante respecto a qué tipo de sistemas podemos construir es cuánto podemos entender. Cualquier cosa que haga que tu código sea más fácil de entender te permite construir un sistema más ambicioso.

- -

Desafortunadamente, aunque entender un sistema construido sobre estructuras de datos persistentes es más fácil, diseñar uno, especialmente cuando tu lenguaje de programación no ayuda, puede ser un poco más difícil. Buscaremos oportunidades para usar estructuras de datos persistentes en este libro, pero también usaremos aquellas que pueden cambiar.

+

Por desgracia, aunque entender un sistema construido sobre estructuras de datos persistentes es más fácil, diseñar uno, especialmente cuando tu lenguaje de programación no ayuda, puede ser un poco más difícil. En este libro, buscaremos oportunidades para usar estructuras de datos persistentes, pero también utilizaremos estructuras modificables.

Simulación

-

Un robot de entrega observa el mundo y decide en qué dirección quiere moverse. Como tal, podríamos decir que un robot es una función que toma un objeto VillageState y devuelve el nombre de un lugar cercano.

+

Un robot de entrega observa el mundo y decide en qué dirección quiere moverse. O sea que podríamos decir que un robot es una función que toma un objeto VillageState y devuelve el nombre de un lugar cercano.

Dado que queremos que los robots puedan recordar cosas, para que puedan hacer y ejecutar planes, también les pasamos su memoria y les permitimos devolver una nueva memoria. Por lo tanto, lo que un robot devuelve es un objeto que contiene tanto la dirección en la que quiere moverse como un valor de memoria que se le dará la próxima vez que se llame.

@@ -162,7 +160,7 @@

¿Cuál es la estrategia más tonta que podría funcionar? El robot podría simplemente caminar en una dirección aleatoria en cada turno. Eso significa que, con gran probabilidad, eventualmente se topará con todos los paquetes y en algún momento también llegará al lugar donde deben ser entregados.

-

Esto es cómo podría lucir eso:

+

Esta es la pinta que podría tener algo así:

function randomPick(array) {
   let choice = Math.floor(Math.random() * array.length);
@@ -173,7 +171,7 @@ 

return {direction: randomPick(roadGraph[state.place])}; }

-

Recuerda que Math.random() devuelve un número entre cero y uno, pero siempre por debajo de uno. Multiplicar dicho número por la longitud de un array y luego aplicarle Math.floor nos da un índice aleatorio para el array.

+

Recuerda que Math.random() devuelve un número entre cero y uno, pero siempre por debajo de uno. Al multiplicar dicho número por la longitud de un array y luego aplicarle Math.floor, obtenemos un índice aleatorio para el array.

Dado que este robot no necesita recordar nada, ignora su segundo argumento (recuerda que las funciones de JavaScript pueden ser llamadas con argumentos adicionales sin efectos adversos) y omite la propiedad memory en su objeto devuelto.

@@ -221,7 +219,7 @@

"Plaza del Mercado", "Oficina de Correos" ];

-

Para implementar el robot que sigue la ruta, necesitaremos hacer uso de la memoria del robot. El robot guarda el resto de su ruta en su memoria y deja caer el primer elemento en cada turno.

+

Para implementar el robot que sigue la ruta, necesitaremos hacer uso de la memoria del robot. El robot guarda el resto de su ruta en su memoria y se desprende del primer elemento de la ruta en cada turno.

function routeRobot(state, memory) {
   if (memory.length == 0) {
@@ -236,15 +234,17 @@ 

Búsqueda de caminos

-

Aún así, no llamaría a seguir ciegamente una ruta fija un comportamiento inteligente. Sería más eficiente si el robot ajustara su comportamiento a la tarea real que debe realizarse.

+

Aún así, no creo que sea muy inteligente seguir ciegamente una ruta fija. Sería más eficiente si el robot ajustara su comportamiento a la tarea real que debe realizarse.

+ +

Para hacer eso, tiene que poder moverse deliberadamente hacia un destino dado o hacia la ubicación donde se debe entregar un paquete. Hacer eso, incluso cuando el objetivo está a más de un movimiento de distancia, requerirá algún tipo de función de búsqueda de ruta.

-

Para hacer eso, tiene que poder moverse deliberadamente hacia un paquete dado o hacia la ubicación donde se debe entregar un paquete. Hacer eso, incluso cuando el objetivo está a más de un movimiento de distancia, requerirá algún tipo de función de búsqueda de ruta.

+

El problema de encontrar una ruta a través de un grafo es un problema de búsqueda típico. Podemos determinar si una solución dada (es decir, una ruta) es una solución válida, pero no podemos hacer un cálculo directo de la solución como podríamos hacerlo para 2 + 2. En su lugar, debemos seguir creando soluciones potenciales hasta encontrar una que funcione.

-

El problema de encontrar una ruta a través de un grafo es un problema de búsqueda típico. Podemos determinar si una solución dada (una ruta) es una solución válida, pero no podemos calcular directamente la solución como podríamos hacerlo para 2 + 2. En su lugar, debemos seguir creando soluciones potenciales hasta encontrar una que funcione.

+

El número de rutas posibles a través de un grafo es enorme. Pero al buscar una ruta de A a B, solo estamos interesados en aquellas que comienzan en A. Además, no nos importan las rutas que visiten el mismo lugar dos veces —esas claramente no son las rutas más eficientes hacia ningún lugar. Así que eso reduce la cantidad de rutas que el buscador de rutas debe considerar.

-

El número de rutas posibles a través de un grafo es infinito. Pero al buscar una ruta de A a B, solo estamos interesados en aquellas que comienzan en A. Además, no nos importan las rutas que visiten el mismo lugar dos veces, esas definitivamente no son las rutas más eficientes en ningún lugar. Así que eso reduce la cantidad de rutas que el buscador de rutas debe considerar.De hecho, estamos mayormente interesados en la ruta más corta. Por lo tanto, queremos asegurarnos de buscar rutas cortas antes de mirar las más largas. Un buen enfoque sería “expandir” rutas desde el punto de inicio, explorando cada lugar alcanzable que aún no haya sido visitado, hasta que una ruta llegue al objetivo. De esta manera, solo exploraremos rutas que sean potencialmente interesantes, y sabremos que la primera ruta que encontremos es la ruta más corta (o una de las rutas más cortas, si hay más de una).

+

De hecho, estamos sobre todo interesados en la ruta más corta. Por lo tanto, queremos asegurarnos de buscar rutas cortas antes de mirar las más largas. Un buen enfoque sería “expandir” rutas desde el punto de inicio, explorando cada lugar alcanzable que aún no haya sido visitado, hasta que una ruta llegue al objetivo. De esta manera, solo exploraremos rutas que sean potencialmente interesantes, y sabremos que la primera ruta que encontremos es la ruta más corta (o una de las rutas más cortas, si hay más de una).

-

Aquí hay una función que hace esto:

+

Aquí, una función que hace esto:

function findRoute(graph, from, to) {
   let work = [{at: from, route: []}];
@@ -261,13 +261,13 @@ 

La exploración debe realizarse en el orden correcto: los lugares que se alcanzaron primero deben explorarse primero. No podemos explorar de inmediato un lugar tan pronto como lleguemos a él porque eso significaría que los lugares alcanzados desde allí también se explorarían de inmediato, y así sucesivamente, incluso si puede haber otros caminos más cortos que aún no se han explorado.

-

Por lo tanto, la función mantiene una lista de trabajo. Esta es una matriz de lugares que deben ser explorados a continuación, junto con la ruta que nos llevó allí. Comienza con solo la posición de inicio y una ruta vacía.

+

Por lo tanto, la función mantiene una lista de trabajo: un array de lugares que deben ser explorados a continuación, junto con la ruta que nos llevó allí. Comienza con solo la posición de inicio y una ruta vacía.

La búsqueda luego opera tomando el siguiente elemento en la lista y explorándolo, lo que significa que se ven todas las rutas que salen de ese lugar. Si una de ellas es el objetivo, se puede devolver una ruta terminada. De lo contrario, si no hemos mirado este lugar antes, se agrega un nuevo elemento a la lista. Si lo hemos mirado antes, dado que estamos buscando rutas cortas primero, hemos encontrado o bien una ruta más larga a ese lugar o una exactamente tan larga como la existente, y no necesitamos explorarla.

-

Puedes imaginar visualmente esto como una red de rutas conocidas que se extienden desde la ubicación de inicio, creciendo de manera uniforme en todos los lados (pero nunca enredándose de nuevo en sí misma). Tan pronto como el primer hilo alcance la ubicación objetivo, ese hilo se rastrea de vuelta al inicio, dándonos nuestra ruta.

+

Puedes imaginar visualmente esto como una red de rutas conocidas que se extienden desde la ubicación de inicio, creciendo de manera uniforme hacia todas partes (pero nunca enredándose de nuevo en sí misma). Tan pronto como el primer hilo alcance la ubicación objetivo, ese hilo se rastrea de vuelta al inicio, dándonos nuestra ruta.

-

Nuestro código no maneja la situación en la que no hay más elementos de trabajo en la lista de trabajo porque sabemos que nuestro gráfico está conectado, lo que significa que se puede llegar a cada ubicación desde todas las demás ubicaciones. Siempre podremos encontrar una ruta entre dos puntos, y la búsqueda no puede fallar.

+

Nuestro código no maneja la situación en la que no hay más elementos de trabajo en la lista de trabajo porque sabemos que nuestro grafo está conectado, lo que significa que se puede llegar a cada ubicación desde todas las demás ubicaciones. Siempre podremos encontrar una ruta entre dos puntos, y la búsqueda no puede fallar.

function goalOrientedRobot({place, parcels}, route) {
   if (route.length == 0) {
@@ -281,14 +281,14 @@ 

return {direction: route[0], memory: route.slice(1)}; }

-

Este robot utiliza el valor de su memoria como una lista de direcciones en las que moverse, al igual que el robot que sigue la ruta. Cuando esa lista está vacía, debe averiguar qué hacer a continuación. Toma el primer paquete no entregado del conjunto y, si ese paquete aún no ha sido recogido, traza una ruta hacia él. Si el paquete ya ha sido recogido, todavía necesita ser entregado, por lo que el robot crea una ruta hacia la dirección de entrega.

+

Este robot utiliza el valor de su memoria como una lista de direcciones a las que moverse, como con el robot que simplemente seguía rutas. Cuando esa lista está vacía, debe averiguar qué hacer a continuación. Toma el primer paquete no entregado del conjunto y, si ese paquete aún no ha sido recogido, traza una ruta hacia él. Si el paquete ya ha sido recogido, todavía necesita ser entregado, por lo que el robot crea una ruta hacia la dirección de entrega.

Veamos cómo lo hace.

runRobotAnimation(VillageState.random(),
                   goalOrientedRobot, []);
-

Este robot suele terminar la tarea de entregar 5 paquetes en aproximadamente 16 turnos. Eso es ligeramente mejor que routeRobot pero definitivamente no es óptimo.

+

Este robot suele terminar la tarea de entregar 5 paquetes en aproximadamente 16 turnos. Eso es ligeramente mejor que routeRobot pero está claro que no es óptimo.

Ejercicios

@@ -298,7 +298,7 @@

Escribe una función compareRobots que tome dos robots (y su memoria inicial). Debería generar 100 tareas y permitir que cada uno de los robots resuelva cada una de estas tareas. Cuando termine, debería mostrar el número promedio de pasos que cada robot dio por tarea.

-

Por el bien de la equidad, asegúrate de darle a cada tarea a ambos robots, en lugar de generar tareas diferentes por robot.

+

Para que sea una comparación justa, asegúrate de darle a cada tarea a ambos robots, en lugar de generar tareas diferentes por robot.

function compareRobots(robot1, memory1, robot2, memory2) {
   // Tu código aquí
@@ -310,13 +310,13 @@ 

Tendrás que escribir una variante de la función runRobot que, en lugar de registrar los eventos en la consola, devuelva el número de pasos que el robot tomó para completar la tarea.

-

Tu función de medición puede, entonces, en un bucle, generar nuevos estados y contar los pasos que toma cada uno de los robots. Cuando haya generado suficientes mediciones, puede usar console.log para mostrar el promedio de cada robot, que es el número total de pasos tomados dividido por el número de mediciones.

+

Tu función de medición puede, entonces, en un bucle, generar nuevos estados y contar los pasos que toma cada uno de los robots. Cuando haya generado suficientes mediciones, puede usar console.log para mostrar el promedio de cada robot, que es el número total de pasos dados dividido por el número de mediciones.

Eficiencia del robot

-

¿Puedes escribir un robot que termine la tarea de entrega más rápido que goalOrientedRobot? Si observas el comportamiento de ese robot, ¿qué cosas claramente absurdas hace? ¿Cómo podrían mejorarse?

+

¿Puedes escribir un robot que termine la tarea de entrega más rápido que goalOrientedRobot? Si observas el comportamiento de ese robot, ¿qué cosas evidentemente absurdas está haciendo? ¿Cómo podrían mejorarse?

Si resolviste el ejercicio anterior, es posible que desees utilizar tu función compareRobots para verificar si mejoraste el robot.

@@ -326,9 +326,9 @@

Mostrar pistas...
-

La principal limitación de goalOrientedRobot es que solo considera un paquete a la vez. A menudo caminará de un lado a otro del pueblo porque el paquete en el que está centrando su atención sucede que está en el otro lado del mapa, incluso si hay otros mucho más cerca.

+

La principal limitación de goalOrientedRobot es que considera los paquetes de uno en uno. A menudo caminará de un lado a otro del pueblo porque el paquete en el que está centrando su atención sucede que está en el otro lado del mapa, incluso si hay otros mucho más cerca.

-

Una posible solución sería calcular rutas para todos paquetes y luego tomar la más corta. Se pueden obtener resultados aún mejores, si hay múltiples rutas más cortas, al preferir aquellas que van a recoger un paquete en lugar de entregarlo.

+

Una posible solución sería calcular rutas para todos paquetes y luego tomar la más corta. Se pueden obtener resultados aún mejores, si hay múltiples rutas más cortas, prefiriendo las que van a recoger un paquete en vez de entregarlo.

@@ -340,7 +340,7 @@

Sin embargo, su método add debería devolver una nueva instancia de PGroup con el miembro dado añadido y dejar la anterior sin cambios. De manera similar, delete crea una nueva instancia sin un miembro dado.

-

La clase debería funcionar para valores de cualquier tipo, no solo para strings. No tiene que ser eficiente cuando se utiliza con grandes cantidades de valores.

+

La clase debería funcionar para valores de cualquier tipo, no solo para strings. No tiene que ser eficiente cuando se utiliza con grandes cantidades de valores.

El constructor no debería ser parte de la interfaz de la clase (aunque definitivamente querrás usarlo internamente). En su lugar, hay una instancia vacía, PGroup.empty, que se puede usar como valor inicial.

From 325812ee84c8290ff2c324df88848dfa20daddc2 Mon Sep 17 00:00:00 2001 From: ckdvk Date: Sun, 16 Feb 2025 01:08:57 +0800 Subject: [PATCH 19/36] capitulo 8 revisado --- 08_error.md | 266 +++++++++++++++++++++---------------------- html/08_error.html | 274 +++++++++++++++++++++++---------------------- 2 files changed, 274 insertions(+), 266 deletions(-) diff --git a/08_error.md b/08_error.md index 4801efb3..a4c23977 100644 --- a/08_error.md +++ b/08_error.md @@ -4,7 +4,7 @@ {{quote {author: "Brian Kernighan and P.J. Plauger", title: "The Elements of Programming Style", chapter: true} -Depurar es el doble de difícil que escribir el código en primer lugar. Por lo tanto, si escribes el código lo más ingeniosamente posible, por definición, no eres lo suficientemente inteligente como para depurarlo. +Depurar es el doble de difícil que escribir el código por primera vez. Por lo tanto, si escribes el código de la manera más inteligente posible, no eres, por definición, lo suficientemente inteligente como para depurarlo. quote}} @@ -12,25 +12,25 @@ quote}} {{index "Kernighan, Brian", "Plauger, P.J.", "depuración", "manejo de errores"}} -Las fallas en los programas de computadora generalmente se llaman _((bug))s_. Hace que los programadores se sientan bien imaginarlos como pequeñas cosas que simplemente se meten en nuestro trabajo. En realidad, por supuesto, nosotros mismos los colocamos allí. +Los errores en los programas de computadora generalmente se llaman _((bug))s_. Los programadores nos sentimos mejor imaginándolos como pequeñas cosas que simplemente se meten en nuestro trabajo. Por supuesto, en realidad, somos nosotros mismos quienes los colocamos allí. -Si un programa es pensamiento cristalizado, puedes clasificar aproximadamente los errores en aquellos causados por pensamientos confusos y aquellos causados por errores introducidos al convertir un pensamiento en código. El primer tipo generalmente es más difícil de diagnosticar y arreglar que el último. +Si entendemos un programa como pensamiento cristalizado, podemos clasificar los errores más o menos en aquellos causados por pensamientos confusos y aquellos causados por errores introducidos al convertir un pensamiento en código. El primer tipo generalmente es más difícil de diagnosticar y arreglar que el último.º ## Lenguaje {{index "análisis", parsing}} -Muchos errores podrían ser señalados automáticamente por la computadora, si supiera lo suficiente sobre lo que estamos intentando hacer. Pero la laxitud de JavaScript es un obstáculo aquí. Su concepto de enlaces y propiedades es lo suficientemente vago como para rara vez atrapar ((typo))s antes de ejecutar realmente el programa. E incluso entonces, te permite hacer algunas cosas claramente absurdas sin quejarse, como calcular `true * "monkey"`. +Muchos errores podrían ser señalados automáticamente por la computadora si esta supiera lo suficiente sobre lo que estamos intentando hacer. Pero la laxitud de JavaScript es un obstáculo aquí. Su concepto de asociaciones y propiedades es lo suficientemente vago como para rara vez atrapar ((errata))s antes de ejecutar realmente el programa. E incluso entonces, todavía te permite hacer algunas cosas claramente absurdas sin quejarse, como calcular `true * "monkey"`. {{index [sintaxis, error], [propiedad, acceso]}} -Hay algunas cosas sobre las que JavaScript sí se queja. Escribir un programa que no siga la ((gramática)) del lenguaje hará que la computadora se queje de inmediato. Otras cosas, como llamar a algo que no es una función o buscar una propiedad en un valor ((undefined)) harán que se reporte un error cuando el programa intente realizar la acción. +Hay algunas cosas sobre las que JavaScript sí que se queja. Escribir un programa que no siga la ((gramática)) del lenguaje hará que la computadora se queje de inmediato. Otras cosas, como llamar a algo que no es una función o buscar una propiedad en un valor ((undefined)) harán que se reporte un error cuando el programa intente realizar la acción. {{index NaN, error}} -Pero a menudo, tu cálculo absurdo simplemente producirá `NaN` (no es un número) o un valor indefinido, mientras que el programa continúa felizmente, convencido de que está haciendo algo significativo. El error se manifestará solo más tarde, después de que el valor falso haya pasado por varias funciones. Es posible que no desencadene un error en absoluto, pero silenciosamente cause que la salida del programa sea incorrecta. Encontrar la fuente de tales problemas puede ser difícil. +Pero a menudo, tu cálculo absurdo simplemente producirá `NaN` (not a number) o un valor indefinido, mientras que el programa continúa alegremente, convencido de que está haciendo algo con sentido. El error solo se pondrá de manifiesto más adelante, después de que el valor falso haya pasado ya por varias funciones. Es posible que no desencadene ningún error, sino que silenciosamente cause que la salida del programa sea incorrecta. Encontrar la fuente de tales problemas puede ser difícil. -El proceso de encontrar errores—bugs—en los programas se llama _((depuración))_. +El proceso de encontrar errores —bugs— en los programas se llama _((depuración))_ (en inglés, _debugging_). ## Modo estricto @@ -38,65 +38,67 @@ El proceso de encontrar errores—bugs—en los programas se llama _((depuració {{indexsee "use strict", "modo estricto"}} -JavaScript puede ser un _poco_ más estricto al habilitar el _modo estricto_. Esto se hace colocando la cadena `"use strict"` en la parte superior de un archivo o en el cuerpo de una función. Aquí tienes un ejemplo: +Se puede hacer que JavaScript sea un _poco_ más estricto al habilitar el _modo estricto_. Esto se hace colocando la cadena `"use strict"` en la parte superior de un archivo o en el cuerpo de una función. Aquí tienes un ejemplo: ```{test: "error \"ReferenceError: counter is not defined\""} -function canYouSpotTheProblem() { +function puedesEncontrarElProblema() { "use strict"; - for (counter = 0; counter < 10; counter++) { + for (contador = 0; contador < 10; contador++) { console.log("Happy happy"); } } -canYouSpotTheProblem(); -// → ReferenceError: counter is not defined +puedesEncontrarElProblema(); +// → ReferenceError: contador is not defined ``` +El código de dentro de una clase o un módulo (que veremos en el [Capítulo ?](modules)) se considera automáticamente en modo estricto. Se sigue manteniendo el comportamiento no estricto solo porque hay algo de código antiguo que podría quizá depender de él. De esta manera, los diseñadores del lenguaje evitan romper programas existentes. + {{index "let keyword", [binding, global]}} -Normalmente, cuando olvidas poner `let` frente a tu enlace, como en el caso de `counter` en el ejemplo, JavaScript silenciosamente crea un enlace global y lo utiliza. En modo estricto, se reporta un ((error)) en su lugar. Esto es muy útil. Sin embargo, cabe mencionar que esto no funciona cuando el enlace en cuestión ya existe en algún lugar del ámbito. En ese caso, el bucle seguirá sobrescribiendo silenciosamente el valor del enlace. +Normalmente, cuando olvidas poner `let` frente a tu asociación, como en el caso de `counter` en el ejemplo, JavaScript silenciosamente crea un enlace global y lo utiliza. En modo,estricto, sin embargo, se reporta un ((error)). Esto es muy útil. No obstante, cabe mencionar que no aparecerá ningún mensaje de error cuando la asociación en cuestión ya existe en alguna parte del ámbito. En ese caso, el bucle igualmente sobrescribirá silenciosamente el valor de la asociación y seguirá con su tarea. {{index "this binding", "global object", undefined, "strict mode"}} -Otro cambio en el modo estricto es que el enlace `this` mantiene el valor `undefined` en funciones que no son llamadas como ((método))s. Al hacer una llamada de este tipo fuera del modo estricto, `this` se refiere al objeto de ámbito global, que es un objeto cuyas propiedades son los enlaces globales. Entonces, si accidentalmente llamas incorrectamente a un método o constructor en modo estricto, JavaScript producirá un error tan pronto como intente leer algo de `this`, en lugar de escribir felizmente en el ámbito global. +Otro cambio en el modo estricto es que el enlace `this` tiene el valor `undefined` en funciones que no son llamadas como ((método))s. Al hacer una llamada de este tipo fuera del modo estricto, `this` se refiere al objeto del ámbito global, que es un objeto cuyas propiedades son los enlaces globales. Así que, si llamas incorrectamente a un método o constructor por error en modo estricto, JavaScript producirá un error tan pronto como intente leer algo de `this`, en lugar de escribir en el ámbito global. -Por ejemplo, considera el siguiente código, que llama a una función ((constructor)) sin la palabra clave `new` para que su `this` _no_ se refiera a un objeto recién construido: +Considera, por ejemplo, el siguiente código, que llama a una función ((constructor)) sin la palabra clave `new` para que su `this` _no_ se refiera a un objeto recién construido: ``` -function Person(name) { this.name = name; } -let ferdinand = Person("Ferdinand"); // oops -console.log(name); +function Persona(nombre) { this.nombre = nombre; } +let ferdinand = Persona("Ferdinand"); // oops +console.log(nombre); // → Ferdinand ``` {{index error}} -Entonces, la llamada falsa a `Person` tuvo éxito pero devolvió un valor no definido y creó el enlace global `name`. En modo estricto, el resultado es diferente. +La llamada errónea a `Persona` ha tenido éxito pero ha devuelto un valor no definido y ha creado el enlace global `name`. En modo estricto, el resultado es diferente. ```{test: "error \"TypeError: Cannot set properties of undefined (setting 'name')\""} "use strict"; -function Person(name) { this.name = name; } -let ferdinand = Person("Ferdinand"); // olvidó el new -// → TypeError: Cannot set property 'name' of undefined +function Persona(nombre) { this.nombre = nombre; } +let ferdinand = Persona("Ferdinand"); // falta el new +// → TypeError: Cannot set property 'nombre' of undefined ``` -Inmediatamente se nos informa que algo está mal. Esto es útil. +Inmediatamente se nos informa de que algo falla. Esto es útil. -Afortunadamente, los constructores creados con la notación `class` siempre mostrarán una queja si se llaman sin `new`, lo que hace que esto sea menos problemático incluso en modo no estricto. +Por suerte, los constructores creados con la notación `class` siempre se van a quejar si se llaman sin `new`, conque esto no será tanto problema incluso en modo no estricto. {{index parameter, [binding, naming], "with statement"}} -El modo estricto hace algunas cosas más. Prohíbe darle a una función múltiples parámetros con el mismo nombre y elimina ciertas características problemáticas del lenguaje por completo (como la declaración `with`, que es tan incorrecta que no se discute más en este libro). +El modo estricto hace algunas cosas más. Prohíbe darle a una función múltiples parámetros con el mismo nombre y elimina ciertas características problemáticas del lenguaje por completo (como la declaración `with`, que es tan incorrecta que ni se va a discutir más en este libro). {{index debugging}} -En resumen, colocar `"use strict"` al principio de tu programa rara vez duele y podría ayudarte a identificar un problema. +En resumen, colocar `"use strict"` al principio de tu programa rara vez hace daño y podría ayudarte a identificar un problema. ## Tipos -Algunos lenguajes quieren saber los tipos de todos tus enlaces y expresiones antes de ejecutar un programa. Te indicarán de inmediato cuando un tipo se utiliza de manera inconsistente. JavaScript considera los tipos solo cuando realmente se ejecuta el programa, e incluso allí a menudo intenta convertir valores implícitamente al tipo que espera, por lo que no es de mucha ayuda. +Algunos lenguajes quieren saber los tipos de todas tus asociaciones y expresiones antes de ejecutar un programa. Te indicarán de inmediato cuando un tipo se utiliza de manera inconsistente. JavaScript considera los tipos solo cuando realmente se ejecuta el programa, e incluso allí a menudo intenta convertir valores implícitamente al tipo que espera, por lo que no es de mucha ayuda. -No obstante, los tipos proporcionan un marco útil para hablar sobre programas. Muchos errores provienen de estar confundido acerca del tipo de valor que entra o sale de una función. Si tienes esa información escrita, es menos probable que te confundas.Podrías agregar un comentario como el siguiente antes de la función `findRoute` del capítulo anterior para describir su tipo: +Aun así, los tipos proporcionan un marco útil para hablar sobre programas. Muchos errores surgen de la confusión acerca del tipo de valor que entra o sale de una función. Si tienes esa información escrita, es menos probable que te confundas. Podrías agregar un comentario como el siguiente antes de la función `findRoute` del capítulo anterior para describir su tipo: ``` // (graph: Object, from: string, to: string) => string[] @@ -107,7 +109,7 @@ function findRoute(graph, from, to) { Existen varias convenciones diferentes para anotar programas de JavaScript con tipos. -Una cosa sobre los tipos es que necesitan introducir su propia complejidad para poder describir suficiente código para ser útiles. ¿Qué tipo crees que tendría la función `randomPick` que devuelve un elemento aleatorio de un array? Necesitarías introducir una _((variable de tipo))_, _T_, que pueda representar cualquier tipo, para que puedas darle a `randomPick` un tipo como `(T[]) → T` (función de un array de *T* a un *T*). +Una cosa sobre los tipos es que necesitan introducir su propia complejidad para ser capaces de describir el suficiente código como para ser útiles. ¿Qué tipo crees que tendría la función `randomPick` que devuelve un elemento aleatorio de un array? Necesitarías introducir una _((variable de tipo))_, _T_, que pueda representar cualquier tipo, para que puedas darle a `randomPick` un tipo como `(T[]) → T` (función de un array de *T* a un *T*). {{index "comprobación de tipos", TypeScript}} @@ -117,49 +119,49 @@ Cuando los tipos de un programa son conocidos, es posible que la computadora los En este libro, continuaremos utilizando código JavaScript crudo, peligroso y sin tipos. -## Pruebas +## Testing {{index "suite de pruebas", "error en tiempo de ejecución", "automatización", pruebas}} -Si el lenguaje no nos va a ayudar mucho a encontrar errores, tendremos que encontrarlos a la antigua: ejecutando el programa y viendo si hace lo correcto. +Si el lenguaje no nos va a ayudar mucho a encontrar errores, habrá que encontrarlos por las malas: ejecutando el programa y viendo si hace lo correcto. -Hacer esto manualmente, una y otra vez, es una idea muy mala. No solo es molesto, también tiende a ser ineficaz, ya que lleva demasiado tiempo probar exhaustivamente todo cada vez que haces un cambio. +Hacer esto manualmente, una y otra vez, es una idea muy mala. No solo es una lata, sino que tiende a ser ineficaz, ya que lleva demasiado tiempo probar exhaustivamente todo cada vez que haces un cambio. -Las computadoras son buenas en tareas repetitivas, y las pruebas son la tarea repetitiva ideal. Las pruebas automatizadas son el proceso de escribir un programa que prueba otro programa. Es un poco más trabajo escribir pruebas que probar manualmente, pero una vez que lo has hecho, adquieres una especie de superpoder: solo te llevará unos segundos verificar que tu programa siga comportándose correctamente en todas las situaciones para las que escribiste pruebas. Cuando rompes algo, lo notarás de inmediato en lugar de encontrártelo al azar en algún momento posterior. +Las computadoras son buenas en tareas repetitivas, y las pruebas (o el testing) son la tarea repetitiva ideal. Los tests automatizados son el proceso de escribir un programa que testea otro programa. Lleva algo más de trabajo escribir tests que hacer las pruebas a mano, pero una vez que lo has hecho, adquieres una especie de superpoder: solo te llevará unos segundos verificar que tu programa sigue comportándose correctamente en todas las situaciones para las que escribiste tus tests. Cuando rompes algo, lo notarás de inmediato en lugar de encontrártelo de casualidad más adelante. {{index "método toUpperCase"}} -Las pruebas suelen tomar la forma de pequeños programas etiquetados que verifican algún aspecto de tu código. Por ejemplo, un conjunto de pruebas para el (probablemente ya probado por alguien más) método `toUpperCase` estándar podría lucir así: +Los tests suelen ser pequeños programas etiquetados que verifican algún aspecto de tu código. Por ejemplo, un conjunto de tests para el (probablemente ya probado por alguien más) método `toUpperCase` estándar podría tener esta pinta: ``` -function test(label, body) { - if (!body()) console.log(`Fallo: ${label}`); +function test(etiqueta, cuerpo) { + if (!cuerpo()) console.log(`Fallo: ${etiqueta}`); } test("convertir texto latino a mayúsculas", () => { - return "hello".toUpperCase() == "HELLO"; + return "hola".toUpperCase() == "HOLA"; }); test("convertir texto griego a mayúsculas", () => { return "Χαίρετε".toUpperCase() == "ΧΑΊΡΕΤΕ"; }); -test("no convertir caracteres sin caso", () => { +test("no convertir caracteres sin mayúsculas", () => { return "مرحبا".toUpperCase() == "مرحبا"; }); ``` {{index "lenguaje específico de dominio"}} -Escribir pruebas de esta forma tiende a producir código bastante repetitivo y torpe. Afortunadamente, existen software que te ayudan a construir y ejecutar colecciones de pruebas (_((suites de pruebas))_) al proporcionar un lenguaje (en forma de funciones y métodos) adecuado para expresar pruebas y al producir información informativa cuando una prueba falla. Estos suelen llamarse _((corredores de pruebas))_. +Escribir tests de esta manera tiende a generar código repetitivo y poco elegante. Por suerte, hay software que te ayuda a construir y ejecutar colecciones de tests (_((test suites))_) al proporcionar un lenguaje (en forma de funciones y métodos) adecuado para expresar tests y producir información descriptiva cuando un test falla. Estas herramientas suelen llamarse _((test runners))_. {{index "estructura de datos persistente"}} -Alguno código es más fácil de probar que otro código. Generalmente, cuantos más objetos externos interactúan con el código, más difícil es configurar el contexto para probarlo. El estilo de programación mostrado en el [capítulo anterior](robot), que utiliza valores persistentes autocontenidos en lugar de objetos cambiantes, tiende a ser fácil de probar. +Hay códigos más fáciles de testar que otros. Generalmente, cuantos más objetos externos interactúan con el código, más difícil es configurar el contexto para testearlo. El estilo de programación que vimos en el [capítulo anterior](robot), que utiliza valores persistentes autocontenidos en lugar de objetos cambiantes, suele ser fácil de probar. ## Depuración {{index debugging}} -Una vez que notas que hay algo mal en tu programa porque se comporta de manera incorrecta o produce errores, el siguiente paso es descubrir _cuál_ es el problema. +Una vez que notas que hay algo mal en tu programa porque no se comporta como debe o produce errores, el siguiente paso es descubrir _cuál_ es el problema. A veces es obvio. El mensaje de ((error)) señalará una línea específica de tu programa, y si miras la descripción del error y esa línea de código, a menudo puedes ver el problema. @@ -169,22 +171,22 @@ Pero no siempre. A veces la línea que desencadenó el problema es simplemente e {{index "número decimal", "número binario"}} -El siguiente programa de ejemplo intenta convertir un número entero en una cadena en una base dada (decimal, binaria, y así sucesivamente) al seleccionar repetidamente el último ((dígito)) y luego dividir el número para deshacerse de este dígito. Pero la extraña salida que produce actualmente sugiere que tiene un ((error)). +El siguiente programa de ejemplo intenta convertir un número entero en una cadena en una base dada (decimal, binaria, etc.) al seleccionar consecutivamente el último ((dígito)) y luego dividir el número para deshacerse de este dígito. Pero la extraña salida que produce actualmente sugiere que tiene un ((error)). ``` -function numberToString(n, base = 10) { - let result = "", sign = ""; +function númeroACadena(n, base = 10) { + let resultado = "", signo = ""; if (n < 0) { - sign = "-"; + signo = "-"; n = -n; } do { - result = String(n % base) + result; + resultado = String(n % base) + resultado; n /= base; } while (n > 0); - return sign + result; + return signo + resultado; } -console.log(numberToString(13, 10)); +console.log(númeroACadena(13, 10)); // → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3… ``` @@ -194,11 +196,11 @@ Incluso si ya ves el problema, finge por un momento que no lo haces. Sabemos que {{index "ensayo y error"}} -Aquí es donde debes resistir la tentación de empezar a hacer cambios aleatorios en el código para ver si eso lo mejora. En cambio, _piensa_. Analiza lo que está sucediendo y elabora una ((teoría)) sobre por qué podría estar ocurriendo. Luego, realiza observaciones adicionales para probar esta teoría, o si aún no tienes una teoría, realiza observaciones adicionales para ayudarte a crear una. +Aquí es donde debes resistir la tentación de empezar a hacer cambios aleatorios en el código para ver si así mejora. En vez de eso, _piensa_. Analiza lo que está sucediendo y elabora una ((teoría)) sobre por qué podría estar ocurriendo. Luego, realiza observaciones adicionales para probar esta teoría, o, si aún no tienes una teoría, realiza observaciones adicionales para ayudarte a crear una. {{index "console.log", salida, "depuración", registro}} -Colocar algunas llamadas `console.log` estratégicas en el programa es una buena manera de obtener información adicional sobre lo que está haciendo el programa. En este caso, queremos que `n` tome los valores `13`, `1` y luego `0`. Vamos a escribir su valor al inicio del ciclo. +Colocar algunas llamadas a `console.log` estratégicamente en el programa es una buena manera de obtener información adicional sobre lo que este está haciendo. En este caso, queremos que `n` tome los valores `13`, `1` y luego `0`. Vamos a escribir su valor al inicio del bucle. ```{lang: null} 13 @@ -211,59 +213,61 @@ Colocar algunas llamadas `console.log` estratégicas en el programa es una buena {{index rounding}} -_Correcto_. Al dividir 13 por 10 no se produce un número entero. En lugar de `n /= base`, lo que realmente queremos es `n = Math.floor(n / base)` para que el número se "desplace" correctamente hacia la derecha. +_Correcto_. Al dividir 13 por 10 no se produce un número entero. En lugar de `n /= base`, lo que realmente queremos es `n = Math.floor(n / base)` de manera que pasamos correctamente a calcular el siguiente dígito. {{index "consola de JavaScript", "sentencia de depuración"}} -Una alternativa a usar `console.log` para observar el comportamiento del programa es utilizar las capacidades del _depurador_ de tu navegador. Los navegadores vienen con la capacidad de establecer un _((punto de interrupción))_ en una línea específica de tu código. Cuando la ejecución del programa llega a una línea con un punto de interrupción, se pausa y puedes inspeccionar los valores de las asignaciones en ese punto. No entraré en detalles, ya que los depuradores difieren de un navegador a otro, pero busca en las ((herramientas de desarrollo)) de tu navegador o busca instrucciones en la Web.Otra forma de establecer un punto de interrupción es incluir una instrucción `debugger` (consistente únicamente en esa palabra clave) en tu programa. Si las herramientas de ((desarrollo)) de tu navegador están activas, el programa se pausará cada vez que alcance dicha instrucción. +Una alternativa a usar `console.log` para observar el comportamiento del programa es utilizar las capacidades del _depurador_ de tu navegador. Los navegadores vienen con la capacidad de establecer un _((punto de interrupción))_ en una línea específica de tu código. Cuando la ejecución del programa llega a una línea con un punto de interrupción, esta se pausa y puedes inspeccionar los valores de las asignaciones o variables en ese punto. No entraré en detalles, ya que los depuradores difieren de un navegador a otro, pero busca en las ((herramientas de desarrollo)) de tu navegador o busca instrucciones en la web. + +Otra forma de establecer un punto de interrupción es incluir una instrucción `debugger` (consistente únicamente en esa palabra clave) en tu programa. Si las herramientas de ((desarrollo)) de tu navegador están activas, el programa se pausará cada vez que alcance dicha instrucción. ## Propagación de errores {{index entrada, salida, "error en tiempo de ejecución", error, "validación"}} -Lamentablemente, no todos los problemas pueden ser prevenidos por el programador. Si tu programa se comunica de alguna manera con el mundo exterior, es posible recibir entradas malformadas, sobrecargarse de trabajo o que falle la red. +Lamentablemente, el programador no puede evitar todos los problemas. Si tu programa se comunica de alguna manera con el mundo exterior, es posible recibir entradas con el formato incorrecto, sobrecargarse de trabajo o que falle la red. {{index "recuperación de errores"}} -Si estás programando solo para ti, puedes permitirte simplemente ignorar esos problemas hasta que ocurran. Pero si estás construyendo algo que será utilizado por alguien más, generalmente quieres que el programa haga algo más que simplemente colapsar. A veces lo correcto es aceptar la entrada incorrecta y continuar ejecutándose. En otros casos, es mejor informar al usuario sobre lo que salió mal y luego rendirse. Pero en cualquier situación, el programa debe hacer algo activamente en respuesta al problema. +Si estás programando solo para ti, puedes permitirte simplemente ignorar esos problemas hasta que ocurran. Pero si estás construyendo algo que será utilizado por alguien más, generalmente quieres que el programa haga algo más que simplemente colapsar. A veces lo correcto es aceptar la entrada errónea y continuar ejecutándose. En otros casos, lo mejor es informar al usuario sobre lo que salió mal y luego rendirse. Pero, en cualquier caso, el programa debe hacer algo activamente en respuesta al problema. {{index "función promptNumber", "validación"}} -Imaginemos que tienes una función `promptNumber` que solicita al usuario un número y lo retorna. ¿Qué debería retornar si el usuario ingresa "naranja"? +Imaginemos que tienes una función `solicitarNúmero` que solicita al usuario un número y lo devuelve. ¿Qué debería devolver si el usuario dice "naranja"? {{index null, undefined, "valor de retorno", "valor de retorno especial"}} -Una opción es hacer que retorne un valor especial. Las opciones comunes para tales valores son `null`, `undefined` o -1. +Una opción es hacer que devuelva un valor especial. Algunas opciones comunes para tales valores son `null`, `undefined` o -1. ```{test: no} -function promptNumber(pregunta) { +function solicitarNúmero(pregunta) { let resultado = Number(prompt(pregunta)); if (Number.isNaN(resultado)) return null; else return resultado; } -console.log(promptNumber("¿Cuántos árboles ves?")); +console.log(solicitarNúmero("¿Cuántos árboles ves?")); ``` -Ahora, cualquier código que llame a `promptNumber` debe verificar si se leyó un número real y, de no ser así, debe recuperarse de alguna manera, quizás volviendo a preguntar o completando con un valor predeterminado. O podría retornar nuevamente un valor especial a su llamante para indicar que no pudo hacer lo que se le pidió. +Ahora, cualquier código que llame a `solicitarNúmero` debe verificar si de verdad se leyó un número y, de no ser así, debe recuperarse de alguna manera, quizás volviendo a preguntar o completando con un valor predeterminado. O podría devolver nuevamente un valor especial a quién la llamó para indicar que no pudo hacer lo que se le pidió. {{index "manejo de errores"}} -En muchas situaciones, sobre todo cuando los ((errores)) son comunes y el llamante debería tomarlos explícitamente en cuenta, retornar un valor especial es una buena manera de indicar un error. Sin embargo, tiene sus inconvenientes. Primero, ¿qué pasa si la función ya puede devolver todos los tipos posibles de valores? En tal función, tendrás que hacer algo como envolver el resultado en un objeto para poder distinguir el éxito del fracaso, de la misma manera que lo hace el método `next` en la interfaz del iterador. +En muchas situaciones, sobre todo cuando los ((errores)) son comunes y el llamante debería tomarlos explícitamente en cuenta, devolver un valor especial es una buena manera de indicar un error. Sin embargo, tiene sus inconvenientes. Primero, ¿qué pasa si la función ya puede devolver todos los tipos posibles de valores? En tal función, tendrás que hacer algo como envolver el resultado en un objeto para poder distinguir el éxito del fracaso, de la misma manera que lo hace el método `next` en la interfaz del iterador. ``` -function lastElement(arreglo) { - if (arreglo.length == 0) { +function últimoElemento(array) { + if (array.length == 0) { return {falló: true}; } else { - return {valor: arreglo[arreglo.length - 1]}; + return {valor: array[array.length - 1]}; } } ``` {{index "valor de retorno especial", legibilidad}} -El segundo problema con retornar valores especiales es que puede llevar a un código incómodo. Si un fragmento de código llama a `promptNumber` 10 veces, tendrá que verificar 10 veces si se devolvió `null`. Y si su respuesta al encontrar `null` es simplemente devolver `null` en sí mismo, los llamantes de la función a su vez tendrán que comprobarlo, y así sucesivamente. +El segundo problema con devolver valores especiales es que puede hacer que el código sea incómodo de manejar. Si un fragmento de código llama a `solicitarNúmero` 10 veces, tendrá que verificar 10 veces si se devolvió `null`. Y si su respuesta al encontrar `null` es simplemente devolver `null` en sí mismo, los que llamen a la función a su vez tendrán que comprobarlo, y así sucesivamente. ## Excepciones @@ -273,33 +277,33 @@ Cuando una función no puede proceder normalmente, lo que a menudo _queremos_ ha {{index ["flujo de control", excepciones], "lanzar (excepción)", "palabra clave throw", "pila de llamadas"}} -Las excepciones son un mecanismo que hace posible que el código que se encuentra con un problema _lanze_ (o _emita_) una excepción. Una excepción puede ser cualquier valor. Lanzar una se asemeja de alguna manera a un retorno super potenciado de una función: sale no solo de la función actual sino también de sus llamadores, hasta llegar a la primera llamada que inició la ejecución actual. Esto se llama _((desenrollar la pila))_. Puede recordar la pila de llamadas a funciones que se mencionó en el [Capítulo ?](functions#stack). Una excepción recorre esta pila, descartando todos los contextos de llamada que encuentra. +Las excepciones son un mecanismo que hace posible que el código que se encuentra con un problema _lance_ (o _emita_) una excepción. Una excepción puede ser cualquier valor. Lanzar una es de alguna manera como un retorno de función supervitaminado: no solo se sale fuera de la función actual sino también de sus llamadores, hasta llegar a la primera llamada que inició la ejecución actual. Esto se llama _((desenrollar la pila))_. Recordarás la pila de llamadas a funciones que se mencionó en el [Capítulo ?](functions#stack). Una excepción recorre esta pila, descartando todos los contextos de llamada que encuentra. {{index "manejo de errores", [sintaxis, "declaración"], "palabra clave catch"}} -Si las excepciones siempre fueran directamente hasta el final de la pila, no serían de mucha utilidad. Simplemente proporcionarían una forma novedosa de hacer que su programa falle. Su poder radica en el hecho de que puede colocar "obstáculos" a lo largo de la pila para _capturar_ la excepción mientras viaja hacia abajo. Una vez que ha capturado una excepción, puede hacer algo con ella para resolver el problema y luego continuar ejecutando el programa. +Si las excepciones siempre fueran directamente hasta el final de la pila, no serían de mucha utilidad. Simplemente serían una forma alternativa de hacer que tu programa falle. Su poder radica en el hecho de que puede colocar "obstáculos" a lo largo de la pila para _capturar_ la excepción mientras viaja hacia afuera. Una vez que ha capturado una excepción, puede hacer algo con ella para resolver el problema y luego continuar ejecutando el programa. Aquí tienes un ejemplo: {{id look}} ``` -function promptDirection(question) { - let result = prompt(question); - if (result.toLowerCase() == "left") return "L"; - if (result.toLowerCase() == "right") return "R"; - throw new Error("Dirección inválida: " + result); +function solicitarDirección(pregunta) { + let resultado = prompt(pregunta); + if (resultado.toLowerCase() == "izquierda") return "L"; + if (resultado.toLowerCase() == "derecha") return "R"; + throw new Error("Dirección inválida: " + resultado); } -function look() { - if (promptDirection("¿Hacia dónde?") == "L") { +function mirar() { + if (solicitarDirección("¿Hacia dónde?") == "L") { return "una casa"; } else { - return "dos osos enojados"; + return "dos osos enfadados"; } } try { - console.log("Ves", look()); + console.log("Ves", mirar()); } catch (error) { console.log("Algo salió mal: " + error); } @@ -307,15 +311,15 @@ try { {{index "manejo de excepciones", bloque, "palabra clave throw", "palabra clave try", "palabra clave catch"}} -La palabra clave `throw` se utiliza para lanzar una excepción. La captura de una excepción se realiza envolviendo un trozo de código en un bloque `try`, seguido de la palabra clave `catch`. Cuando el código en el bloque `try` provoca que se lance una excepción, se evalúa el bloque `catch`, con el nombre entre paréntesis vinculado al valor de la excepción. Después de que el bloque `catch` finalice, o si el bloque `try` finaliza sin problemas, el programa continúa debajo de toda la instrucción `try/catch`. +La palabra clave `throw` se utiliza para lanzar una excepción. La captura de una excepción se realiza envolviendo un trozo de código en un bloque `try`, seguido de la palabra clave `catch`. Cuando el código en el bloque `try` provoca que se lance una excepción, se evalúa el bloque `catch`, con el nombre entre paréntesis vinculado al valor de la excepción. Cuando el bloque `catch` acabe, o cuando el bloque `try` finalice sin problemas, el programa continúa debajo de toda la instrucción `try/catch`. {{index "depuración", "pila de llamadas", "Tipo de error"}} -En este caso, utilizamos el ((constructor)) `Error` para crear nuestro valor de excepción. Este es un constructor de JavaScript ((estándar)) que crea un objeto con una propiedad `message`. Las instancias de `Error` también recopilan información sobre la pila de llamadas que existía cuando se creó la excepción, una llamada _((traza de pila))_. Esta información se almacena en la propiedad `stack` y puede ser útil al intentar depurar un problema: nos indica la función donde ocurrió el problema y qué funciones realizaron la llamada fallida. +En este caso, utilizamos el ((constructor)) `Error` para crear nuestro valor de excepción. Este es un constructor de JavaScript ((estándar)) que crea un objeto con una propiedad `message`. Las instancias de `Error` también recopilan información sobre la pila de llamadas que existía cuando se creó la excepción, lo que se conoce como una _((traza de pila))_. Esta información se almacena en la propiedad `stack` y puede ser útil al intentar depurar un problema: nos indica la función donde ocurrió el problema y qué funciones realizaron la llamada fallida. {{index "manejo de excepciones"}} -Ten en cuenta que la función `look` ignora por completo la posibilidad de que `promptDirection` pueda fallar. Esta es la gran ventaja de las excepciones: el código de manejo de errores solo es necesario en el punto donde ocurre el error y en el punto donde se maneja. Las funciones intermedias pueden olvidarse por completo de ello. +Ten en cuenta que la función `mirar` ignora por completo la posibilidad de que `solicitarDirección` pueda fallar. Esta es la gran ventaja de las excepciones: el código de manejo de errores solo es necesario en el punto donde ocurre el error y en el punto donde se maneja. Las funciones intermedias pueden olvidarse por completo de ello. Bueno, casi... @@ -323,72 +327,72 @@ Bueno, casi... {{index "manejo de excepciones", "limpieza", ["flujo de control", excepciones]}} -El efecto de una excepción es otro tipo de flujo de control. Cada acción que pueda causar una excepción, que es prácticamente cada llamada a función y acceso a propiedad, puede hacer que el control salga repentinamente de tu código. +El resultado de una excepción es otro tipo de flujo de control. Cada acción que pueda causar una excepción, que es prácticamente cualquier llamada a función y acceso a propiedad, puede hacer que el control salga repentinamente de tu código. -Esto significa que cuando el código tiene varios efectos secundarios, incluso si su flujo de control "regular" parece que siempre ocurrirán todos, una excepción podría evitar que algunos de ellos sucedan. +Esto significa que, cuando el código tiene varios efectos secundarios, una excepción podría impedir que algunos de ellos ocurran, incluso si en el flujo de control "normal" parece que siempre deberían ejecutarse todos. {{index "ejemplo de banco"}} Aquí tienes un código bancario realmente malo. ```{includeCode: true} -const accounts = { +const cuentas = { a: 100, b: 0, c: 20 }; -function getAccount() { - let accountName = prompt("Ingresa el nombre de una cuenta"); - if (!Object.hasOwn(accounts, accountName)) { - throw new Error(`No existe esa cuenta: ${accountName}`); +function obtenerCuenta() { + let nombreCuenta = prompt("Ingresa el nombre de una cuenta"); + if (!Object.hasOwn(cuentas, nombreCuenta)) { + throw new Error(`No existe esa cuenta: ${nombreCuenta}`); } - return accountName; + return nombreCuenta; } -function transfer(from, amount) { - if (accounts[from] < amount) return; - accounts[from] -= amount; - accounts[getAccount()] += amount; +function transferir(desde, cantidad) { + if (cuentas[desde] < cantidad) return; + cuentas[desde] -= cantidad; + cuentas[obtenerCuenta()] += cantidad; } ``` -La función `transfer` transfiere una suma de dinero desde una cuenta dada a otra, pidiendo el nombre de la otra cuenta en el proceso. Si se proporciona un nombre de cuenta inválido, `getAccount` lanza una excepción. +La función `transferir` transfiere una suma de dinero desde una cuenta dada a otra, pidiendo el nombre de la otra cuenta en el proceso. Si se proporciona un nombre de cuenta inválido, `obtenerCuenta` lanza una excepción. -Pero `transfer` _primero_ retira el dinero de la cuenta y _luego_ llama a `getAccount` antes de agregarlo a otra cuenta. Si se interrumpe por una excepción en ese momento, simplemente hará desaparecer el dinero. +Pero `transferir` _primero_ retira el dinero de la cuenta y _luego_ llama a `obtenerCuenta` antes de agregarlo a otra cuenta. Si se interrumpe por una excepción en ese momento, simplemente hará desaparecer el dinero. -Ese código podría haber sido escrito de manera un poco más inteligente, por ejemplo, llamando a `getAccount` antes de comenzar a mover el dinero. Pero a menudo los problemas como este ocurren de formas más sutiles. Incluso las funciones que no parecen que lanzarán una excepción podrían hacerlo en circunstancias excepcionales o cuando contienen un error del programador. +Ese código podría haber sido escrito de manera un poco más inteligente, por ejemplo, llamando a `obtenerCuenta` antes de comenzar a mover el dinero. Pero a menudo problemas como este ocurren de formas mucho más sutiles. Incluso funciones que aparentemente no lanzarían una excepción podrían hacerlo en circunstancias excepcionales o cuando contienen un error del programador. -Una manera de abordar esto es utilizar menos efectos secundarios. Nuevamente, un estilo de programación que calcule nuevos valores en lugar de cambiar datos existentes ayuda. Si un fragmento de código deja de ejecutarse en medio de la creación de un nuevo valor, no se dañaron estructuras de datos existentes, lo que facilita la recuperación. +Una manera de abordar este problema es utilizar menos efectos secundarios. De nuevo, un estilo de programación que calcule valores nuevos en lugar de cambiar datos existentes, ayuda. Si un fragmento de código deja de ejecutarse en medio de la creación de un nuevo valor, al menos no se dañan estructuras de datos existentes, lo que facilita la recuperación. {{index block, "palabra clave try", "palabra clave finally"}} -Pero eso no siempre es práctico. Por eso existe otra característica que tienen las instrucciones `try`. Pueden estar seguidas de un bloque `finally` en lugar o además de un bloque `catch`. Un bloque `finally` dice "sin importar _qué_ suceda, ejecuta este código después de intentar ejecutar el código en el bloque `try`." +Como eso no siempre es práctico, las instrucciones `try` tienen otra funcionalidad: pueden estar seguidas de un bloque `finally` en lugar o además de un bloque `catch`. Un bloque `finally` dice "sin importar _qué_ suceda, ejecuta este código después de intentar ejecutar el código en el bloque `try`." ```{includeCode: true} -function transfer(from, amount) { - if (accounts[from] < amount) return; - let progress = 0; +function transferir(desde, cantidad) { + if (cuentas[desde] < cantidad) return; + let progreso = 0; try { - accounts[from] -= amount; - progress = 1; - accounts[getAccount()] += amount; - progress = 2; + cuentas[desde] -= cantidad; + progreso = 1; + cuentas[obtenerCuenta()] += cantidad; + progreso = 2; } finally { - if (progress == 1) { - accounts[from] += amount; + if (progreso == 1) { + cuentas[desde] += cantidad; } } } ``` -Esta versión de la función rastrea su progreso y, si al salir nota que fue abortada en un punto donde había creado un estado del programa inconsistente, repara el daño causado. +Esta versión de la función rastrea su progreso y, si al salir se da cuenta de que pasó algo en un punto donde había creado un estado del programa inconsistente, repara el daño causado. Cabe destacar que aunque el código `finally` se ejecuta cuando se lanza una excepción en el bloque `try`, no interfiere con la excepción. Después de que se ejecuta el bloque `finally`, la pila continúa desenrollándose. {{index "excepción de seguridad"}} -Escribir programas que funcionen de manera confiable incluso cuando surgen excepciones en lugares inesperados es difícil. Muchas personas simplemente no se preocupan, y debido a que las excepciones suelen reservarse para circunstancias excepcionales, el problema puede ocurrir tan raramente que ni siquiera se note. Si eso es algo bueno o realmente malo depende de cuánto daño causará el software cuando falle. +Escribir programas que funcionen de manera fiable incluso cuando surgen excepciones en lugares inesperados es difícil. Mucha gente simplemente no se preocupa, y debido a que las excepciones suelen reservarse para circunstancias excepcionales, el problema puede ocurrir tan raramente que ni siquiera se note. Si eso es algo bueno o realmente malo depende de cuánto daño causará el software cuando falle. ## Captura selectiva @@ -402,7 +406,7 @@ Para errores de programación, a menudo dejar que el error siga su curso es lo m {{index "interfaz de usuario"}} -Para problemas que se _espera_ que ocurran durante el uso rutinario, fallar con una excepción no manejada es una estrategia terrible. +Para problemas que se _espera_ que puedan ocurrir de normal, fallar con una excepción no manejada es una muy mala estrategia. {{index ["función", "aplicación"], "manejo de excepciones", "tipo de error", [enlace, indefinido]}} @@ -414,16 +418,16 @@ Cuando se entra en un cuerpo `catch`, todo lo que sabemos es que _algo_ en nuest {{index "manejo de excepciones"}} -JavaScript (en una omisión bastante llamativa) no proporciona un soporte directo para capturar excepciones selectivamente: o las capturas todas o no capturas ninguna. Esto hace que sea tentador _asumir_ que la excepción que obtienes es la que tenías en mente cuando escribiste el bloque `catch`. +JavaScript (en una omisión bastante evidente) no proporciona un soporte directo para capturar excepciones selectivamente: o las capturas todas o no capturas ninguna. Esto hace que sea tentador _asumir_ que la excepción que obtienes es la que tenías en mente cuando escribiste el bloque `catch`. {{index "función promptDirection"}} -Pero podría no serlo. Alguno otra ((asunción)) podría estar violada, o podrías haber introducido un error que está causando una excepción. Aquí tienes un ejemplo que _intenta_ seguir llamando a `promptDirection` hasta obtener una respuesta válida: +Pero podría no serlo. Algún otro ((supuesto)) podría no cumplirse, o puede que hayas introducido un error que está causando una excepción. Aquí tienes un ejemplo que _intenta_ seguir llamando a `solicitarDirección` hasta obtener una respuesta válida: ```{test: no} for (;;) { try { - let dir = promptDirection("¿Dónde?"); // ← ¡Error de tipeo! + let dir = soliitarDirección("¿Dónde?"); // ← ¡Error de tipeo! console.log("Elegiste ", dir); break; } catch (e) { @@ -434,15 +438,15 @@ for (;;) { {{index "bucle infinito", "bucle for", "palabra clave catch", "depuración"}} -La construcción `for (;;)` es una forma de crear intencionalmente un bucle que no se termina por sí mismo. Salimos del bucle solo cuando se proporciona una dirección válida. _Pero_ escribimos mal `promptDirection`, lo que resultará en un error de "variable no definida". Debido a que el bloque `catch` ignora por completo el valor de la excepción (`e`), asumiendo que sabe cuál es el problema, trata erróneamente el error de enlace mal escrito como indicativo de una entrada incorrecta. Esto no solo causa un bucle infinito, sino que también "entorpece" el útil mensaje de error sobre el enlace mal escrito. +La construcción `for (;;)` es una forma de crear intencionalmente un bucle que no se termina por sí mismo. Salimos del bucle solo cuando se proporciona una dirección válida. _Pero_ escribimos mal `solicitarDirección`, lo que resultará en un error de "variable no definida". Debido a que el bloque `catch` ignora por completo el valor de la excepción (`e`), trata erróneamente el error de asociación mal escrita al asumir que sabe cuál es el problema, indicando entonces que el problema se debió a una entrada incorrecta. Esto no solo causa un bucle infinito, sino que también "entierra" el útil mensaje de error sobre el enlace mal escrito. -Como regla general, no captures excepciones de manera general a menos que sea con el propósito de "enviarlas" a algún lugar, por ejemplo, a través de la red para informar a otro sistema que nuestro programa se bloqueó. E incluso en ese caso, piensa cuidadosamente cómo podrías estar ocultando información. +Como regla general, no captures excepciones indiscriminadamente a menos que sea con el propósito de "enviarlas" a algún lugar, por ejemplo, a través de la red para informar a otro sistema de que nuestro programa se bloqueó. E incluso en ese caso, piensa cuidadosamente cómo podrías estar ocultando información. {{index "manejo de excepciones"}} -Por lo tanto, queremos capturar un tipo _específico_ de excepción. Podemos hacer esto verificando en el bloque `catch` si la excepción que recibimos es la que nos interesa y relanzándola en caso contrario. Pero, ¿cómo reconocemos una excepción? +Queremos capturar un tipo _específico_ de excepción. Podemos hacer esto verificando en el bloque `catch` si la excepción que recibimos es la que nos interesa y relanzándola en caso contrario. Pero, ¿cómo reconocemos una excepción? -Podríamos comparar su propiedad `message` con el mensaje que esperamos ((error)). Pero esta es una forma poco confiable de escribir código, estaríamos utilizando información diseñada para consumo humano (el mensaje) para tomar una decisión programática. Tan pronto como alguien cambie (o traduzca) el mensaje, el código dejará de funcionar. +Podríamos comparar su propiedad `message` con el mensaje de ((error)) que esperamos. Pero esta es una forma poco fiable de escribir código, estaríamos utilizando información diseñada para consumo humano (el mensaje) para tomar una decisión programática. Tan pronto como alguien cambie (o traduzca) el mensaje, el código dejará de funcionar. {{index "tipo de Error", "operador instanceof", "función promptDirection"}} @@ -451,17 +455,17 @@ En lugar de eso, definamos un nuevo tipo de error y usemos `instanceof` para ide ```{includeCode: true} class InputError extends Error {} -function promptDirection(question) { - let result = prompt(question); - if (result.toLowerCase() == "izquierda") return "I"; - if (result.toLowerCase() == "derecha") return "D"; - throw new InputError("Dirección no válida: " + result); +function solicitarDirección(pregunta) { + let resultado = prompt(pregunta); + if (resultado.toLowerCase() == "izquierda") return "I"; + if (resultado.toLowerCase() == "derecha") return "D"; + throw new InputError("Dirección no válida: " + resultado); } ``` {{index "palabra clave throw", herencia}} -La nueva clase de error extiende `Error`. No define su propio constructor, lo que significa que hereda el constructor de `Error`, que espera un mensaje de cadena como argumento. De hecho, no define nada en absoluto, la clase está vacía. Los objetos `InputError` se comportan como objetos `Error`, excepto que tienen una clase diferente mediante la cual podemos reconocerlos. +La nueva clase de error extiende la clase `Error`. No define su propio constructor, lo que significa que hereda el constructor de `Error`, que espera un mensaje de cadena como argumento. De hecho, no define nada en absoluto, la clase está vacía. Los objetos `InputError` se comportan como objetos `Error`, excepto que tienen una clase diferente mediante la cual podemos reconocerlos. {{index "manejo de excepciones"}} @@ -470,7 +474,7 @@ Ahora el bucle puede capturar esto con más cuidado. ```{test: no} for (;;) { try { - let dir = promptDirection("¿Dónde?"); + let dir = solicitarDirección("¿Dónde?"); console.log("Elegiste ", dir); break; } catch (e) { @@ -485,13 +489,13 @@ for (;;) { {{index "depuración"}} -Esto capturará solo instancias de `InputError` y permitirá que pasen excepciones no relacionadas. Si vuelves a introducir el error de tipeo, el error de enlace no definido se informará correctamente. +Esto capturará solo instancias de `InputError` y permitirá que cualquier excepción no relacionada pase sin más (solamente lanzando el error). Si vuelves a introducir el error de tipeo, el error de enlace no definido se informará correctamente. -## Afirmaciones +## Asertos {{index "función assert", "afirmación", "depuración"}} -Las _afirmaciones_ son verificaciones dentro de un programa que aseguran que algo es como se supone que debe ser. Se utilizan no para manejar situaciones que pueden surgir en la operación normal, sino para encontrar errores de programación. +Los _asertos_ son verificaciones dentro de un programa que aseguran que algo es como se supone que debe ser. Se utilizan no para manejar situaciones que pueden surgir con un uso normal del programa, sino para encontrar errores del programador. Si, por ejemplo, se describe `primerElemento` como una función que nunca debería ser llamada en arrays vacíos, podríamos escribirla de la siguiente manera: @@ -506,15 +510,15 @@ function primerElemento(array) { {{index "validación", "error en tiempo de ejecución", fallo, "suposición"}} -Ahora, en lugar de devolver silenciosamente `undefined` (que es lo que obtienes al leer una propiedad de un array que no existe), esto hará que tu programa falle ruidosamente tan pronto como lo uses incorrectamente. Esto hace que sea menos probable que tales errores pasen desapercibidos y más fácil encontrar su causa cuando ocurran. +Ahora, en lugar de devolver silenciosamente `undefined` (que es lo que obtienes al leer una propiedad de un array que no existe), esto hará que tu programa falle "ruidosamente" tan pronto como lo uses incorrectamente. Esto hace que sea menos probable que tales errores pasen desapercibidos y más fácil encontrar su causa cuando ocurran. -No recomiendo intentar escribir afirmaciones para cada tipo de entrada incorrecta posible. Eso sería mucho trabajo y llevaría a un código muy ruidoso. Querrás reservarlas para errores que son fáciles de cometer (o que te encuentres cometiendo). +No recomiendo intentar escribir afirmaciones para cada tipo de entrada incorrecta posible. Eso sería mucho trabajo y llevaría a un código muy ruidoso. Querrás reservarlas para errores que son fáciles de cometer (o que veas que estás cometiendo). ## Resumen -Una parte importante de programar es encontrar, diagnosticar y corregir errores. Los problemas pueden ser más fáciles de notar si tienes un conjunto de pruebas automatizadas o agregas afirmaciones a tus programas. +Una parte importante de programar es encontrar, diagnosticar y corregir errores. Los problemas pueden ser más fáciles de notar si tienes un conjunto de tests automatizados o agregas asertos a tus programas. -Los problemas causados por factores fuera del control del programa generalmente deberían ser planificados activamente. A veces, cuando el problema puede ser manejado localmente, los valores de retorno especiales son una buena forma de rastrearlos. De lo contrario, las excepciones pueden ser preferibles. +Los problemas causados por factores fuera del control del programa generalmente deberían ser planificados activamente. A veces, cuando el problema puede ser manejado localmente, los valores de retorno especiales son una buena forma de rastrearlos. De lo contrario, puede ser preferible usar excepciones. Lanzar una excepción provoca que la pila de llamadas se desenrolle hasta el próximo bloque `try/catch` envolvente o hasta la base de la pila. El valor de la excepción será entregado al bloque `catch` que la captura, el cual debe verificar que sea realmente el tipo de excepción esperado y luego hacer algo con él. Para ayudar a abordar el flujo de control impredecible causado por las excepciones, se pueden utilizar bloques `finally` para asegurar que un trozo de código se ejecute _siempre_ cuando un bloque termina. @@ -558,7 +562,7 @@ if}} La llamada a `primitiveMultiply` definitivamente debería ocurrir en un bloque `try`. El bloque `catch` correspondiente debería relanzar la excepción cuando no sea una instancia de `MultiplicatorUnitFailure` y asegurarse de que la llamada se reintente cuando lo sea. -Para hacer el reintentamiento, puedes usar un bucle que se detenga solo cuando una llamada tiene éxito, como en el ejemplo de [`look`](error#look) anterior en este capítulo, o usar la ((recursión)) y esperar que no tengas una cadena tan larga de fallos que colapse la pila (lo cual es bastante improbable). +Para hacer el reintento, puedes usar un bucle que se detenga solo cuando una llamada tiene éxito, como en el ejemplo de [`mirar`](error#look) anterior en este capítulo, o usar la ((recursión)) y esperar que no tengas una cadena tan larga de fallos que colapse la pila (lo cual es bastante improbable). hint}} @@ -586,7 +590,7 @@ Es una ((caja)) con una cerradura. Hay un array en la caja, pero solo puedes acc {{index "finally keyword", "exception handling"}} -Escribe una función llamada `withBoxUnlocked` que reciba como argumento un valor de función, desbloquee la caja, ejecute la función y luego asegure que la caja esté cerrada de nuevo antes de devolverla, independientemente de si la función de argumento devolvió normalmente o lanzó una excepción. +Escribe una función llamada `withBoxUnlocked` que reciba como argumento un valor de función, desbloquee la caja, ejecute la función y luego asegure que la caja esté cerrada de nuevo antes de devolverla, independientemente de si la función de argumento terminó con normalidad o lanzó una excepción. {{if interactive @@ -624,7 +628,7 @@ console.log(box.locked); if}} -Para puntos adicionales, asegúrate de que si llamas a `withBoxUnlocked` cuando la caja ya está desbloqueada, la caja permanezca desbloqueada. +Para más puntos, asegúrate de que si llamas a `withBoxUnlocked` cuando la caja ya está desbloqueada, la caja permanezca desbloqueada. {{hint diff --git a/html/08_error.html b/html/08_error.html index ee490def..f5b80f63 100644 --- a/html/08_error.html +++ b/html/08_error.html @@ -14,71 +14,73 @@

Bugs y Errores

-

Depurar es el doble de difícil que escribir el código en primer lugar. Por lo tanto, si escribes el código lo más ingeniosamente posible, por definición, no eres lo suficientemente inteligente como para depurarlo.

+

Depurar es el doble de difícil que escribir el código por primera vez. Por lo tanto, si escribes el código de la manera más inteligente posible, no eres, por definición, lo suficientemente inteligente como para depurarlo.

Brian Kernighan and P.J. Plauger, The Elements of Programming Style
Ilustración mostrando varios insectos y un ciempiés
-

Las fallas en los programas de computadora generalmente se llaman bugs. Hace que los programadores se sientan bien imaginarlos como pequeñas cosas que simplemente se meten en nuestro trabajo. En realidad, por supuesto, nosotros mismos los colocamos allí.

+

Los errores en los programas de computadora generalmente se llaman bugs. Los programadores nos sentimos mejor imaginándolos como pequeñas cosas que simplemente se meten en nuestro trabajo. Por supuesto, en realidad, somos nosotros mismos quienes los colocamos allí.

-

Si un programa es pensamiento cristalizado, puedes clasificar aproximadamente los errores en aquellos causados por pensamientos confusos y aquellos causados por errores introducidos al convertir un pensamiento en código. El primer tipo generalmente es más difícil de diagnosticar y arreglar que el último.

+

Si entendemos un programa como pensamiento cristalizado, podemos clasificar los errores más o menos en aquellos causados por pensamientos confusos y aquellos causados por errores introducidos al convertir un pensamiento en código. El primer tipo generalmente es más difícil de diagnosticar y arreglar que el último.º

Lenguaje

-

Muchos errores podrían ser señalados automáticamente por la computadora, si supiera lo suficiente sobre lo que estamos intentando hacer. Pero la laxitud de JavaScript es un obstáculo aquí. Su concepto de enlaces y propiedades es lo suficientemente vago como para rara vez atrapar typos antes de ejecutar realmente el programa. E incluso entonces, te permite hacer algunas cosas claramente absurdas sin quejarse, como calcular true * "monkey".

+

Muchos errores podrían ser señalados automáticamente por la computadora si esta supiera lo suficiente sobre lo que estamos intentando hacer. Pero la laxitud de JavaScript es un obstáculo aquí. Su concepto de asociaciones y propiedades es lo suficientemente vago como para rara vez atrapar erratas antes de ejecutar realmente el programa. E incluso entonces, todavía te permite hacer algunas cosas claramente absurdas sin quejarse, como calcular true * "monkey".

-

Hay algunas cosas sobre las que JavaScript sí se queja. Escribir un programa que no siga la gramática del lenguaje hará que la computadora se queje de inmediato. Otras cosas, como llamar a algo que no es una función o buscar una propiedad en un valor undefined harán que se reporte un error cuando el programa intente realizar la acción.

+

Hay algunas cosas sobre las que JavaScript sí que se queja. Escribir un programa que no siga la gramática del lenguaje hará que la computadora se queje de inmediato. Otras cosas, como llamar a algo que no es una función o buscar una propiedad en un valor undefined harán que se reporte un error cuando el programa intente realizar la acción.

-

Pero a menudo, tu cálculo absurdo simplemente producirá NaN (no es un número) o un valor indefinido, mientras que el programa continúa felizmente, convencido de que está haciendo algo significativo. El error se manifestará solo más tarde, después de que el valor falso haya pasado por varias funciones. Es posible que no desencadene un error en absoluto, pero silenciosamente cause que la salida del programa sea incorrecta. Encontrar la fuente de tales problemas puede ser difícil.

+

Pero a menudo, tu cálculo absurdo simplemente producirá NaN (not a number) o un valor indefinido, mientras que el programa continúa alegremente, convencido de que está haciendo algo con sentido. El error solo se pondrá de manifiesto más adelante, después de que el valor falso haya pasado ya por varias funciones. Es posible que no desencadene ningún error, sino que silenciosamente cause que la salida del programa sea incorrecta. Encontrar la fuente de tales problemas puede ser difícil.

-

El proceso de encontrar errores—bugs—en los programas se llama depuración.

+

El proceso de encontrar errores —bugs— en los programas se llama depuración (en inglés, debugging).

Modo estricto

-

JavaScript puede ser un poco más estricto al habilitar el modo estricto. Esto se hace colocando la cadena "use strict" en la parte superior de un archivo o en el cuerpo de una función. Aquí tienes un ejemplo:

+

Se puede hacer que JavaScript sea un poco más estricto al habilitar el modo estricto. Esto se hace colocando la cadena "use strict" en la parte superior de un archivo o en el cuerpo de una función. Aquí tienes un ejemplo:

-
function canYouSpotTheProblem() {
+
function puedesEncontrarElProblema() {
   "use strict";
-  for (counter = 0; counter < 10; counter++) {
+  for (contador = 0; contador < 10; contador++) {
     console.log("Happy happy");
   }
 }
 
-canYouSpotTheProblem();
-// → ReferenceError: counter is not defined
+puedesEncontrarElProblema(); +// → ReferenceError: contador is not defined
-

Normalmente, cuando olvidas poner let frente a tu enlace, como en el caso de counter en el ejemplo, JavaScript silenciosamente crea un enlace global y lo utiliza. En modo estricto, se reporta un error en su lugar. Esto es muy útil. Sin embargo, cabe mencionar que esto no funciona cuando el enlace en cuestión ya existe en algún lugar del ámbito. En ese caso, el bucle seguirá sobrescribiendo silenciosamente el valor del enlace.

+

El código de dentro de una clase o un módulo (que veremos en el Capítulo 10) se considera automáticamente en modo estricto. Se sigue manteniendo el comportamiento no estricto solo porque hay algo de código antiguo que podría quizá depender de él. De esta manera, los diseñadores del lenguaje evitan romper programas existentes.

-

Otro cambio en el modo estricto es que el enlace this mantiene el valor undefined en funciones que no son llamadas como métodos. Al hacer una llamada de este tipo fuera del modo estricto, this se refiere al objeto de ámbito global, que es un objeto cuyas propiedades son los enlaces globales. Entonces, si accidentalmente llamas incorrectamente a un método o constructor en modo estricto, JavaScript producirá un error tan pronto como intente leer algo de this, en lugar de escribir felizmente en el ámbito global.

+

Normalmente, cuando olvidas poner let frente a tu asociación, como en el caso de counter en el ejemplo, JavaScript silenciosamente crea un enlace global y lo utiliza. En modo,estricto, sin embargo, se reporta un error. Esto es muy útil. No obstante, cabe mencionar que no aparecerá ningún mensaje de error cuando la asociación en cuestión ya existe en alguna parte del ámbito. En ese caso, el bucle igualmente sobrescribirá silenciosamente el valor de la asociación y seguirá con su tarea.

-

Por ejemplo, considera el siguiente código, que llama a una función constructor sin la palabra clave new para que su this no se refiera a un objeto recién construido:

+

Otro cambio en el modo estricto es que el enlace this tiene el valor undefined en funciones que no son llamadas como métodos. Al hacer una llamada de este tipo fuera del modo estricto, this se refiere al objeto del ámbito global, que es un objeto cuyas propiedades son los enlaces globales. Así que, si llamas incorrectamente a un método o constructor por error en modo estricto, JavaScript producirá un error tan pronto como intente leer algo de this, en lugar de escribir en el ámbito global.

-
function Person(name) { this.name = name; }
-let ferdinand = Person("Ferdinand"); // oops
-console.log(name);
+

Considera, por ejemplo, el siguiente código, que llama a una función constructor sin la palabra clave new para que su this no se refiera a un objeto recién construido:

+ +
function Persona(nombre) { this.nombre = nombre; }
+let ferdinand = Persona("Ferdinand"); // oops
+console.log(nombre);
 // → Ferdinand
-

Entonces, la llamada falsa a Person tuvo éxito pero devolvió un valor no definido y creó el enlace global name. En modo estricto, el resultado es diferente.

+

La llamada errónea a Persona ha tenido éxito pero ha devuelto un valor no definido y ha creado el enlace global name. En modo estricto, el resultado es diferente.

-
"use strict";
-function Person(name) { this.name = name; }
-let ferdinand = Person("Ferdinand"); // olvidó el new
-// → TypeError: Cannot set property 'name' of undefined
+
"use strict";
+function Persona(nombre) { this.nombre = nombre; }
+let ferdinand = Persona("Ferdinand"); // falta el new
+// → TypeError: Cannot set property 'nombre' of undefined
-

Inmediatamente se nos informa que algo está mal. Esto es útil.

+

Inmediatamente se nos informa de que algo falla. Esto es útil.

-

Afortunadamente, los constructores creados con la notación class siempre mostrarán una queja si se llaman sin new, lo que hace que esto sea menos problemático incluso en modo no estricto.

+

Por suerte, los constructores creados con la notación class siempre se van a quejar si se llaman sin new, conque esto no será tanto problema incluso en modo no estricto.

-

El modo estricto hace algunas cosas más. Prohíbe darle a una función múltiples parámetros con el mismo nombre y elimina ciertas características problemáticas del lenguaje por completo (como la declaración with, que es tan incorrecta que no se discute más en este libro).

+

El modo estricto hace algunas cosas más. Prohíbe darle a una función múltiples parámetros con el mismo nombre y elimina ciertas características problemáticas del lenguaje por completo (como la declaración with, que es tan incorrecta que ni se va a discutir más en este libro).

-

En resumen, colocar "use strict" al principio de tu programa rara vez duele y podría ayudarte a identificar un problema.

+

En resumen, colocar "use strict" al principio de tu programa rara vez hace daño y podría ayudarte a identificar un problema.

Tipos

-

Algunos lenguajes quieren saber los tipos de todos tus enlaces y expresiones antes de ejecutar un programa. Te indicarán de inmediato cuando un tipo se utiliza de manera inconsistente. JavaScript considera los tipos solo cuando realmente se ejecuta el programa, e incluso allí a menudo intenta convertir valores implícitamente al tipo que espera, por lo que no es de mucha ayuda.

+

Algunos lenguajes quieren saber los tipos de todas tus asociaciones y expresiones antes de ejecutar un programa. Te indicarán de inmediato cuando un tipo se utiliza de manera inconsistente. JavaScript considera los tipos solo cuando realmente se ejecuta el programa, e incluso allí a menudo intenta convertir valores implícitamente al tipo que espera, por lo que no es de mucha ayuda.

-

No obstante, los tipos proporcionan un marco útil para hablar sobre programas. Muchos errores provienen de estar confundido acerca del tipo de valor que entra o sale de una función. Si tienes esa información escrita, es menos probable que te confundas.Podrías agregar un comentario como el siguiente antes de la función findRoute del capítulo anterior para describir su tipo:

+

Aun así, los tipos proporcionan un marco útil para hablar sobre programas. Muchos errores surgen de la confusión acerca del tipo de valor que entra o sale de una función. Si tienes esa información escrita, es menos probable que te confundas. Podrías agregar un comentario como el siguiente antes de la función findRoute del capítulo anterior para describir su tipo:

// (graph: Object, from: string, to: string) => string[]
 function findRoute(graph, from, to) {
@@ -87,70 +89,70 @@ 

Existen varias convenciones diferentes para anotar programas de JavaScript con tipos.

-

Una cosa sobre los tipos es que necesitan introducir su propia complejidad para poder describir suficiente código para ser útiles. ¿Qué tipo crees que tendría la función randomPick que devuelve un elemento aleatorio de un array? Necesitarías introducir una variable de tipo, T, que pueda representar cualquier tipo, para que puedas darle a randomPick un tipo como (T[]) → T (función de un array de T a un T).

+

Una cosa sobre los tipos es que necesitan introducir su propia complejidad para ser capaces de describir el suficiente código como para ser útiles. ¿Qué tipo crees que tendría la función randomPick que devuelve un elemento aleatorio de un array? Necesitarías introducir una variable de tipo, T, que pueda representar cualquier tipo, para que puedas darle a randomPick un tipo como (T[]) → T (función de un array de T a un T).

Cuando los tipos de un programa son conocidos, es posible que la computadora los verifique por ti, señalando errores antes de que se ejecute el programa. Hay varios dialectos de JavaScript que añaden tipos al lenguaje y los verifican. El más popular se llama TypeScript. Si estás interesado en agregar más rigor a tus programas, te recomiendo que lo pruebes.

En este libro, continuaremos utilizando código JavaScript crudo, peligroso y sin tipos.

-

Pruebas

+

Testing

-

Si el lenguaje no nos va a ayudar mucho a encontrar errores, tendremos que encontrarlos a la antigua: ejecutando el programa y viendo si hace lo correcto.

+

Si el lenguaje no nos va a ayudar mucho a encontrar errores, habrá que encontrarlos por las malas: ejecutando el programa y viendo si hace lo correcto.

-

Hacer esto manualmente, una y otra vez, es una idea muy mala. No solo es molesto, también tiende a ser ineficaz, ya que lleva demasiado tiempo probar exhaustivamente todo cada vez que haces un cambio.

+

Hacer esto manualmente, una y otra vez, es una idea muy mala. No solo es una lata, sino que tiende a ser ineficaz, ya que lleva demasiado tiempo probar exhaustivamente todo cada vez que haces un cambio.

-

Las computadoras son buenas en tareas repetitivas, y las pruebas son la tarea repetitiva ideal. Las pruebas automatizadas son el proceso de escribir un programa que prueba otro programa. Es un poco más trabajo escribir pruebas que probar manualmente, pero una vez que lo has hecho, adquieres una especie de superpoder: solo te llevará unos segundos verificar que tu programa siga comportándose correctamente en todas las situaciones para las que escribiste pruebas. Cuando rompes algo, lo notarás de inmediato en lugar de encontrártelo al azar en algún momento posterior.

+

Las computadoras son buenas en tareas repetitivas, y las pruebas (o el testing) son la tarea repetitiva ideal. Los tests automatizados son el proceso de escribir un programa que testea otro programa. Lleva algo más de trabajo escribir tests que hacer las pruebas a mano, pero una vez que lo has hecho, adquieres una especie de superpoder: solo te llevará unos segundos verificar que tu programa sigue comportándose correctamente en todas las situaciones para las que escribiste tus tests. Cuando rompes algo, lo notarás de inmediato en lugar de encontrártelo de casualidad más adelante.

-

Las pruebas suelen tomar la forma de pequeños programas etiquetados que verifican algún aspecto de tu código. Por ejemplo, un conjunto de pruebas para el (probablemente ya probado por alguien más) método toUpperCase estándar podría lucir así:

+

Los tests suelen ser pequeños programas etiquetados que verifican algún aspecto de tu código. Por ejemplo, un conjunto de tests para el (probablemente ya probado por alguien más) método toUpperCase estándar podría tener esta pinta:

-
function test(label, body) {
-  if (!body()) console.log(`Fallo: ${label}`);
+
function test(etiqueta, cuerpo) {
+  if (!cuerpo()) console.log(`Fallo: ${etiqueta}`);
 }
 
 test("convertir texto latino a mayúsculas", () => {
-  return "hello".toUpperCase() == "HELLO";
+  return "hola".toUpperCase() == "HOLA";
 });
 test("convertir texto griego a mayúsculas", () => {
   return "Χαίρετε".toUpperCase() == "ΧΑΊΡΕΤΕ";
 });
-test("no convertir caracteres sin caso", () => {
+test("no convertir caracteres sin mayúsculas", () => {
   return "مرحبا".toUpperCase() == "مرحبا";
 });
-

Escribir pruebas de esta forma tiende a producir código bastante repetitivo y torpe. Afortunadamente, existen software que te ayudan a construir y ejecutar colecciones de pruebas (suites de pruebas) al proporcionar un lenguaje (en forma de funciones y métodos) adecuado para expresar pruebas y al producir información informativa cuando una prueba falla. Estos suelen llamarse corredores de pruebas.

+

Escribir tests de esta manera tiende a generar código repetitivo y poco elegante. Por suerte, hay software que te ayuda a construir y ejecutar colecciones de tests (test suites) al proporcionar un lenguaje (en forma de funciones y métodos) adecuado para expresar tests y producir información descriptiva cuando un test falla. Estas herramientas suelen llamarse test runners.

-

Alguno código es más fácil de probar que otro código. Generalmente, cuantos más objetos externos interactúan con el código, más difícil es configurar el contexto para probarlo. El estilo de programación mostrado en el capítulo anterior, que utiliza valores persistentes autocontenidos en lugar de objetos cambiantes, tiende a ser fácil de probar.

+

Hay códigos más fáciles de testar que otros. Generalmente, cuantos más objetos externos interactúan con el código, más difícil es configurar el contexto para testearlo. El estilo de programación que vimos en el capítulo anterior, que utiliza valores persistentes autocontenidos en lugar de objetos cambiantes, suele ser fácil de probar.

Depuración

-

Una vez que notas que hay algo mal en tu programa porque se comporta de manera incorrecta o produce errores, el siguiente paso es descubrir cuál es el problema.

+

Una vez que notas que hay algo mal en tu programa porque no se comporta como debe o produce errores, el siguiente paso es descubrir cuál es el problema.

A veces es obvio. El mensaje de error señalará una línea específica de tu programa, y si miras la descripción del error y esa línea de código, a menudo puedes ver el problema.

Pero no siempre. A veces la línea que desencadenó el problema es simplemente el primer lugar donde se utiliza de manera incorrecta un valor defectuoso producido en otro lugar. Si has estado resolviendo los ejercicios en capítulos anteriores, probablemente ya hayas experimentado estas situaciones.

-

El siguiente programa de ejemplo intenta convertir un número entero en una cadena en una base dada (decimal, binaria, y así sucesivamente) al seleccionar repetidamente el último dígito y luego dividir el número para deshacerse de este dígito. Pero la extraña salida que produce actualmente sugiere que tiene un error.

+

El siguiente programa de ejemplo intenta convertir un número entero en una cadena en una base dada (decimal, binaria, etc.) al seleccionar consecutivamente el último dígito y luego dividir el número para deshacerse de este dígito. Pero la extraña salida que produce actualmente sugiere que tiene un error.

-
function numberToString(n, base = 10) {
-  let result = "", sign = "";
+
function númeroACadena(n, base = 10) {
+  let resultado = "", signo = "";
   if (n < 0) {
-    sign = "-";
+    signo = "-";
     n = -n;
   }
   do {
-    result = String(n % base) + result;
+    resultado = String(n % base) + resultado;
     n /= base;
   } while (n > 0);
-  return sign + result;
+  return signo + resultado;
 }
-console.log(numberToString(13, 10));
+console.log(númeroACadena(13, 10));
 // → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…

Incluso si ya ves el problema, finge por un momento que no lo haces. Sabemos que nuestro programa no funciona correctamente, y queremos descubrir por qué.

-

Aquí es donde debes resistir la tentación de empezar a hacer cambios aleatorios en el código para ver si eso lo mejora. En cambio, piensa. Analiza lo que está sucediendo y elabora una teoría sobre por qué podría estar ocurriendo. Luego, realiza observaciones adicionales para probar esta teoría, o si aún no tienes una teoría, realiza observaciones adicionales para ayudarte a crear una.

+

Aquí es donde debes resistir la tentación de empezar a hacer cambios aleatorios en el código para ver si así mejora. En vez de eso, piensa. Analiza lo que está sucediendo y elabora una teoría sobre por qué podría estar ocurriendo. Luego, realiza observaciones adicionales para probar esta teoría, o, si aún no tienes una teoría, realiza observaciones adicionales para ayudarte a crear una.

-

Colocar algunas llamadas console.log estratégicas en el programa es una buena manera de obtener información adicional sobre lo que está haciendo el programa. En este caso, queremos que n tome los valores 13, 1 y luego 0. Vamos a escribir su valor al inicio del ciclo.

+

Colocar algunas llamadas a console.log estratégicamente en el programa es una buena manera de obtener información adicional sobre lo que este está haciendo. En este caso, queremos que n tome los valores 13, 1 y luego 0. Vamos a escribir su valor al inicio del bucle.

13
 1.3
@@ -159,139 +161,141 @@ 

-

Correcto. Al dividir 13 por 10 no se produce un número entero. En lugar de n /= base, lo que realmente queremos es n = Math.floor(n / base) para que el número se “desplace” correctamente hacia la derecha.

+

Correcto. Al dividir 13 por 10 no se produce un número entero. En lugar de n /= base, lo que realmente queremos es n = Math.floor(n / base) de manera que pasamos correctamente a calcular el siguiente dígito.

+ +

Una alternativa a usar console.log para observar el comportamiento del programa es utilizar las capacidades del depurador de tu navegador. Los navegadores vienen con la capacidad de establecer un punto de interrupción en una línea específica de tu código. Cuando la ejecución del programa llega a una línea con un punto de interrupción, esta se pausa y puedes inspeccionar los valores de las asignaciones o variables en ese punto. No entraré en detalles, ya que los depuradores difieren de un navegador a otro, pero busca en las herramientas de desarrollo de tu navegador o busca instrucciones en la web.

-

Una alternativa a usar console.log para observar el comportamiento del programa es utilizar las capacidades del depurador de tu navegador. Los navegadores vienen con la capacidad de establecer un punto de interrupción en una línea específica de tu código. Cuando la ejecución del programa llega a una línea con un punto de interrupción, se pausa y puedes inspeccionar los valores de las asignaciones en ese punto. No entraré en detalles, ya que los depuradores difieren de un navegador a otro, pero busca en las herramientas de desarrollo de tu navegador o busca instrucciones en la Web.Otra forma de establecer un punto de interrupción es incluir una instrucción debugger (consistente únicamente en esa palabra clave) en tu programa. Si las herramientas de desarrollo de tu navegador están activas, el programa se pausará cada vez que alcance dicha instrucción.

+

Otra forma de establecer un punto de interrupción es incluir una instrucción debugger (consistente únicamente en esa palabra clave) en tu programa. Si las herramientas de desarrollo de tu navegador están activas, el programa se pausará cada vez que alcance dicha instrucción.

Propagación de errores

-

Lamentablemente, no todos los problemas pueden ser prevenidos por el programador. Si tu programa se comunica de alguna manera con el mundo exterior, es posible recibir entradas malformadas, sobrecargarse de trabajo o que falle la red.

+

Lamentablemente, el programador no puede evitar todos los problemas. Si tu programa se comunica de alguna manera con el mundo exterior, es posible recibir entradas con el formato incorrecto, sobrecargarse de trabajo o que falle la red.

-

Si estás programando solo para ti, puedes permitirte simplemente ignorar esos problemas hasta que ocurran. Pero si estás construyendo algo que será utilizado por alguien más, generalmente quieres que el programa haga algo más que simplemente colapsar. A veces lo correcto es aceptar la entrada incorrecta y continuar ejecutándose. En otros casos, es mejor informar al usuario sobre lo que salió mal y luego rendirse. Pero en cualquier situación, el programa debe hacer algo activamente en respuesta al problema.

+

Si estás programando solo para ti, puedes permitirte simplemente ignorar esos problemas hasta que ocurran. Pero si estás construyendo algo que será utilizado por alguien más, generalmente quieres que el programa haga algo más que simplemente colapsar. A veces lo correcto es aceptar la entrada errónea y continuar ejecutándose. En otros casos, lo mejor es informar al usuario sobre lo que salió mal y luego rendirse. Pero, en cualquier caso, el programa debe hacer algo activamente en respuesta al problema.

-

Imaginemos que tienes una función promptNumber que solicita al usuario un número y lo retorna. ¿Qué debería retornar si el usuario ingresa “naranja”?

+

Imaginemos que tienes una función solicitarNúmero que solicita al usuario un número y lo devuelve. ¿Qué debería devolver si el usuario dice “naranja”?

-

Una opción es hacer que retorne un valor especial. Las opciones comunes para tales valores son null, undefined o -1.

+

Una opción es hacer que devuelva un valor especial. Algunas opciones comunes para tales valores son null, undefined o -1.

-
function promptNumber(pregunta) {
+
function solicitarNúmero(pregunta) {
   let resultado = Number(prompt(pregunta));
   if (Number.isNaN(resultado)) return null;
   else return resultado;
 }
 
-console.log(promptNumber("¿Cuántos árboles ves?"));
+console.log(solicitarNúmero("¿Cuántos árboles ves?"));
-

Ahora, cualquier código que llame a promptNumber debe verificar si se leyó un número real y, de no ser así, debe recuperarse de alguna manera, quizás volviendo a preguntar o completando con un valor predeterminado. O podría retornar nuevamente un valor especial a su llamante para indicar que no pudo hacer lo que se le pidió.

+

Ahora, cualquier código que llame a solicitarNúmero debe verificar si de verdad se leyó un número y, de no ser así, debe recuperarse de alguna manera, quizás volviendo a preguntar o completando con un valor predeterminado. O podría devolver nuevamente un valor especial a quién la llamó para indicar que no pudo hacer lo que se le pidió.

-

En muchas situaciones, sobre todo cuando los errores son comunes y el llamante debería tomarlos explícitamente en cuenta, retornar un valor especial es una buena manera de indicar un error. Sin embargo, tiene sus inconvenientes. Primero, ¿qué pasa si la función ya puede devolver todos los tipos posibles de valores? En tal función, tendrás que hacer algo como envolver el resultado en un objeto para poder distinguir el éxito del fracaso, de la misma manera que lo hace el método next en la interfaz del iterador.

+

En muchas situaciones, sobre todo cuando los errores son comunes y el llamante debería tomarlos explícitamente en cuenta, devolver un valor especial es una buena manera de indicar un error. Sin embargo, tiene sus inconvenientes. Primero, ¿qué pasa si la función ya puede devolver todos los tipos posibles de valores? En tal función, tendrás que hacer algo como envolver el resultado en un objeto para poder distinguir el éxito del fracaso, de la misma manera que lo hace el método next en la interfaz del iterador.

-
function lastElement(arreglo) {
-  if (arreglo.length == 0) {
+
function últimoElemento(array) {
+  if (array.length == 0) {
     return {falló: true};
   } else {
-    return {valor: arreglo[arreglo.length - 1]};
+    return {valor: array[array.length - 1]};
   }
 }
-

El segundo problema con retornar valores especiales es que puede llevar a un código incómodo. Si un fragmento de código llama a promptNumber 10 veces, tendrá que verificar 10 veces si se devolvió null. Y si su respuesta al encontrar null es simplemente devolver null en sí mismo, los llamantes de la función a su vez tendrán que comprobarlo, y así sucesivamente.

+

El segundo problema con devolver valores especiales es que puede hacer que el código sea incómodo de manejar. Si un fragmento de código llama a solicitarNúmero 10 veces, tendrá que verificar 10 veces si se devolvió null. Y si su respuesta al encontrar null es simplemente devolver null en sí mismo, los que llamen a la función a su vez tendrán que comprobarlo, y así sucesivamente.

Excepciones

Cuando una función no puede proceder normalmente, lo que a menudo queremos hacer es simplemente detener lo que estamos haciendo e ir directamente a un lugar que sepa cómo manejar el problema. Esto es lo que hace el manejo de excepciones.

-

Las excepciones son un mecanismo que hace posible que el código que se encuentra con un problema lanze (o emita) una excepción. Una excepción puede ser cualquier valor. Lanzar una se asemeja de alguna manera a un retorno super potenciado de una función: sale no solo de la función actual sino también de sus llamadores, hasta llegar a la primera llamada que inició la ejecución actual. Esto se llama desenrollar la pila. Puede recordar la pila de llamadas a funciones que se mencionó en el Capítulo 3. Una excepción recorre esta pila, descartando todos los contextos de llamada que encuentra.

+

Las excepciones son un mecanismo que hace posible que el código que se encuentra con un problema lance (o emita) una excepción. Una excepción puede ser cualquier valor. Lanzar una es de alguna manera como un retorno de función supervitaminado: no solo se sale fuera de la función actual sino también de sus llamadores, hasta llegar a la primera llamada que inició la ejecución actual. Esto se llama desenrollar la pila. Recordarás la pila de llamadas a funciones que se mencionó en el Capítulo 3. Una excepción recorre esta pila, descartando todos los contextos de llamada que encuentra.

-

Si las excepciones siempre fueran directamente hasta el final de la pila, no serían de mucha utilidad. Simplemente proporcionarían una forma novedosa de hacer que su programa falle. Su poder radica en el hecho de que puede colocar “obstáculos” a lo largo de la pila para capturar la excepción mientras viaja hacia abajo. Una vez que ha capturado una excepción, puede hacer algo con ella para resolver el problema y luego continuar ejecutando el programa.

+

Si las excepciones siempre fueran directamente hasta el final de la pila, no serían de mucha utilidad. Simplemente serían una forma alternativa de hacer que tu programa falle. Su poder radica en el hecho de que puede colocar “obstáculos” a lo largo de la pila para capturar la excepción mientras viaja hacia afuera. Una vez que ha capturado una excepción, puede hacer algo con ella para resolver el problema y luego continuar ejecutando el programa.

Aquí tienes un ejemplo:

-
function promptDirection(question) {
-  let result = prompt(question);
-  if (result.toLowerCase() == "left") return "L";
-  if (result.toLowerCase() == "right") return "R";
-  throw new Error("Dirección inválida: " + result);
+
function solicitarDirección(pregunta) {
+  let resultado = prompt(pregunta);
+  if (resultado.toLowerCase() == "izquierda") return "L";
+  if (resultado.toLowerCase() == "derecha") return "R";
+  throw new Error("Dirección inválida: " + resultado);
 }
 
-function look() {
-  if (promptDirection("¿Hacia dónde?") == "L") {
+function mirar() {
+  if (solicitarDirección("¿Hacia dónde?") == "L") {
     return "una casa";
   } else {
-    return "dos osos enojados";
+    return "dos osos enfadados";
   }
 }
 
 try {
-  console.log("Ves", look());
+  console.log("Ves", mirar());
 } catch (error) {
   console.log("Algo salió mal: " + error);
 }
-

La palabra clave throw se utiliza para lanzar una excepción. La captura de una excepción se realiza envolviendo un trozo de código en un bloque try, seguido de la palabra clave catch. Cuando el código en el bloque try provoca que se lance una excepción, se evalúa el bloque catch, con el nombre entre paréntesis vinculado al valor de la excepción. Después de que el bloque catch finalice, o si el bloque try finaliza sin problemas, el programa continúa debajo de toda la instrucción try/catch.

+

La palabra clave throw se utiliza para lanzar una excepción. La captura de una excepción se realiza envolviendo un trozo de código en un bloque try, seguido de la palabra clave catch. Cuando el código en el bloque try provoca que se lance una excepción, se evalúa el bloque catch, con el nombre entre paréntesis vinculado al valor de la excepción. Cuando el bloque catch acabe, o cuando el bloque try finalice sin problemas, el programa continúa debajo de toda la instrucción try/catch.

-

En este caso, utilizamos el constructor Error para crear nuestro valor de excepción. Este es un constructor de JavaScript estándar que crea un objeto con una propiedad message. Las instancias de Error también recopilan información sobre la pila de llamadas que existía cuando se creó la excepción, una llamada traza de pila. Esta información se almacena en la propiedad stack y puede ser útil al intentar depurar un problema: nos indica la función donde ocurrió el problema y qué funciones realizaron la llamada fallida.

+

En este caso, utilizamos el constructor Error para crear nuestro valor de excepción. Este es un constructor de JavaScript estándar que crea un objeto con una propiedad message. Las instancias de Error también recopilan información sobre la pila de llamadas que existía cuando se creó la excepción, lo que se conoce como una traza de pila. Esta información se almacena en la propiedad stack y puede ser útil al intentar depurar un problema: nos indica la función donde ocurrió el problema y qué funciones realizaron la llamada fallida.

-

Ten en cuenta que la función look ignora por completo la posibilidad de que promptDirection pueda fallar. Esta es la gran ventaja de las excepciones: el código de manejo de errores solo es necesario en el punto donde ocurre el error y en el punto donde se maneja. Las funciones intermedias pueden olvidarse por completo de ello.

+

Ten en cuenta que la función mirar ignora por completo la posibilidad de que solicitarDirección pueda fallar. Esta es la gran ventaja de las excepciones: el código de manejo de errores solo es necesario en el punto donde ocurre el error y en el punto donde se maneja. Las funciones intermedias pueden olvidarse por completo de ello.

Bueno, casi...

Limpiando después de excepciones

-

El efecto de una excepción es otro tipo de flujo de control. Cada acción que pueda causar una excepción, que es prácticamente cada llamada a función y acceso a propiedad, puede hacer que el control salga repentinamente de tu código.

+

El resultado de una excepción es otro tipo de flujo de control. Cada acción que pueda causar una excepción, que es prácticamente cualquier llamada a función y acceso a propiedad, puede hacer que el control salga repentinamente de tu código.

-

Esto significa que cuando el código tiene varios efectos secundarios, incluso si su flujo de control “regular” parece que siempre ocurrirán todos, una excepción podría evitar que algunos de ellos sucedan.

+

Esto significa que, cuando el código tiene varios efectos secundarios, una excepción podría impedir que algunos de ellos ocurran, incluso si en el flujo de control “normal” parece que siempre deberían ejecutarse todos.

Aquí tienes un código bancario realmente malo.

-
const accounts = {
+
const cuentas = {
   a: 100,
   b: 0,
   c: 20
 };
 
-function getAccount() {
-  let accountName = prompt("Ingresa el nombre de una cuenta");
-  if (!Object.hasOwn(accounts, accountName)) {
-    throw new Error(`No existe esa cuenta: ${accountName}`);
+function obtenerCuenta() {
+  let nombreCuenta = prompt("Ingresa el nombre de una cuenta");
+  if (!Object.hasOwn(cuentas, nombreCuenta)) {
+    throw new Error(`No existe esa cuenta: ${nombreCuenta}`);
   }
-  return accountName;
+  return nombreCuenta;
 }
 
-function transfer(from, amount) {
-  if (accounts[from] < amount) return;
-  accounts[from] -= amount;
-  accounts[getAccount()] += amount;
+function transferir(desde, cantidad) {
+  if (cuentas[desde] < cantidad) return;
+  cuentas[desde] -= cantidad;
+  cuentas[obtenerCuenta()] += cantidad;
 }
-

La función transfer transfiere una suma de dinero desde una cuenta dada a otra, pidiendo el nombre de la otra cuenta en el proceso. Si se proporciona un nombre de cuenta inválido, getAccount lanza una excepción.

+

La función transferir transfiere una suma de dinero desde una cuenta dada a otra, pidiendo el nombre de la otra cuenta en el proceso. Si se proporciona un nombre de cuenta inválido, obtenerCuenta lanza una excepción.

-

Pero transfer primero retira el dinero de la cuenta y luego llama a getAccount antes de agregarlo a otra cuenta. Si se interrumpe por una excepción en ese momento, simplemente hará desaparecer el dinero.

+

Pero transferir primero retira el dinero de la cuenta y luego llama a obtenerCuenta antes de agregarlo a otra cuenta. Si se interrumpe por una excepción en ese momento, simplemente hará desaparecer el dinero.

-

Ese código podría haber sido escrito de manera un poco más inteligente, por ejemplo, llamando a getAccount antes de comenzar a mover el dinero. Pero a menudo los problemas como este ocurren de formas más sutiles. Incluso las funciones que no parecen que lanzarán una excepción podrían hacerlo en circunstancias excepcionales o cuando contienen un error del programador.

+

Ese código podría haber sido escrito de manera un poco más inteligente, por ejemplo, llamando a obtenerCuenta antes de comenzar a mover el dinero. Pero a menudo problemas como este ocurren de formas mucho más sutiles. Incluso funciones que aparentemente no lanzarían una excepción podrían hacerlo en circunstancias excepcionales o cuando contienen un error del programador.

-

Una manera de abordar esto es utilizar menos efectos secundarios. Nuevamente, un estilo de programación que calcule nuevos valores en lugar de cambiar datos existentes ayuda. Si un fragmento de código deja de ejecutarse en medio de la creación de un nuevo valor, no se dañaron estructuras de datos existentes, lo que facilita la recuperación.

+

Una manera de abordar este problema es utilizar menos efectos secundarios. De nuevo, un estilo de programación que calcule valores nuevos en lugar de cambiar datos existentes, ayuda. Si un fragmento de código deja de ejecutarse en medio de la creación de un nuevo valor, al menos no se dañan estructuras de datos existentes, lo que facilita la recuperación.

-

Pero eso no siempre es práctico. Por eso existe otra característica que tienen las instrucciones try. Pueden estar seguidas de un bloque finally en lugar o además de un bloque catch. Un bloque finally dice “sin importar qué suceda, ejecuta este código después de intentar ejecutar el código en el bloque try.”

+

Como eso no siempre es práctico, las instrucciones try tienen otra funcionalidad: pueden estar seguidas de un bloque finally en lugar o además de un bloque catch. Un bloque finally dice “sin importar qué suceda, ejecuta este código después de intentar ejecutar el código en el bloque try.”

-
function transfer(from, amount) {
-  if (accounts[from] < amount) return;
-  let progress = 0;
+
function transferir(desde, cantidad) {
+  if (cuentas[desde] < cantidad) return;
+  let progreso = 0;
   try {
-    accounts[from] -= amount;
-    progress = 1;
-    accounts[getAccount()] += amount;
-    progress = 2;
+    cuentas[desde] -= cantidad;
+    progreso = 1;
+    cuentas[obtenerCuenta()] += cantidad;
+    progreso = 2;
   } finally {
-    if (progress == 1) {
-      accounts[from] += amount;
+    if (progreso == 1) {
+      cuentas[desde] += cantidad;
     }
   }
 }
-

Esta versión de la función rastrea su progreso y, si al salir nota que fue abortada en un punto donde había creado un estado del programa inconsistente, repara el daño causado.

+

Esta versión de la función rastrea su progreso y, si al salir se da cuenta de que pasó algo en un punto donde había creado un estado del programa inconsistente, repara el daño causado.

Cabe destacar que aunque el código finally se ejecuta cuando se lanza una excepción en el bloque try, no interfiere con la excepción. Después de que se ejecuta el bloque finally, la pila continúa desenrollándose.

-

Escribir programas que funcionen de manera confiable incluso cuando surgen excepciones en lugares inesperados es difícil. Muchas personas simplemente no se preocupan, y debido a que las excepciones suelen reservarse para circunstancias excepcionales, el problema puede ocurrir tan raramente que ni siquiera se note. Si eso es algo bueno o realmente malo depende de cuánto daño causará el software cuando falle.

+

Escribir programas que funcionen de manera fiable incluso cuando surgen excepciones en lugares inesperados es difícil. Mucha gente simplemente no se preocupa, y debido a que las excepciones suelen reservarse para circunstancias excepcionales, el problema puede ocurrir tan raramente que ni siquiera se note. Si eso es algo bueno o realmente malo depende de cuánto daño causará el software cuando falle.

Captura selectiva

@@ -299,19 +303,19 @@

Para errores de programación, a menudo dejar que el error siga su curso es lo mejor que se puede hacer. Una excepción no manejada es una forma razonable de señalar un programa defectuoso, y la consola de JavaScript proporcionará, en navegadores modernos, información sobre qué llamadas a funciones estaban en la pila cuando ocurrió el problema.

-

Para problemas que se espera que ocurran durante el uso rutinario, fallar con una excepción no manejada es una estrategia terrible.

+

Para problemas que se espera que puedan ocurrir de normal, fallar con una excepción no manejada es una muy mala estrategia.

Usos incorrectos del lenguaje, como hacer referencia a un enlace inexistente, buscar una propiedad en null o llamar a algo que no es una función, también provocarán que se lancen excepciones. Estas excepciones también pueden ser capturadas.

Cuando se entra en un cuerpo catch, todo lo que sabemos es que algo en nuestro cuerpo try causó una excepción. Pero no sabemos qué lo hizo ni qué excepción causó.

-

JavaScript (en una omisión bastante llamativa) no proporciona un soporte directo para capturar excepciones selectivamente: o las capturas todas o no capturas ninguna. Esto hace que sea tentador asumir que la excepción que obtienes es la que tenías en mente cuando escribiste el bloque catch.

+

JavaScript (en una omisión bastante evidente) no proporciona un soporte directo para capturar excepciones selectivamente: o las capturas todas o no capturas ninguna. Esto hace que sea tentador asumir que la excepción que obtienes es la que tenías en mente cuando escribiste el bloque catch.

-

Pero podría no serlo. Alguno otra asunción podría estar violada, o podrías haber introducido un error que está causando una excepción. Aquí tienes un ejemplo que intenta seguir llamando a promptDirection hasta obtener una respuesta válida:

+

Pero podría no serlo. Algún otro supuesto podría no cumplirse, o puede que hayas introducido un error que está causando una excepción. Aquí tienes un ejemplo que intenta seguir llamando a solicitarDirección hasta obtener una respuesta válida:

-
for (;;) {
+
for (;;) {
   try {
-    let dir = promptDirection("¿Dónde?"); // ← ¡Error de tipeo!
+    let dir = soliitarDirección("¿Dónde?"); // ← ¡Error de tipeo!
     console.log("Elegiste ", dir);
     break;
   } catch (e) {
@@ -319,32 +323,32 @@ 

-

La construcción for (;;) es una forma de crear intencionalmente un bucle que no se termina por sí mismo. Salimos del bucle solo cuando se proporciona una dirección válida. Pero escribimos mal promptDirection, lo que resultará en un error de “variable no definida”. Debido a que el bloque catch ignora por completo el valor de la excepción (e), asumiendo que sabe cuál es el problema, trata erróneamente el error de enlace mal escrito como indicativo de una entrada incorrecta. Esto no solo causa un bucle infinito, sino que también “entorpece” el útil mensaje de error sobre el enlace mal escrito.

+

La construcción for (;;) es una forma de crear intencionalmente un bucle que no se termina por sí mismo. Salimos del bucle solo cuando se proporciona una dirección válida. Pero escribimos mal solicitarDirección, lo que resultará en un error de “variable no definida”. Debido a que el bloque catch ignora por completo el valor de la excepción (e), trata erróneamente el error de asociación mal escrita al asumir que sabe cuál es el problema, indicando entonces que el problema se debió a una entrada incorrecta. Esto no solo causa un bucle infinito, sino que también “entierra” el útil mensaje de error sobre el enlace mal escrito.

-

Como regla general, no captures excepciones de manera general a menos que sea con el propósito de “enviarlas” a algún lugar, por ejemplo, a través de la red para informar a otro sistema que nuestro programa se bloqueó. E incluso en ese caso, piensa cuidadosamente cómo podrías estar ocultando información.

+

Como regla general, no captures excepciones indiscriminadamente a menos que sea con el propósito de “enviarlas” a algún lugar, por ejemplo, a través de la red para informar a otro sistema de que nuestro programa se bloqueó. E incluso en ese caso, piensa cuidadosamente cómo podrías estar ocultando información.

-

Por lo tanto, queremos capturar un tipo específico de excepción. Podemos hacer esto verificando en el bloque catch si la excepción que recibimos es la que nos interesa y relanzándola en caso contrario. Pero, ¿cómo reconocemos una excepción?

+

Queremos capturar un tipo específico de excepción. Podemos hacer esto verificando en el bloque catch si la excepción que recibimos es la que nos interesa y relanzándola en caso contrario. Pero, ¿cómo reconocemos una excepción?

-

Podríamos comparar su propiedad message con el mensaje que esperamos error. Pero esta es una forma poco confiable de escribir código, estaríamos utilizando información diseñada para consumo humano (el mensaje) para tomar una decisión programática. Tan pronto como alguien cambie (o traduzca) el mensaje, el código dejará de funcionar.

+

Podríamos comparar su propiedad message con el mensaje de error que esperamos. Pero esta es una forma poco fiable de escribir código, estaríamos utilizando información diseñada para consumo humano (el mensaje) para tomar una decisión programática. Tan pronto como alguien cambie (o traduzca) el mensaje, el código dejará de funcionar.

En lugar de eso, definamos un nuevo tipo de error y usemos instanceof para identificarlo.

-
class InputError extends Error {}
+
class InputError extends Error {}
 
-function promptDirection(question) {
-  let result = prompt(question);
-  if (result.toLowerCase() == "izquierda") return "I";
-  if (result.toLowerCase() == "derecha") return "D";
-  throw new InputError("Dirección no válida: " + result);
+function solicitarDirección(pregunta) {
+  let resultado = prompt(pregunta);
+  if (resultado.toLowerCase() == "izquierda") return "I";
+  if (resultado.toLowerCase() == "derecha") return "D";
+  throw new InputError("Dirección no válida: " + resultado);
 }
-

La nueva clase de error extiende Error. No define su propio constructor, lo que significa que hereda el constructor de Error, que espera un mensaje de cadena como argumento. De hecho, no define nada en absoluto, la clase está vacía. Los objetos InputError se comportan como objetos Error, excepto que tienen una clase diferente mediante la cual podemos reconocerlos.

+

La nueva clase de error extiende la clase Error. No define su propio constructor, lo que significa que hereda el constructor de Error, que espera un mensaje de cadena como argumento. De hecho, no define nada en absoluto, la clase está vacía. Los objetos InputError se comportan como objetos Error, excepto que tienen una clase diferente mediante la cual podemos reconocerlos.

Ahora el bucle puede capturar esto con más cuidado.

-
for (;;) {
+
for (;;) {
   try {
-    let dir = promptDirection("¿Dónde?");
+    let dir = solicitarDirección("¿Dónde?");
     console.log("Elegiste ", dir);
     break;
   } catch (e) {
@@ -356,11 +360,11 @@ 

-

Esto capturará solo instancias de InputError y permitirá que pasen excepciones no relacionadas. Si vuelves a introducir el error de tipeo, el error de enlace no definido se informará correctamente.

+

Esto capturará solo instancias de InputError y permitirá que cualquier excepción no relacionada pase sin más (solamente lanzando el error). Si vuelves a introducir el error de tipeo, el error de enlace no definido se informará correctamente.

-

Afirmaciones

+

Asertos

-

Las afirmaciones son verificaciones dentro de un programa que aseguran que algo es como se supone que debe ser. Se utilizan no para manejar situaciones que pueden surgir en la operación normal, sino para encontrar errores de programación.

+

Los asertos son verificaciones dentro de un programa que aseguran que algo es como se supone que debe ser. Se utilizan no para manejar situaciones que pueden surgir con un uso normal del programa, sino para encontrar errores del programador.

Si, por ejemplo, se describe primerElemento como una función que nunca debería ser llamada en arrays vacíos, podríamos escribirla de la siguiente manera:

@@ -371,15 +375,15 @@

return array[0]; }

-

Ahora, en lugar de devolver silenciosamente undefined (que es lo que obtienes al leer una propiedad de un array que no existe), esto hará que tu programa falle ruidosamente tan pronto como lo uses incorrectamente. Esto hace que sea menos probable que tales errores pasen desapercibidos y más fácil encontrar su causa cuando ocurran.

+

Ahora, en lugar de devolver silenciosamente undefined (que es lo que obtienes al leer una propiedad de un array que no existe), esto hará que tu programa falle “ruidosamente” tan pronto como lo uses incorrectamente. Esto hace que sea menos probable que tales errores pasen desapercibidos y más fácil encontrar su causa cuando ocurran.

-

No recomiendo intentar escribir afirmaciones para cada tipo de entrada incorrecta posible. Eso sería mucho trabajo y llevaría a un código muy ruidoso. Querrás reservarlas para errores que son fáciles de cometer (o que te encuentres cometiendo).

+

No recomiendo intentar escribir afirmaciones para cada tipo de entrada incorrecta posible. Eso sería mucho trabajo y llevaría a un código muy ruidoso. Querrás reservarlas para errores que son fáciles de cometer (o que veas que estás cometiendo).

Resumen

-

Una parte importante de programar es encontrar, diagnosticar y corregir errores. Los problemas pueden ser más fáciles de notar si tienes un conjunto de pruebas automatizadas o agregas afirmaciones a tus programas.

+

Una parte importante de programar es encontrar, diagnosticar y corregir errores. Los problemas pueden ser más fáciles de notar si tienes un conjunto de tests automatizados o agregas asertos a tus programas.

-

Los problemas causados por factores fuera del control del programa generalmente deberían ser planificados activamente. A veces, cuando el problema puede ser manejado localmente, los valores de retorno especiales son una buena forma de rastrearlos. De lo contrario, las excepciones pueden ser preferibles.

+

Los problemas causados por factores fuera del control del programa generalmente deberían ser planificados activamente. A veces, cuando el problema puede ser manejado localmente, los valores de retorno especiales son una buena forma de rastrearlos. De lo contrario, puede ser preferible usar excepciones.

Lanzar una excepción provoca que la pila de llamadas se desenrolle hasta el próximo bloque try/catch envolvente o hasta la base de la pila. El valor de la excepción será entregado al bloque catch que la captura, el cual debe verificar que sea realmente el tipo de excepción esperado y luego hacer algo con él. Para ayudar a abordar el flujo de control impredecible causado por las excepciones, se pueden utilizar bloques finally para asegurar que un trozo de código se ejecute siempre cuando un bloque termina.

@@ -412,7 +416,7 @@

La llamada a primitiveMultiply definitivamente debería ocurrir en un bloque try. El bloque catch correspondiente debería relanzar la excepción cuando no sea una instancia de MultiplicatorUnitFailure y asegurarse de que la llamada se reintente cuando lo sea.

-

Para hacer el reintentamiento, puedes usar un bucle que se detenga solo cuando una llamada tiene éxito, como en el ejemplo de look anterior en este capítulo, o usar la recursión y esperar que no tengas una cadena tan larga de fallos que colapse la pila (lo cual es bastante improbable).

+

Para hacer el reintento, puedes usar un bucle que se detenga solo cuando una llamada tiene éxito, como en el ejemplo de mirar anterior en este capítulo, o usar la recursión y esperar que no tengas una cadena tan larga de fallos que colapse la pila (lo cual es bastante improbable).

@@ -434,7 +438,7 @@

Es una caja con una cerradura. Hay un array en la caja, pero solo puedes acceder a él cuando la caja está desbloqueada.

-

Escribe una función llamada withBoxUnlocked que reciba como argumento un valor de función, desbloquee la caja, ejecute la función y luego asegure que la caja esté cerrada de nuevo antes de devolverla, independientemente de si la función de argumento devolvió normalmente o lanzó una excepción.

+

Escribe una función llamada withBoxUnlocked que reciba como argumento un valor de función, desbloquee la caja, ejecute la función y luego asegure que la caja esté cerrada de nuevo antes de devolverla, independientemente de si la función de argumento terminó con normalidad o lanzó una excepción.

const box = new class {
   locked = true;
@@ -466,7 +470,7 @@ 

// → true

-

Para puntos adicionales, asegúrate de que si llamas a withBoxUnlocked cuando la caja ya está desbloqueada, la caja permanezca desbloqueada.

+

Para más puntos, asegúrate de que si llamas a withBoxUnlocked cuando la caja ya está desbloqueada, la caja permanezca desbloqueada.

Mostrar pistas...
From 9d7353fa73d80fbb565082239798b107ebc52c22 Mon Sep 17 00:00:00 2001 From: ckdvk Date: Sun, 16 Feb 2025 15:06:20 +0800 Subject: [PATCH 20/36] =?UTF-8?q?revisado=20cap=C3=ADtulo=209.=20Corregida?= =?UTF-8?q?s=20las=20apariciones=20de=20caracter=20sin=20tilde?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 02_program_structure.md | 2 +- 03_functions.md | 6 +- 04_data.md | 2 +- 05_higher_order.md | 12 +- 09_regexp.md | 281 ++++++++++++++++---------------- html/02_program_structure.html | 2 +- html/03_functions.html | 6 +- html/04_data.html | 2 +- html/05_higher_order.html | 18 +-- html/09_regexp.html | 285 +++++++++++++++++---------------- 10 files changed, 311 insertions(+), 305 deletions(-) diff --git a/02_program_structure.md b/02_program_structure.md index cad7a73c..01d89fa8 100644 --- a/02_program_structure.md +++ b/02_program_structure.md @@ -134,7 +134,7 @@ La palabra `const` significa _((constante))_. Define una asociación constante, {{index "carácter de subrayado", "signo de dólar", [enlace, nomenclatura]}} -Los nombres de asociaciones o enlaces pueden ser cualquier secuencia de una o más letras. Podemos incluir dígitos como parte del nombre de un enlace —`catch22` es un nombre válido, por ejemplo—, siempre y cuando el nombre no empiece por uno de ellos. Un nombre de enlace puede incluir signos de dólar (`$`) o subrayados (`_`), pero ningún otro caracter especial o signo de puntuación. +Los nombres de asociaciones o enlaces pueden ser cualquier secuencia de una o más letras. Podemos incluir dígitos como parte del nombre de un enlace —`catch22` es un nombre válido, por ejemplo—, siempre y cuando el nombre no empiece por uno de ellos. Un nombre de enlace puede incluir signos de dólar (`$`) o subrayados (`_`), pero ningún otro carácter especial o signo de puntuación. {{index [sintaxis, identificador], "implements (palabra reservada)", "interface (palabra reservada)", "package (palabra reservada)", "private (palabra reservada)", "protected (palabra reservada)", "public (palabra reservada)", "static (palabra reservada)", "operador void", "yield (palabra reservada)", "enum (palabra reservada)", "palabra reservada", [enlace, nomenclatura]}} diff --git a/03_functions.md b/03_functions.md index e2aaa544..f8546a88 100644 --- a/03_functions.md +++ b/03_functions.md @@ -207,7 +207,7 @@ El código anterior funciona, incluso aunque la función esté definida _debajo_ {{index "función", "función flecha"}} -Hay una tercera notación para funciones que tiene un aspecto muy diferente a las otras. En lugar de la palabra clave `function`, utiliza una flecha (`=>`) compuesta por un signo igual y un caracter mayor que (no confundir con el operador mayor o igual, que se escribe `>=`): +Hay una tercera notación para funciones que tiene un aspecto muy diferente a las otras. En lugar de la palabra clave `function`, utiliza una flecha (`=>`) compuesta por un signo igual y un carácter mayor que (no confundir con el operador mayor o igual, que se escribe `>=`): ```{test: wrap} const redondearA = (n, paso) => { @@ -739,7 +739,7 @@ hint}} {{index "bean counting (exercise)", [string, indexing], "zero-based counting", ["length property", "for string"]}} -Puedes obtener el N-ésimo caracter, o letra, de una cadena escribiendo `[N]` después de la cadena (por ejemplo, `cadena[2]`). El valor resultante será una cadena que contiene solo un carácter (por ejemplo, `"b"`). El primer carácter tiene la posición 0, lo que hace que el último se encuentre en la posición `cadena.length - 1`. En otras palabras, una cadena de dos caracteres tiene longitud 2, y sus caracteres tienen posiciones 0 y 1. +Puedes obtener el N-ésimo carácter, o letra, de una cadena escribiendo `[N]` después de la cadena (por ejemplo, `cadena[2]`). El valor resultante será una cadena que contiene solo un carácter (por ejemplo, `"b"`). El primer carácter tiene la posición 0, lo que hace que el último se encuentre en la posición `cadena.length - 1`. En otras palabras, una cadena de dos caracteres tiene longitud 2, y sus caracteres tienen posiciones 0 y 1. Escribe una función `countBs` (contarBs) que tome una cadena como único argumento y devuelva un número que indique cuántos caracteres B en mayúscula hay en la cadena. @@ -762,7 +762,7 @@ if}} {{index "bean counting (exercise)", ["length property", "for string"], "counter variable"}} -Tu función necesita un ((bucle)) que mire cada carácter en la cadena. Puede recorrer un índice desde cero hasta uno menos que su longitud (`< cadena.length`). Si el caracter en la posición actual es el mismo que el que la función está buscando, agrega 1 a una variable contadora. Una vez que el bucle ha terminado, el contador puede ser devuelto. +Tu función necesita un ((bucle)) que mire cada carácter en la cadena. Puede recorrer un índice desde cero hasta uno menos que su longitud (`< cadena.length`). Si el carácter en la posición actual es el mismo que el que la función está buscando, agrega 1 a una variable contadora. Una vez que el bucle ha terminado, el contador puede ser devuelto. {{index "local binding"}} diff --git a/04_data.md b/04_data.md index 8e26eb28..2fff0da8 100644 --- a/04_data.md +++ b/04_data.md @@ -669,7 +669,7 @@ console.log(" de acuerdo \n ".trim()); {{id padStart}} -La función `rellenarConCeros` del [capítulo anterior](functions) también existe como un método. Se llama `padStart` y recibe la longitud deseada y el caracter de relleno como argumentos: +La función `rellenarConCeros` del [capítulo anterior](functions) también existe como un método. Se llama `padStart` y recibe la longitud deseada y el carácter de relleno como argumentos: ``` console.log(String(6).padStart(3, "0")); diff --git a/05_higher_order.md b/05_higher_order.md index c6fb693f..bf2f2f4a 100644 --- a/05_higher_order.md +++ b/05_higher_order.md @@ -419,7 +419,7 @@ El método `some` es otra función de orden superior. Toma una función de compr Pero, ¿cómo obtenemos los códigos de caracteres en una cadena? -En el [Capítulo ?](values) mencioné que las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estos se llaman _((unidades de código))_. Al principio, se suponía que un código de caracter Unicode cabía dentro de tal unidad (lo que te da algo más de 65000 caracteres). Cuando quedó claro que eso no iba a ser suficiente, mucha gente se mostró reacia a la necesidad de usar más memoria por caracter. Para abordar estas preocupaciones, se inventó ((UTF-16)), el formato que usan las cadenas de JavaScript. Describe la mayoría de los caracteres comunes usando una única unidad de código de 16 bits, pero usa un par de dos unidades de dicho tipo para otros. +En el [Capítulo ?](values) mencioné que las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estos se llaman _((unidades de código))_. Al principio, se suponía que un código de carácter Unicode cabía dentro de tal unidad (lo que te da algo más de 65000 caracteres). Cuando quedó claro que eso no iba a ser suficiente, mucha gente se mostró reacia a la necesidad de usar más memoria por carácter. Para abordar estas preocupaciones, se inventó ((UTF-16)), el formato que usan las cadenas de JavaScript. Describe la mayoría de los caracteres comunes usando una única unidad de código de 16 bits, pero usa un par de dos unidades de dicho tipo para otros. {{index error}} @@ -435,7 +435,7 @@ let caballoZapato = "🐴👟"; console.log(caballoZapato.length); // → 4 console.log(caballoZapato[0]); -// → (Mitad de caracter inválida) +// → (Mitad de carácter inválida) console.log(caballoZapato.charCodeAt(0)); // → 55357 (Código de la mitad de caracter) console.log(caballoZapato.codePointAt(0)); @@ -452,14 +452,14 @@ En el [capítulo anterior](datos#bucle_for_of), mencioné que un bucle `for`/`of ``` let rosaDragón = "🌹🐉"; -for (let caracter of rosaDragón) { +for (let carácter of rosaDragón) { console.log(caracter); } // → 🌹 // → 🐉 ``` -Si tienes un caracter (que será una cadena de una o dos unidades de código), puedes usar `codePointAt(0)` para obtener su código. +Si tienes un carácter (que será una cadena de una o dos unidades de código), puedes usar `codePointAt(0)` para obtener su código. ## Reconociendo texto @@ -498,8 +498,8 @@ Usando `contarPor`, podemos escribir la función que nos dice qué sistemas de e ```{includeCode: strip_log, startCode: true} function sistemasTexto(texto) { - let sistemas = contarPor(texto, caracter => { - let sistema = sistemaCaracteres(caracter.codePointAt(0)); + let sistemas = contarPor(texto, carácter => { + let sistema = sistemaCaracteres(carácter.codePointAt(0)); return sistema ? sistema.name : "ninguno"; }).filter(({nombre}) => nombre != "ninguno"); diff --git a/09_regexp.md b/09_regexp.md index 7fd24c38..4d48edd1 100644 --- a/09_regexp.md +++ b/09_regexp.md @@ -2,7 +2,7 @@ {{quote {author: "Jamie Zawinski", chapter: true} -Algunas personas, cuando se enfrentan a un problema, piensan '¡Ya sé, usaré expresiones regulares!' Ahora tienen dos problemas. +Hay gente que, cuando se enfrenta a un problema, piensa '¡Ya sé, usaré expresiones regulares!' Ahora tienen dos problemas. quote}} @@ -10,7 +10,7 @@ quote}} {{if interactive -{{quote {author: "Master Yuan-Ma", title: "El Libro de la Programación", chapter: true} +{{quote {author: "Master Yuan-Ma", title: "The Book of Programming", chapter: true} Cuando cortas en contra de la veta de la madera, se necesita mucha fuerza. Cuando programas en contra de la veta del problema, se necesita mucho código. @@ -22,21 +22,21 @@ if}} {{index "evolución", "adopción", "integración"}} -Las herramientas y técnicas de programación sobreviven y se propagan de manera caótica y evolutiva. No siempre ganan las mejores o brillantes, sino aquellas que funcionan lo suficientemente bien dentro del nicho correcto o que se integran con otra pieza exitosa de tecnología. +Las herramientas y técnicas de programación sobreviven y se propagan de manera caótica y evolutiva. No siempre ganan las mejores o más brillantes, sino aquellas que funcionan lo suficientemente bien dentro del nicho correcto o que, por casualidad, están integradas en algún componente tecnológico exitoso. {{index "lenguaje específico de dominio"}} -En este capítulo, discutiré una de esas herramientas, _((expresiones regulares))_. Las expresiones regulares son una forma de describir ((patrón))es en datos de cadena. Forman un pequeño lenguaje separado que es parte de JavaScript y muchos otros lenguajes y sistemas. +En este capítulo, discutiré una de esas herramientas: las _((expresiones regulares))_. Las expresiones regulares son una forma de describir ((patrón))es en datos de tipo cadena. Forman un pequeño lenguaje separado que es parte de JavaScript y muchos otros lenguajes y sistemas. {{index [interfaz, "diseño"]}} -Las expresiones regulares son tanto terriblemente incómodas como extremadamente útiles. Su sintaxis es críptica y la interfaz de programación que JavaScript proporciona para ellas es torpe. Pero son una herramienta poderosa para inspeccionar y procesar cadenas. Comprender adecuadamente las expresiones regulares te hará un programador más efectivo. +Las expresiones regulares son tanto terriblemente incómodas como extremadamente útiles. Su sintaxis es críptica y la interfaz de programación que JavaScript proporciona para ellas es torpe. Pero son una herramienta poderosa para inspeccionar y procesar cadenas. Comprender adecuadamente las expresiones regulares hará de ti un programador más efectivo. ## Creando una expresión regular {{index ["expresión regular", "creación"], "clase RegExp", "expresión literal", "carácter de barra diagonal"}} -Una expresión regular es un tipo de objeto. Puede ser construido con el constructor `RegExp` o escrito como un valor literal al encerrar un patrón entre caracteres de barra diagonal (`/`). +Una expresión regular es un tipo de objeto. Se puede construir con el constructor `RegExp` o escrito como un valor literal al encerrar un patrón entre caracteres de barra hacia adelante (`/`). ``` let re1 = new RegExp("abc"); @@ -51,13 +51,13 @@ Cuando se utiliza el constructor `RegExp`, el patrón se escribe como una cadena {{index ["expresión regular", escape], [escape, "en regexps"], "carácter de barra diagonal"}} -La segunda notación, donde el patrón aparece entre caracteres de barra diagonal, trata las barras invertidas de manera un poco diferente. Primero, dado que una barra diagonal termina el patrón, debemos poner una barra invertida antes de cualquier barra diagonal que queramos que sea _parte_ del patrón. Además, las barras invertidas que no forman parte de códigos de caracteres especiales (como `\n`) serán _preservadas_, en lugar de ser ignoradas como lo son en las cadenas, y cambian el significado del patrón. Algunos caracteres, como signos de interrogación y signos de más, tienen significados especiales en las expresiones regulares y deben ser precedidos por una barra invertida si se desea representar el propio carácter. +La segunda notación, donde el patrón aparece entre caracteres de barra diagonal, trata las barras invertidas de manera un poco diferente. Primero, dado que el patrón termina con una barra diagonal, debemos poner una barra invertida antes de cualquier barra diagonal que queramos que sea _parte_ del patrón. Además, las barras invertidas que no forman parte de códigos de caracteres especiales (como `\n`) serán _preservadas_, en lugar de ser ignoradas como lo son en las cadenas, y cambian el significado del patrón. Algunos caracteres, como signos de interrogación y signos de suma, tienen significados especiales en las expresiones regulares y deben ser precedidos por una barra invertida si se desea representar el propio carácter. ``` -let aPlus = /A\+/; +let unMás = /Un\+/; ``` -## Pruebas de coincidencias +## Testeo para coincidencias {{index coincidencia, "método test", ["expresión regular", "métodos"]}} @@ -72,7 +72,7 @@ console.log(/abc/.test("abxde")); {{index "patrón"}} -Una ((expresión regular)) que consiste solo en caracteres no especiales simplemente representa esa secuencia de caracteres. Si _abc_ aparece en cualquier parte de la cadena contra la cual estamos probando (no solo al principio), `test` devolverá `true`. +Una ((expresión regular)) que consiste solo en caracteres no especiales simplemente representa esa secuencia de caracteres. Si _abc_ aparece en cualquier parte de la cadena contra la cual estamos testeando (no solo al principio), `test` devolverá `true`. ## Conjuntos de caracteres @@ -80,7 +80,7 @@ Una ((expresión regular)) que consiste solo en caracteres no especiales simplem Descubrir si una cadena contiene _abc_ también se podría hacer con una llamada a `indexOf`. Las expresiones regulares son útiles porque nos permiten describir patrones más complicados. -Digamos que queremos hacer coincidir cualquier ((número)). En una expresión regular, poner un ((conjunto)) de caracteres entre corchetes hace que esa parte de la expresión coincida con cualquiera de los caracteres entre los corchetes. +Digamos que queremos recoger cualquier ((número)). En una expresión regular, poner un ((conjunto)) de caracteres entre corchetes hace que esa parte de la expresión coincida con cualquiera de los caracteres entre los corchetes. Ambas expresiones siguientes hacen coincidir todas las cadenas que contienen un ((dígito)): @@ -93,17 +93,17 @@ console.log(/[0-9]/.test("in 1992")); {{index "carácter de guion"}} -Dentro de corchetes, un guion (`-`) entre dos caracteres se puede usar para indicar un rango de caracteres, donde el orden es determinado por el número del carácter en el ((Unicode)). Los caracteres del 0 al 9 están uno al lado del otro en este orden (códigos 48 a 57), por lo que `[0-9]` abarca todos ellos y coincide con cualquier ((dígito)). +Dentro de corchetes, se puede usar un guion (`-`) entre dos caracteres para indicar un rango de caracteres, donde el orden es determinado por el número del carácter en la codificación ((Unicode)). Los caracteres del 0 al 9 están uno al lado del otro en este orden (códigos 48 a 57), por lo que `[0-9]` abarca todos ellos y coincide con cualquier ((dígito)). -{{index ["espacio en blanco", coincidencia], "caracter alfanumérico", "caracter de punto"}} +{{index ["espacio en blanco", coincidencia], "carácter alfanumérico", "carácter de punto"}} -Varios grupos comunes de caracteres tienen sus propias abreviaturas incorporadas. Los dígitos son uno de ellos: `\d` significa lo mismo que `[0-9]`. +Algunos grupos comunes de caracteres tienen sus propias abreviaturas incorporadas. Los dígitos son uno de ellos: `\d` significa lo mismo que `[0-9]`. {{index "carácter de nueva línea", ["espacio en blanco", coincidencia]}} {{table {cols: [1, 5]}}} -| `\d` | Cualquier carácter ((dígito)) +| `\d` | Cualquier carácter de ((dígito)) | `\w` | Un carácter alfanumérico ("carácter de palabra") | `\s` | Cualquier carácter de espacio en blanco (espacio, tabulación, nueva línea, y similares) | `\D` | Un carácter que _no_ es un dígito @@ -114,16 +114,16 @@ Varios grupos comunes de caracteres tienen sus propias abreviaturas incorporadas Así que podrías hacer coincidir un formato de ((fecha)) y ((hora)) como 01-30-2003 15:20 con la siguiente expresión: ``` -let dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/; -console.log(dateTime.test("01-30-2003 15:20")); +let fechaYHora = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/; +console.log(fechaYHora.test("01-30-2003 15:20")); // → true -console.log(dateTime.test("30-ene-2003 15:20")); +console.log(fechaYHora.test("30-ene-2003 15:20")); // → false ``` {{index ["carácter de barra invertida", "en expresiones regulares"]}} -¡Eso se ve completamente horrible, ¿verdad? La mitad son barras invertidas, produciendo un ruido de fondo que dificulta identificar el ((patrón)) expresado. Veremos una versión ligeramente mejorada de esta expresión [más adelante](regexp#date_regexp_counted). +Tiene una pinta terrible, ¿verdad? La mitad son barras invertidas, produciendo un ruido de fondo que dificulta identificar el ((patrón)) expresado. Veremos una versión ligeramente mejorada de esta expresión [más adelante](regexp#date_regexp_counted). {{index [escape, "en regexps"], "expresión regular", conjunto}} @@ -134,10 +134,10 @@ Estos códigos de barra invertida también se pueden usar dentro de ((corchetes) Para _invertir_ un conjunto de caracteres, es decir, expresar que deseas hacer coincidir cualquier carácter _excepto_ los que están en el conjunto, puedes escribir un carácter circunflejo (`^`) después del corchete de apertura. ``` -let nonBinary = /[^01]/; -console.log(nonBinary.test("1100100010100110")); +let noBinario = /[^01]/; +console.log(noBinario.test("1100100010100110")); // → false -console.log(nonBinary.test("0111010112101001")); +console.log(noBinario.test("0111010112101001")); // → true ``` @@ -145,7 +145,7 @@ console.log(nonBinary.test("0111010112101001")); {{index "internacionalización", Unicode, ["expresión regular", "internacionalización"]}} -Debido a la implementación simplista inicial de JavaScript y al hecho de que este enfoque simplista luego se estableció como comportamiento ((estándar)), las expresiones regulares de JavaScript son bastante simples en lo que respecta a los caracteres que no aparecen en el idioma inglés. Por ejemplo, según las expresiones regulares de JavaScript, un "((carácter de palabra))" es solo uno de los 26 caracteres del alfabeto latino (mayúsculas o minúsculas), dígitos decimales y, por alguna razón, el guion bajo. Cosas como _é_ o _β_, que definitivamente son caracteres de palabra, no coincidirán con `\w` (y _sí_ coincidirán con `\W` en mayúsculas, la categoría de no palabras). +Debido a la inicial implementación simplista de JavaScript y al hecho de que este enfoque simplista luego se estableció como comportamiento ((estándar)), las expresiones regulares de JavaScript son bastante limitadas en lo que respecta a los caracteres que no aparecen en el idioma inglés. Por ejemplo, según las expresiones regulares de JavaScript, un "((carácter de palabra))" es solo uno de los 26 caracteres del alfabeto latino (mayúsculas o minúsculas), dígitos de la base 10 y, por alguna razón, el guion bajo. Cosas como _é_ o _β_, que claramente son caracteres de palabra, no coincidirán con `\w` (y _sí_ coincidirán con `\W` en mayúsculas, la categoría de no palabras). {{index [espacio en blanco, coincidencia]}} @@ -161,9 +161,9 @@ Es posible usar `\p` en una expresión regular para hacer coincidir todos los ca | `\p{N}` | Cualquier carácter numérico | `\p{P}` | Cualquier carácter de puntuación | `\P{L}` | Cualquier no letra (la P en mayúsculas invierte) -| `\p{Script=Hangul}` | Cualquier carácter del guion dado (ver [Capítulo ?](higher_order#scripts)) +| `\p{Script=Hangul}` | Cualquier carácter del sistema de escritura dado (ver [Capítulo ?](higher_order#scripts)) -Usar `\w` para el procesamiento de texto que puede necesitar manejar texto no inglés (o incluso texto en inglés con palabras prestadas como "cliché") es una desventaja, ya que no tratará caracteres como "é" como letras. Aunque tienden a ser un poco más verbosos, los grupos de propiedades `\p` son más robustos. +Usar `\w` para el procesamiento de texto que puede necesitar manejar texto no inglés (o incluso texto en inglés con palabras prestadas como "cliché") es un riesgo, ya que no tratará caracteres como "é" como letras. Aunque tienden a ser un poco más verbosos, los grupos de propiedades `\p` son más robustos. ```{test: never} console.log(/\p{L}/u.test("α")); @@ -226,8 +226,8 @@ Para indicar que un patrón debe ocurrir un número preciso de veces, utiliza ll Aquí tienes otra versión del patrón de ((fecha)) y ((hora)) que permite días, meses y horas de uno o dos ((dígitos)). También es un poco más fácil de entender. ``` -let dateTime = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/; -console.log(dateTime.test("1-30-2003 8:45")); +let fechaYHora = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/; +console.log(fechaYHora.test("1-30-2003 8:45")); // → true ``` @@ -240,14 +240,14 @@ También puedes especificar ((rangos)) abiertos al utilizar llaves omitiendo el Para usar un operador como `*` o `+` en más de un elemento a la vez, debes utilizar paréntesis. Una parte de una expresión regular que está encerrada entre paréntesis cuenta como un solo elemento en lo que respecta a los operadores que le siguen. ``` -let cartoonCrying = /boo+(hoo+)+/i; -console.log(cartoonCrying.test("Boohoooohoohooo")); +let dibujitoLlorando = /boo+(hoo+)+/i; +console.log(dibujitoLlorando.test("Boohoooohoohooo")); // → true ``` {{index crying}} -Los primeros y segundos caracteres `+` aplican solo al segundo _o_ en _boo_ y _hoo_, respectivamente. El tercer `+` se aplica a todo el grupo `(hoo+)`, haciendo coincidir una o más secuencias como esa. +Los primeros y segundos caracteres `+` aplican solo a la segunda _o_ en _boo_ y _hoo_, respectivamente. El tercer `+` se aplica a todo el grupo `(hoo+)`, haciendo coincidir una o más secuencias como esa. {{index "sensibilidad a mayúsculas", "capitalización", ["expresión regular", banderas]}} @@ -269,7 +269,7 @@ console.log(coincidencia.index); {{index "propiedad de índice", [string, "indexación"]}} -Un objeto devuelto por `exec` tiene una propiedad de `index` que nos dice _dónde_ en la cadena comienza la coincidencia exitosa. Aparte de eso, el objeto parece (y de hecho es) un array de strings, cuyo primer elemento es la cadena que coincidió. En el ejemplo anterior, esta es la secuencia de ((dígitos)) que estábamos buscando. +Un objeto devuelto por `exec` tiene una propiedad de `index` que nos dice _dónde_ en la cadena comienza la coincidencia exitosa. Aparte de eso, el objeto parece (y de hecho es) un array de strings, cuyo primer elemento es la cadena que coincidió. En el ejemplo anterior, esta cadena es la serie de ((dígitos)) que estábamos buscando. {{index [string, "métodos"], "método match"}} @@ -282,7 +282,7 @@ console.log("uno dos 100".match(/\d+/)); {{index "agrupación", "grupo de captura", "método exec"}} -Cuando la expresión regular contiene subexpresiones agrupadas con paréntesis, el texto que coincidió con esos grupos también aparecerá en el array. La coincidencia completa es siempre el primer elemento. El siguiente elemento es la parte coincidente con el primer grupo (el que tiene el paréntesis de apertura primero en la expresión), luego el segundo grupo, y así sucesivamente. +Cuando la expresión regular contiene subexpresiones agrupadas con paréntesis, el texto que coincidió con esos grupos también aparecerá en el array. La coincidencia completa es siempre el primer elemento. El siguiente elemento es la parte coincidente con el primer grupo (el que tiene el primer paréntesis de apertura en la expresión), luego el segundo grupo, y así sucesivamente. ``` let textoEntreComillas = /'([^']*)'/; @@ -292,16 +292,16 @@ console.log(textoEntreComillas.exec("ella dijo 'hola'")); {{index "grupo de captura"}} -Cuando un grupo no termina coincidiendo en absoluto (por ejemplo, cuando está seguido por un signo de pregunta), su posición en el array de salida contendrá `undefined`. Y cuando un grupo coincide múltiples veces (por ejemplo, cuando está seguido por un `+`), solo la última coincidencia termina en el array. +Cuando un grupo no coincide con nada (por ejemplo, cuando está seguido por un signo de pregunta), su posición en el array de salida contendrá `undefined`. Y cuando un grupo coincide múltiples veces (por ejemplo, cuando está seguido por un `+`), solo la última coincidencia termina estando en el array. ``` -console.log(/mal(mente)?/.exec("mal")); +console.log(/mal(amente)?/.exec("mal")); // → ["mal", undefined] console.log(/(\d)+/.exec("123")); // → ["123", "3"] ``` -Si quieres utilizar paréntesis puramente para agrupar, sin que aparezcan en el array de coincidencias, puedes colocar `?:` después del paréntesis de apertura. +Si quieres utilizar paréntesis solamente para agrupar, sin que aparezcan en el array de coincidencias, puedes colocar `?:` después del paréntesis de apertura. ``` console.log(/(?:na)+/.exec("banana")); @@ -312,13 +312,13 @@ console.log(/(?:na)+/.exec("banana")); Los grupos pueden ser útiles para extraer partes de una cadena. Si no solo queremos verificar si una cadena contiene una ((fecha)) sino también extraerla y construir un objeto que la represente, podemos envolver paréntesis alrededor de los patrones de dígitos y seleccionar directamente la fecha del resultado de `exec`. -Pero primero haremos un breve desvío, en el que discutiremos la forma incorporada de representar fechas y ((horas)) en JavaScript. +Pero primero haremos un breve paréntesis, en el que discutiremos la forma de representar fechas y ((horas)) en JavaScript. ## La clase Date {{index constructor, "clase Date"}} -JavaScript tiene una clase estándar para representar ((fechas))—o, más bien, puntos en ((tiempo)). Se llama `Date`. Si simplemente creas un objeto de fecha usando `new`, obtendrás la fecha y hora actuales. +JavaScript tiene una clase estándar para representar ((fechas)) —o, más bien, puntos en ((tiempo)). Se llama `Date`. Si simplemente creas un objeto de fecha usando `new`, obtendrás la fecha y hora actuales. ```{test: no} console.log(new Date()); @@ -338,13 +338,13 @@ console.log(new Date(2009, 11, 9, 12, 59, 59, 999)); {{index "Conteo basado en cero", [interfaz, "diseño"]}} -JavaScript utiliza una convención donde los números de mes empiezan en cero (por lo que diciembre es 11), pero los números de día comienzan en uno. Esto es confuso y tonto. Ten cuidado. +JavaScript utiliza una convención donde los números de mes empiezan en cero (por lo que diciembre es 11), pero los números de día comienzan en uno. Esto es confuso y estúpido. Ten cuidado. Los últimos cuatro argumentos (horas, minutos, segundos y milisegundos) son opcionales y se consideran cero cuando no se proporcionan. {{index "Método getTime", marca de tiempo}} -Las marcas de tiempo se almacenan como el número de milisegundos desde el comienzo de 1970, en UTC (zona horaria). Esto sigue una convención establecida por "tiempo de Unix", que fue inventado alrededor de esa época. Puedes usar números negativos para tiempos antes de 1970. El método `getTime` en un objeto de fecha retorna este número. Es grande, como te puedes imaginar. +Las marcas de tiempo (timestamps) se almacenan como el número de milisegundos desde el comienzo de 1970, en la zona horaria UTC. Esto sigue una convención establecida por el "tiempo Unix", que fue inventado por esa época. Puedes usar números negativos para tiempos antes de 1970. El método `getTime` en un objeto de fecha retorna este número. Es grande, como te puedes imaginar. ``` console.log(new Date(2013, 11, 19).getTime()); @@ -359,7 +359,7 @@ Si le proporcionas un único argumento al constructor `Date`, ese argumento se t {{index "Método getFullYear", "Método getMonth", "Método getDate", "Método getHours", "Método getMinutes", "Método getSeconds", "Método getYear"}} -Los objetos de fecha proporcionan métodos como `getFullYear`, `getMonth`, `getDate`, `getHours`, `getMinutes` y `getSeconds` para extraer sus componentes. Además de `getFullYear`, también existe `getYear`, que te da el año menos 1900 (`98` o `119`) y es en su mayoría inútil. +Los objetos de fecha proporcionan métodos como `getFullYear`, `getMonth`, `getDate`, `getHours`, `getMinutes` y `getSeconds` para extraer sus componentes. Además de `getFullYear`, también existe `getYear`, que te da el año menos 1900 (`98` o `119`) y es en esencialmente inútil. {{index "Grupo de captura", "Método getDate", ["paréntesis", "en expresiones regulares"]}} @@ -367,38 +367,38 @@ Los objetos de fecha proporcionan métodos como `getFullYear`, `getMonth`, `getD Poniendo paréntesis alrededor de las partes de la expresión que nos interesan, podemos crear un objeto de fecha a partir de una cadena. ``` -function getDate(string) { - let [_, month, day, year] = - /(\d{1,2})-(\d{1,2})-(\d{4})/.exec(string); - return new Date(year, month - 1, day); +function obtenerFecha(cadena) { + let [_, mes, día, año] = + /(\d{1,2})-(\d{1,2})-(\d{4})/.exec(cadena); + return new Date(año, mes - 1, díaday); } -console.log(getDate("1-30-2003")); +console.log(obtenerFecha("1-30-2003")); // → Jue Ene 30 2003 00:00:00 GMT+0100 (CET) ``` {{index destructuring, "carácter guion bajo"}} -La vinculación `_` (guion bajo) se ignora y se utiliza solo para omitir el elemento de coincidencia completa en el array devuelto por `exec`. +La asociación `_` (guion bajo) se ignora y se utiliza solo para omitir el elemento de coincidencia completa con la expresión regular en el array devuelto por `exec`. ## Límites y anticipación {{index matching, ["expresión regular", "límite"]}} -Desafortunadamente, `getDate` también extraerá felizmente una fecha de la cadena `"100-1-30000"`. Una coincidencia puede ocurrir en cualquier parte de la cadena, por lo que en este caso, simplemente empezará en el segundo carácter y terminará en el antepenúltimo carácter. +Desafortunadamente, `obtenerFecha` también extraerá felizmente una fecha de la cadena `"100-1-30000"`. Una coincidencia puede ocurrir en cualquier parte de la cadena, por lo que en este caso, simplemente empezará en el segundo carácter y terminará en el antepenúltimo carácter. {{index "límite", "carácter circunflejo", "signo de dólar"}} -Si queremos asegurar que la coincidencia abarque toda la cadena, podemos agregar los marcadores `^` y `$`. El circunflejo coincide con el inicio de la cadena de entrada, mientras que el signo de dólar coincide con el final. Por lo tanto, `/^\d+$/` coincide con una cadena que consiste completamente de uno o más dígitos, `/^!/` coincide con cualquier cadena que comience con un signo de exclamación y `/x^/` no coincide con ninguna cadena (no puede haber una _x_ antes del inicio de la cadena). +Si queremos asegurar que la coincidencia abarque toda la cadena, podemos agregar los marcadores `^` y `$`. El circunflejo coincide con el inicio de la cadena de entrada, mientras que el signo de dólar coincide con el final. Por lo tanto, `/^\d+$/` coincide con una cadena que consiste completamente de uno o más dígitos, `/^!/` coincide con cualquier cadena que comience con un signo de exclamación y `/x^/` no coincide con ninguna cadena (es imposible que haya una _x_ antes del inicio de la cadena). {{index "límite de palabra", "carácter de palabra"}} -También existe un marcador `\b`, que coincide con los "límites de palabra", posiciones que tienen un carácter de palabra a un lado y un carácter que no es de palabra al otro. Desafortunadamente, estos utilizan el mismo concepto simplista de caracteres de palabra que `\w`, por lo que no son muy confiables. +También existe un marcador `\b`, que coincide con los "límites de palabra", posiciones que tienen un carácter de palabra a un lado y un carácter que no es de palabra al otro. Desafortunadamente, estos utilizan el mismo concepto simplista de caracteres de palabra que `\w`, por lo que no son muy fiables. Ten en cuenta que estos marcadores no coinciden con ningún carácter real. Simplemente aseguran que se cumpla una condición determinada en el lugar donde aparecen en el patrón. {{index "mirar adelante"}} -Las pruebas de _mirar adelante_ hacen algo similar. Proporcionan un patrón y harán que la coincidencia falle si la entrada no coincide con ese patrón, pero en realidad no mueven la posición de la coincidencia hacia adelante. Se escriben entre `(?=` y `)`. +Las pruebas de _anticipación_ hacen algo similar. Proporcionan un patrón y harán que la coincidencia falle si la entrada no coincide con ese patrón, pero en realidad no mueven la posición de la coincidencia hacia adelante. Se escriben entre `(?=` y `)`. ``` console.log(/a(?=e)/.exec("braeburn")); @@ -407,7 +407,7 @@ console.log(/a(?! )/.exec("a b")); // → null ``` -Observa cómo la `e` en el primer ejemplo es necesaria para coincidir, pero no forma parte de la cadena coincidente. La notación `(?! )` expresa un mirar adelante _negativo_. Esto solo coincide si el patrón entre paréntesis _no_ coincide, lo que hace que el segundo ejemplo solo coincida con caracteres "a" que no tienen un espacio después de ellos. +Observa cómo la `e` en el primer ejemplo es necesaria para coincidir, pero no forma parte de la cadena coincidente. La notación `(?! )` expresa una anticipación _negativa_. Esto solo coincide si el patrón entre paréntesis _no_ coincide, lo que hace que el segundo ejemplo solo coincida con caracteres "a" que no tienen un espacio después de ellos. ## Patrones de elección @@ -415,13 +415,13 @@ Observa cómo la `e` en el primer ejemplo es necesaria para coincidir, pero no f Digamos que queremos saber si un texto contiene no solo un número, sino un número seguido de una de las palabras _pig_, _cow_ o _chicken_, o cualquiera de sus formas en plural. -Podríamos escribir tres expresiones regulares y probarlas sucesivamente, pero hay una forma más sencilla. El carácter de ((barra vertical)) (`|`) denota una ((elección)) entre el patrón a su izquierda y el patrón a su derecha. Así que puedo decir esto: +Podríamos escribir tres expresiones regulares y probarlas sucesivamente, pero hay una forma más sencilla. El carácter de ((barra vertical)) (`|`) denota una ((elección)) entre el patrón a su izquierda y el patrón a su derecha. Podemos usarlo en expresiones como est: ``` -let animalCount = /\d+ (pig|cow|chicken)s?/; -console.log(animalCount.test("15 pigs")); +let recuentoAnimal = /\d+ (pig|cow|chicken)s?/; +console.log(recuentoAnimal.test("15 pigs")); // → true -console.log(animalCount.test("15 pugs")); +console.log(recuentoAnimal.test("15 pugs")); // → false ``` @@ -437,13 +437,13 @@ Conceptualmente, cuando utilizas `exec` o `test`, el motor de expresiones regula {{index ["expresión regular", coincidencia], [coincidencia, algoritmo]}} -Para hacer la coincidencia real, el motor trata a una expresión regular algo así como un ((diagrama de flujo)). Este es el diagrama para la expresión de ganado en el ejemplo anterior: +Para hacer lo que es la coincidencia, el motor trata las expresiones regulares de algún modo como un ((diagrama de flujo)). Este es el diagrama para la expresión de ganado en el ejemplo anterior: -{{figure {url: "img/re_pigchickens.svg", alt: "Diagrama de ferrocarril que primero pasa por un recuadro etiquetado 'dígito', que tiene un bucle que regresa desde después de él a antes de él, y luego un recuadro para un carácter de espacio. Después de eso, el ferrocarril se divide en tres, pasando por cuadros para 'pig', 'cow' y 'chicken'. Después de estos, se reúne de nuevo y pasa por un cuadro etiquetado 's', que, al ser opcional, también tiene un ferrocarril que lo pasa por alto. Finalmente, la línea llega al estado de aceptación."}}} +{{figure {url: "img/re_pigchickens.svg", alt: "Diagrama de ferrocarril que primero pasa por un recuadro etiquetado como 'dígito', que tiene un bucle que regresa desde después de él a antes de él, y luego un recuadro para un carácter de espacio. Después de eso, el diagrama se divide en tres, pasando por cuadros para 'pig', 'cow' y 'chicken'. Después de estos, se reúne de nuevo y pasa por un cuadro etiquetado 's', que, al ser opcional, también tiene un camino que lo pasa por alto. Finalmente, la línea llega al estado de aceptación."}}} {{index ["expresión regular", diagrama de flujo]}} -Nuestra expresión coincide si podemos encontrar un camino desde el lado izquierdo del diagrama hasta el lado derecho. Mantenemos una posición actual en la cadena, y cada vez que avanzamos a través de un recuadro, verificamos que la parte de la cadena después de nuestra posición actual coincida con ese recuadro. +Nuestra expresión coincide cuando podemos encontrar un camino desde el lado izquierdo del diagrama hasta el lado derecho. Mantenemos una posición actual en la cadena, y cada vez que avanzamos a través de un recuadro en el diagrama, verificamos que la parte de la cadena después de nuestra posición actual coincida con ese recuadro. {{id retroceso}} @@ -451,17 +451,17 @@ Nuestra expresión coincide si podemos encontrar un camino desde el lado izquier {{index ["expresión regular", retroceso], "número binario", "número decimal", "número hexadecimal", "diagrama de flujo", [coincidencia, algoritmo], retroceso}} -La expresión regular `/^([01]+b|[\da-f]+h|\d+)$/` coincide ya sea con un número binario seguido de una _b_, un número hexadecimal (es decir, base 16, con las letras _a_ a _f_ representando los dígitos del 10 al 15) seguido de un _h_, o un número decimal regular sin un carácter de sufijo. Este es el diagrama correspondiente: +La expresión regular `/^([01]+b|[\da-f]+h|\d+)$/` coincide ya sea con un número binario seguido de una _b_, un número hexadecimal (es decir, base 16, con las letras _a_ a _f_ representando los dígitos del 10 al 15) seguido de un _h_, o un número decimal normal sin un carácter de sufijo. Este es el diagrama correspondiente: {{figure {url: "img/re_number.svg", alt: "Diagrama de ferrocarril para la expresión regular '^([01]+b|\\d+|[\\da-f]+h)$'"}}} {{index "ramificación"}} -Al coincidir con esta expresión, a menudo sucede que se ingresa por la rama superior (binaria) aunque la entrada en realidad no contenga un número binario. Al coincidir con la cadena `"103"`, por ejemplo, solo se aclara en el 3 que estamos en la rama incorrecta. La cadena _coincide_ con la expresión, simplemente no con la rama en la que nos encontramos actualmente. +Al coincidir con esta expresión, a menudo sucede que se ingresa por la rama superior (binaria) aunque la entrada en realidad no contenga un número binario. Al coincidir con la cadena `"103"`, por ejemplo, solo se aclara en el 3 que estamos en la rama incorrecta. La cadena _coincide_ con la expresión, solo que no con la rama en la que nos encontramos actualmente. {{index retroceso, "problema de búsqueda"}} -Entonces, el coincidente _retrocede_. Al ingresar a una rama, recuerda su posición actual (en este caso, al principio de la cadena, justo después del primer cuadro de límite en el diagrama) para poder retroceder y probar otra rama si la actual no funciona. Para la cadena `"103"`, después de encontrar el carácter 3, intentará la rama para los números hexadecimales, lo cual también falla porque no hay un _h_ después del número. Entonces intenta la rama para los números decimales. Esta encaja, y se informa una coincidencia después de todo. +Entonces, el coincidente _retrocede_ (o hace _backtracking_). Al ingresar a una rama, recuerda su posición actual (en este caso, al principio de la cadena, justo después del primer cuadro de límite en el diagrama) para poder retroceder y probar otra rama si la actual no funciona. Para la cadena `"103"`, después de encontrar el carácter 3, intentará la rama para los números hexadecimales, lo cual también falla porque no hay un _h_ después del número. Entonces intenta la rama para los números decimales. Esta encaja, y se informa una coincidencia después de todo. {{index [coincidencia, algoritmo]}} @@ -477,7 +477,7 @@ Es posible escribir expresiones regulares que realizarán _mucho_ retroceso. Est {{index "bucle interno", [anidamiento, "en expresiones regulares"]}} -Si intenta hacer coincidir una serie larga de ceros y unos sin un caracter _b_ al final, el analizador primero pasa por el bucle interno hasta que se queda sin dígitos. Luego se da cuenta de que no hay _b_, por lo que retrocede una posición, pasa por el bucle externo una vez y vuelve a darse por vencido, intentando retroceder nuevamente fuera del bucle interno. Continuará intentando todas las rutas posibles a través de estos dos bucles. Esto significa que la cantidad de trabajo se _duplica_ con cada carácter adicional. Incluso con apenas unas pocas docenas de caracteres, la coincidencia resultante tomará prácticamente para siempre. +Si intenta hacer coincidir una serie larga de ceros y unos sin un carácter _b_ al final, el analizador primero pasa por el bucle interno hasta que se queda sin dígitos. Luego se da cuenta de que no hay _b_, por lo que retrocede una posición, pasa por el bucle externo una vez y vuelve a darse por vencido, intentando retroceder nuevamente fuera del bucle interno. Continuará intentando todas las rutas posibles a través de estos dos bucles. Esto significa que la cantidad de trabajo se _duplica_ con cada carácter adicional. Incluso con apenas unas pocas docenas de caracteres, la coincidencia resultante llevará prácticamente una eternidad. ## El método replace @@ -524,7 +524,7 @@ Aquí tienes un ejemplo: ``` let stock = "1 limón, 2 repollos y 101 huevos"; -function menosUno(match, cantidad, unidad) { +function menosUno(coincidencia, cantidad, unidad) { cantidad = Number(cantidad) - 1; if (cantidad == 1) { // solo queda uno, se elimina la 's' unidad = unidad.slice(0, unidad.length - 1); @@ -538,23 +538,23 @@ console.log(stock.replace(/(\d+) (\p{L}+)/gu, menosUno)); ``` Esta función toma una cadena, encuentra todas las ocurrencias de un número seguido de una palabra alfanumérica, y devuelve una cadena que tiene una cantidad menos de cada una de esas ocurrencias. -El grupo `(\d+)` termina siendo el argumento `amount` de la función, y el grupo `(\p{L}+)` se asigna a `unit`. La función convierte `amount` a un número, lo cual siempre funciona ya que coincide con `\d+`, y realiza algunos ajustes en caso de que solo quede uno o ninguno. +El grupo `(\d+)` termina siendo el argumento `cantidad` de la función, y el grupo `(\p{L}+)` se asigna a `unidad`. La función convierte `cantidad` a un número, lo cual siempre funciona ya que coincide con `\d+`, y realiza algunos ajustes en caso de que solo quede uno o ninguno. ## Avaricia {{index avaricia, "expresión regular"}} -Es posible usar `replace` para escribir una función que elimine todos los comentarios de un fragmento de código JavaScript. Aquí tienes un primer intento: +Podemos usar `replace` para escribir una función que elimine todos los comentarios de un fragmento de código JavaScript. Aquí tienes un primer intento: ```{test: wrap} -function stripComments(code) { +function quitarComentarios(code) { return code.replace(/\/\/.*|\/\*[^]*\*\//g, ""); } -console.log(stripComments("1 + /* 2 */3")); +console.log(quitarComentarios("1 + /* 2 */3")); // → 1 + 3 -console.log(stripComments("x = 10;// ¡diez!")); +console.log(quitarComentarios("x = 10;// ¡diez!")); // → x = 10; -console.log(stripComments("1 /* a */+/* b */ 1")); +console.log(quitarComentarios("1 /* a */+/* b */ 1")); // → 1 1 ``` @@ -568,36 +568,36 @@ Pero la salida para la última línea parece haber salido mal. ¿Por qué? La parte `[^]*` de la expresión, como describí en la sección sobre retroceso, primero intentará coincidir con todo lo que pueda. Si esto hace que la siguiente parte del patrón falle, el coincidente retrocede un carácter y vuelve a intentar desde ahí. En el ejemplo, el coincidente intenta primero coincidir con el resto completo de la cadena y luego retrocede desde allí. Encontrará una ocurrencia de `*/` después de retroceder cuatro caracteres y coincidirá con eso. Esto no es lo que queríamos, la intención era coincidir con un único comentario, no llegar hasta el final del código y encontrar el final del último comentario de bloque. -Debido a este comportamiento, decimos que los operadores de repetición (`+`, `*`, `?`, y `{}`) son _avariciosos_, lo que significa que coinciden con todo lo que pueden y retroceden desde allí. Si colocas un ((signo de interrogación)) después de ellos (`+?`, `*?`, `??`, `{}?`), se vuelven no avariciosos y comienzan coincidiendo con la menor cantidad posible, coincidiendo más solo cuando el patrón restante no encaja con la coincidencia más pequeña. +Debido a este comportamiento, decimos que los operadores de repetición (`+`, `*`, `?`, y `{}`) son _avariciosos_, lo que significa que coinciden con todo lo que pueden y retroceden desde allí. Si colocas un ((signo de interrogación)) después de ellos (`+?`, `*?`, `??`, `{}?`), se vuelven no avariciosos y comienzan coincidiendo con la menor cantidad posible, expandiéndose solo si el resto del patrón no encaja con la coincidencia más pequeña. Y eso es exactamente lo que queremos en este caso. Al hacer que el asterisco coincida con la menor cantidad de caracteres que nos lleva a `*/`, consumimos un comentario de bloque y nada más. ```{test: wrap} -function stripComments(code) { - return code.replace(/\/\/.*|\/\*[^]*?\*\//g, ""); +function quitarComentarios(código) { + return código.replace(/\/\/.*|\/\*[^]*?\*\//g, ""); } -console.log(stripComments("1 /* a */+/* b */ 1")); +console.log(quitarComentarios("1 /* a */+/* b */ 1")); // → 1 + 1 ``` -Muchos ((error))s en programas de ((expresión regular)) pueden rastrearse hasta el uso no intencionado de un operador avaricioso donde uno no avaricioso funcionaría mejor. Cuando uses un operador de repetición, prefiere la variante no avariciosa. +Muchos ((error))es en programas con ((expresiones regulares)) pueden rastrearse hasta el uso no intencionado de un operador avaricioso donde uno no avaricioso encajaría mejor. Cuando uses un operador de repetición, dale preferencia a la variante no avariciosa. ## Creación dinámica de objetos RegExp {{index ["expresión regular", "creación"], "carácter de subrayado", "clase RegExp"}} -Hay casos en los que es posible que no sepas el patrón exacto que necesitas para hacer coincidir cuando estás escribiendo tu código. Digamos que quieres probar el nombre de usuario en un fragmento de texto. Puedes construir una cadena y usar el `constructor` `RegExp` en ello. Aquí tienes un ejemplo: +Hay casos en los que es posible que no sepas el patrón exacto que necesitas para hacer coincidir cuando estás escribiendo tu código. Digamos que quieres testear el nombre de usuario en un fragmento de texto. Puedes construir una cadena y usar el `constructor` `RegExp` sobre ella. Aquí tienes un ejemplo: ``` -let name = "harry"; -let regexp = new RegExp("(^|\\s)" + name + "($|\\s)", "gi"); +let nombre = "harry"; +let regexp = new RegExp("(^|\\s)" + nombre + "($|\\s)", "gi"); console.log(regexp.test("Harry es un personaje dudoso.")); // → true ``` {{index ["expresión regular", banderas], ["carácter de barra invertida", "en expresiones regulares"]}} -Al crear la parte `\s` de la cadena, tenemos que usar dos barras invertidas porque las estamos escribiendo en una cadena normal, no en una expresión regular entre barras. El segundo argumento del constructor `RegExp` contiene las opciones para la expresión regular, en este caso, `"gi"` para global e insensible a mayúsculas y minúsculas. +Al crear la parte `\s` de la cadena, tenemos que usar dos barras invertidas porque las estamos escribiendo en una expresión de cadena (string) normal, no en una expresión regular entre barras. El segundo argumento del constructor `RegExp` contiene las opciones para la expresión regular, en este caso, `"gi"` para global e insensible a mayúsculas y minúsculas. Este expresión captura el nombre que se le pasa, ya esté al principio o final de una cadena, o rodeado por espacios. Pero ¿qué pasa si el nombre es `"dea+hl[]rd"` porque nuestro usuario es un adolescente ((nerd))? Eso resultaría en una expresión regular absurda que en realidad no coincidiría con el nombre del usuario. @@ -606,20 +606,22 @@ Pero ¿qué pasa si el nombre es `"dea+hl[]rd"` porque nuestro usuario es un ado Para solucionar esto, podemos agregar barras invertidas antes de cualquier carácter que tenga un significado especial. ``` -let name = "dea+hl[]rd"; -let escaped = name.replace(/[\\[.+*?(){|^$]/g, "\\$&"); -let regexp = new RegExp("(^|\\s)" + escaped + "($|\\s)", +let nombre = "dea+hl[]rd"; +let escapado = nombre.replace(/[\\[.+*?(){|^$]/g, "\\$&"); +let regexp = new RegExp("(^|\\s)" + escapado + "($|\\s)", "gi"); -let text = "Este chico dea+hl[]rd es súper molesto."; -console.log(regexp.test(text)); +let texto = "Este chico dea+hl[]rd es súper pesado."; +console.log(regexp.test(texto)); // → true ``` +{{note "**N. del T.:** Recordemos que, dentro de `[...]`, casi los caracteres especiales pierden su significado, exceptuando en este caso la barra invertida, `\\`, que debe escaparse."}} + ## El método search {{index ["expresión regular", "métodos"], "método indexOf", "método search"}} -El método `indexOf` en las cadenas no puede ser llamado con una expresión regular. Pero hay otro método, `search`, que espera una expresión regular. Al igual que `indexOf`, devuelve el primer índice en el que se encontró la expresión, o -1 cuando no se encontró. +Con una expresión regular no podemos usar el método `indexOf` de las cadenas. Pero hay otro método, `search`, que espera una expresión regular. Al igual que `indexOf`, devuelve el primer índice en el que se encuentra la expresión, o -1 cuando no se encuentra. ``` console.log(" palabra".search(/\S/)); @@ -628,13 +630,13 @@ console.log(" ".search(/\S/)); // → -1 ``` -Desafortunadamente, no hay una forma de indicar que la coincidencia debería comenzar en un offset dado (como se puede hacer con el segundo argumento de `indexOf`), lo cual a menudo sería útil. +Desafortunadamente, no hay una forma de indicar que la coincidencia debería comenzar en un offset dado (como se puede hacer con el segundo argumento de `indexOf`), lo que podría ser bastante útil a veces. ## La propiedad lastIndex {{index "método exec", "expresión regular"}} -El método `exec` de manera similar no proporciona una forma conveniente de comenzar a buscar desde una posición dada en la cadena. Pero sí proporciona una forma *in*conveniente. +De manera parecida, el método `exec` no proporciona una forma conveniente de comenzar a buscar desde una posición dada en la cadena. Pero sí proporciona una forma *in*cómoda de hacerlo. {{index ["expresión regular", coincidencia], coincidencia, "propiedad source", "propiedad lastIndex"}} @@ -642,15 +644,15 @@ Los objetos de expresión regular tienen propiedades. Una de esas propiedades es {{index [interface, "diseño"], "método exec", ["expresión regular", global]}} -Estas circunstancias implican que la expresión regular debe tener la opción global (`g`) o pegajosa (`y`) activada, y la coincidencia debe ocurrir a través del método `exec`. Nuevamente, una solución menos confusa habría sido simplemente permitir que se pase un argumento adicional a `exec`, pero la confusión es una característica esencial de la interfaz de expresiones regulares de JavaScript. +Estas circunstancias son que la expresión regular debe tener la opción global (`g`) o pegajosa (`y`) activadas, y la coincidencia debe ocurrir a través del método `exec`. De nuevo, una solución menos confusa habría sido simplemente permitir que se pase un argumento adicional a `exec`, pero la confusión es una característica esencial de la interfaz de expresiones regulares de JavaScript. ``` -let pattern = /y/g; -pattern.lastIndex = 3; -let match = pattern.exec("xyzzy"); -console.log(match.index); +let patrón = /y/g; +patrón.lastIndex = 3; +let coincidencia = patrón.exec("xyzzy"); +console.log(coincidencia.index); // → 4 -console.log(pattern.lastIndex); +console.log(patrón.lastIndex); // → 5 ``` @@ -658,14 +660,14 @@ console.log(pattern.lastIndex); Si la coincidencia tuvo éxito, la llamada a `exec` actualiza automáticamente la propiedad `lastIndex` para que apunte después de la coincidencia. Si no se encontró ninguna coincidencia, `lastIndex` se restablece a cero, que es también el valor que tiene en un objeto de expresión regular recién construido. -La diferencia entre las opciones global y sticky es que, cuando se habilita sticky, la coincidencia solo se producirá si comienza directamente en `lastIndex`, mientras que con global se buscará una posición donde pueda comenzar una coincidencia. +La diferencia entre las opciones global y pegajosa (_sticky_) es que, cuando se habilita la opción pegajosa, la coincidencia solo se produce si comienza directamente en `lastIndex`, mientras que con global se buscará una posición donde pueda comenzar una coincidencia. ``` let global = /abc/g; console.log(global.exec("xyz abc")); // → ["abc"] -let sticky = /abc/y; -console.log(sticky.exec("xyz abc")); +let pegajosa = /abc/y; +console.log(pegajosa.exec("xyz abc")); // → null ``` @@ -674,35 +676,35 @@ console.log(sticky.exec("xyz abc")); Al usar un valor de expresión regular compartido para múltiples llamadas a `exec`, estas actualizaciones automáticas a la propiedad `lastIndex` pueden causar problemas. Es posible que tu expresión regular comience accidentalmente en un índice que quedó de una llamada previa. ``` -let digit = /\d/g; -console.log(digit.exec("aquí está: 1")); +let dígito = /\d/g; +console.log(dígito.exec("aquí está: 1")); // → ["1"] -console.log(digit.exec("ahora: 1")); +console.log(dígito.exec("ahora: 1")); // → null ``` {{index ["expresión regular", global], "método match"}} -Otro efecto interesante de la opción global es que cambia la forma en que funciona el método `match` en las cadenas. Cuando se llama con una expresión global, en lugar de devolver una matriz similar a la devuelta por `exec`, `match` encontrará _todas_ las coincidencias del patrón en la cadena y devolverá una matriz que contiene las cadenas coincidentes. +Otro efecto interesante de la opción global es que cambia la forma en que funciona el método `match` en las cadenas. Cuando se llama con una expresión global, en lugar de devolver un array como el que devuelve `exec`, `match` encontrará _todas_ las coincidencias del patrón en la cadena y devolverá un array que contiene las cadenas coincidentes. ``` console.log("Banana".match(/an/g)); // → ["an", "an"] ``` -Así que ten cuidado con las expresiones regulares globales. Los casos en los que son necesarias, como las llamadas a `replace` y los lugares donde quieres usar explícitamente `lastIndex`, son típicamente los únicos lugares donde las deseas utilizar. +Así que ten cuidado con las expresiones regulares globales. Los casos en los que son necesarias, como las llamadas a `replace` y los lugares donde quieres usar explícitamente `lastIndex`, son normalmente los únicos lugares donde querrás utilizarlas. ### Obteniendo todas las coincidencias {{index "propiedad lastIndex", "método exec", bucle}} -Algo común que se hace es encontrar todas las coincidencias de una expresión regular en una cadena. Podemos hacer esto usando el método `matchAll`. +Algo se suele hacer es encontrar todas las coincidencias de una expresión regular en una cadena. Podemos hacer esto usando el método `matchAll`. ``` let input = "Una cadena con 3 números... 42 y 88."; -let matches = input.matchAll(/\d+/g); -for (let match of matches) { - console.log("Encontrado", match[0], "en", match.index); +let coincidencias = input.matchAll(/\d+/g); +for (let coincidencia of coincidencias) { + console.log("Encontrado", coincidencia[0], "en", coincidencia.index); } // → Encontrado 3 en 14 // Encontrado 42 en 33 @@ -711,14 +713,14 @@ for (let match of matches) { {{index ["expresión regular", global]}} -Este método devuelve una matriz de matrices de coincidencias. La expresión regular que se le proporciona _debe_ tener `g` habilitado. +Este método devuelve un array de arrays de coincidencias. La expresión regular que se le proporciona _debe_ tener `g` habilitado. {{id ini}} ## Analizando un archivo INI {{index comentario, "formato de archivo", "ejemplo de enemigos", "archivo INI"}} -Para concluir el capítulo, analizaremos un problema que requiere ((expresiones regulares)). Imagina que estamos escribiendo un programa para recopilar automáticamente información sobre nuestros enemigos desde ((Internet)). (En realidad, no escribiremos ese programa aquí, solo la parte que lee el archivo de ((configuración)). Lo siento.) El archivo de configuración se ve así: +Para concluir el capítulo, analizaremos un problema que requiere ((expresiones regulares)). Imagina que estamos escribiendo un programa para recopilar automáticamente información sobre nuestros enemigos desde ((Internet)) (en realidad, no escribiremos ese programa aquí, solo la parte que lee el archivo de ((configuración)), lo siento). El archivo de configuración tiene esta pinta: ```{lang: "null"} motorbusqueda=https://duckduckgo.com/?q=$1 @@ -727,13 +729,13 @@ rencor=9.7 ; comentarios precedidos por un punto y coma... ; cada sección se refiere a un enemigo individual [larry] -fullname=Larry Doe -type=matón de jardín de infantes +nombrecompleto=Larry Doe +tipo=matón de jardín de infancia website=http://www.geocities.com/CapeCanaveral/11451 [davaeorn] -fullname=Davaeorn -type=mago malvado +nombrecompleto=Davaeorn +tipo=mago malvado outputdir=/home/marijn/enemies/davaeorn ``` @@ -745,7 +747,7 @@ Las reglas exactas para este formato (que es un formato ampliamente utilizado, g - Las líneas envueltas en `[` y `]` inician una nueva ((sección)). -- Las líneas que contienen un identificador alfanumérico seguido de un caracter `=` agregan una configuración a la sección actual. +- Las líneas que contienen un identificador alfanumérico seguido de un carácter `=` agregan una configuración a la sección actual. - Cualquier otra cosa es inválida. @@ -756,42 +758,43 @@ Nuestra tarea es convertir una cadena como esta en un objeto cuyas propiedades c Dado que el formato debe procesarse ((línea)) por línea, dividir el archivo en líneas separadas es un buen comienzo. Vimos el método `split` en el [Capítulo ?](data#split). Sin embargo, algunos sistemas operativos utilizan no solo un carácter de nueva línea para separar líneas sino un carácter de retorno de carro seguido de una nueva línea (`"\r\n"`). Dado que el método `split` también permite una expresión regular como argumento, podemos usar una expresión regular como `/\r?\n/` para dividir de una manera que permita tanto `"\n"` como `"\r\n"` entre líneas. ```{startCode: true} -function parseINI(string) { +function procesaINI(cadena) { // Comenzar con un objeto para contener los campos de nivel superior - let result = {}; - let section = result; - for (let line of string.split(/\r?\n/)) { - let match; - if (match = line.match(/^(\w+)=(.*)$/)) { - section[match[1]] = match[2]; - } else if (match = line.match(/^\[(.*)\]$/)) { - section = result[match[1]] = {}; - } else if (!/^\s*(;|$)/.test(line)) { - throw new Error("La línea '" + line + "' no es válida."); + let resultado = {}; + let sección = resultado; + for (let línea of cadena.split(/\r?\n/)) { + let coincidencia; + if (coincidencia = línea.match(/^(\w+)=(.*)$/)) { + sección[coincidencia[1]] = coincidencia[2]; + } else if (coincidencia = línea.match(/^\[(.*)\]$/)) { + sección = resultado[coincidencia[1]] = {}; + } else if (!/^\s*(;|$)/.test(línea)) { + throw new Error("La línea '" + línea + "' no es válida."); } }; - return result; + return resultado; } -console.log(parseINI(` -name=Vasilis -[address] -city=Tessaloniki`)); -// → {name: "Vasilis", address: {city: "Tessaloniki"}} +console.log(procesaINI(` +nombre=Vasilis +[dirección] +ciudad=Tessaloniki`)); +// → {nombre: "Vasilis", dirección: {ciudad: "Tessaloniki"}} ``` {{index "función parseINI", "análisis"}} El código recorre las líneas del archivo y construye un objeto. Las propiedades en la parte superior se almacenan directamente en ese objeto, mientras que las propiedades encontradas en secciones se almacenan en un objeto de sección separado. El enlace `section` apunta al objeto para la sección actual. -Hay dos tipos de líneas significativas: encabezados de sección o líneas de propiedades. Cuando una línea es una propiedad regular, se almacena en la sección actual. Cuando es un encabezado de sección, se crea un nuevo objeto de sección y `section` se establece para apuntar a él. +Hay dos tipos de líneas significativas: encabezados de sección o líneas de propiedades. Cuando una línea es una propiedad normal, se almacena en la sección actual. Cuando es un encabezado de sección, se crea un nuevo objeto de sección y se hace que `section` apunte a él. {{index "carácter indicador", "signo de dólar", "límite"}} -Observa el uso recurrente de `^` y `$` para asegurarse de que la expresión coincida con toda la línea, no solo parte de ella. Dejarlos fuera resulta en un código que funciona en su mayor parte pero se comporta de manera extraña para algunas entradas, lo que puede ser un error difícil de rastrear. +Observa el uso recurrente de `^` y `$` para asegurarse de que la expresión coincida con toda la línea, no solo parte de ella. No usarlos resultaría en un código que funciona en su mayor parte pero se comporta de manera extraña para algunas entradas, lo que podría ser un error difícil de rastrear. {{index "instrucción if", "asignación", ["operador =", "como expresión"]}} -```El patrón `if (match = string.match(...))` hace uso del hecho de que el valor de una expresión de ((asignación)) (`=`) es el valor asignado. A menudo no estás seguro de que tu llamada a `match` tendrá éxito, por lo que solo puedes acceder al objeto resultante dentro de una declaración `if` que comprueba esto. Para no romper la agradable cadena de formas de `else if`, asignamos el resultado de la coincidencia a un enlace y usamos inmediatamente esa asignación como la prueba para la declaración `if`. + +El patrón `if (coincidencia = string.match(...))` hace uso del hecho de que el valor de una expresión de ((asignación)) (`=`) es el valor asignado. A menudo no estás seguro de que tu llamada a `match` tendrá éxito, por lo que solo puedes acceder al objeto resultante dentro de una declaración `if` que comprueba esto. Para no romper la agradable cadena de formularios `else if`, asignamos el resultado de la coincidencia a una asociación y usamos inmediatamente esa asignación como comprobación para la declaración `if`. {{index ["paréntesis", "en expresiones regulares"]}} @@ -799,7 +802,7 @@ Si una línea no es un encabezado de sección o una propiedad, la función verif ## Unidades de código y caracteres -Otro error de diseño que se ha estandarizado en las expresiones regulares de JavaScript es que, por defecto, operadores como `.` o `?` trabajan en unidades de código, como se discute en el [Capítulo ?](higher_order#code_units), no en caracteres reales. Esto significa que los caracteres que están compuestos por dos unidades de código se comportan de manera extraña. +Otro error de diseño que se ha estandarizado en las expresiones regulares de JavaScript es que, por defecto, operadores como `.` o `?` trabajan en unidades de código, como se discute en el [Capítulo ?](higher_order#code_units), y no en caracteres reales. Esto significa que los caracteres que están compuestos por dos unidades de código se comportan de manera extraña. ``` console.log(/🍎{3}/.test("🍎🍎🍎")); @@ -832,7 +835,7 @@ Las expresiones regulares son objetos que representan patrones en cadenas. Utili | `/[^abc]/` | Cualquier carácter _que no esté_ en un conjunto de caracteres | `/[0-9]/` | Cualquier carácter en un rango de caracteres | `/x+/` | Una o más ocurrencias del patrón `x` -| `/x+?/` | Una o más ocurrencias, perezoso +| `/x+?/` | Una o más ocurrencias, no avaricioso | `/x*/` | Cero o más ocurrencias | `/x?/` | Cero o una ocurrencia | `/x{2,4}/` | Dos a cuatro ocurrencias @@ -845,11 +848,11 @@ Las expresiones regulares son objetos que representan patrones en cadenas. Utili | `/\p{L}/u` | Cualquier carácter de letra | `/^/` | Inicio de entrada | `/$/` | Fin de entrada -| `/(?=a)/` | Una prueba de vistazo hacia adelante +| `/(?=a)/` | Una prueba de anticipación -Una expresión regular tiene un método `test` para comprobar si una cadena dada coincide con ella. También tiene un método `exec` que, cuando se encuentra una coincidencia, devuelve un array que contiene todos los grupos coincidentes. Dicho array tiene una propiedad `index` que indica dónde empezó la coincidencia.Las cadenas tienen un método `match` para compararlas con una expresión regular y un método `search` para buscar una, devolviendo solo la posición de inicio de la coincidencia. Su método `replace` puede reemplazar coincidencias de un patrón con una cadena o función de reemplazo. +Una expresión regular tiene un método `test` para comprobar si una cadena dada coincide con ella. También tiene un método `exec` que, cuando se encuentra una coincidencia, devuelve un array que contiene todos los grupos coincidentes. Dicho array tiene una propiedad `index` que indica dónde empezó la coincidencia. Las cadenas tienen un método `match` para compararlas con una expresión regular y un método `search` para buscar una, devolviendo solo la posición de inicio de la coincidencia. Su método `replace` puede reemplazar coincidencias de un patrón con una cadena o función de reemplazo. -Las expresiones regulares pueden tener opciones, que se escriben después de la barra de cierre. La opción `i` hace que la coincidencia no distinga entre mayúsculas y minúsculas. La opción `g` hace que la expresión sea _global_, lo que, entre otras cosas, hace que el método `replace` reemplace todas las instancias en lugar de solo la primera. La opción `y` la hace persistente, lo que significa que no buscará por delante ni omitirá parte de la cadena al buscar una coincidencia. La opción `u` activa el modo Unicode, que habilita la sintaxis `\p` y soluciona varios problemas en torno al manejo de caracteres que ocupan dos unidades de código. +Las expresiones regulares pueden tener opciones, que se escriben después de la barra de cierre. La opción `i` hace que la coincidencia no distinga entre mayúsculas y minúsculas. La opción `g` hace que la expresión sea _global_, lo que, entre otras cosas, hace que el método `replace` reemplace todas las instancias en lugar de solo la primera. La opción `y` la hace "pegajosa", lo que significa que no buscará por delante ni omitirá parte de la cadena al buscar una coincidencia. La opción `u` activa el modo Unicode, que habilita la sintaxis `\p` y soluciona varios problemas en torno al manejo de caracteres que ocupan dos unidades de código. Las expresiones regulares son una ((herramienta)) afilada con un mango incómodo. Simplifican enormemente algunas tareas, pero pueden volverse rápidamente ingobernables cuando se aplican a problemas complejos. Parte de saber cómo usarlas es resistir la tentación de intentar forzar cosas que no pueden expresarse de forma clara en ellas. @@ -857,7 +860,7 @@ Las expresiones regulares son una ((herramienta)) afilada con un mango incómodo {{index debugging, bug}} -Es casi inevitable que, al trabajar en estos ejercicios, te sientas confundido y frustrado por el comportamiento inexplicable de algunas expresiones regulares. A veces ayuda introducir tu expresión en una herramienta en línea como [_debuggex.com_](https://www.debuggex.com/) para ver si su visualización corresponde a lo que pretendías y para ((experimentar)) con la forma en que responde a diferentes cadenas de entrada. +Es casi inevitable que, al trabajar en estos ejercicios, te sientas confundido y frustrado por el comportamiento inexplicable de algunas expresiones regulares. A veces ayuda introducir tu expresión en una herramienta en línea como [_debuggex.com_](https://www.debuggex.com/) para ver si su visualización corresponde con lo que pretendías y para ((experimentar)) con la forma en que responde a diferentes cadenas de entrada. ### Regexp golf @@ -961,7 +964,7 @@ hint}} {{index sign, "fractional number", [syntax, number], minus, "plus character", exponent, "scientific notation", "period character"}} -Escribe una expresión que coincida solo con los números al estilo de JavaScript. Debe admitir un signo menos _o_ más opcional delante del número, el punto decimal y la notación de exponente—`5e-3` o `1E10`—de nuevo con un signo opcional delante del exponente. También ten en cuenta que no es necesario que haya dígitos delante o después del punto, pero el número no puede ser solo un punto. Es decir, `.5` y `5.` son números de JavaScript válidos, pero un punto solitario _no_ lo es. +Escribe una expresión que coincida solo con los números al estilo de JavaScript. Debe admitir un signo menos _o_ más opcional delante del número, el punto decimal y la notación de exponente —`5e-3` o `1E10`— de nuevo con un signo opcional delante del exponente. También ten en cuenta que no es necesario que haya dígitos delante o después del punto, pero el número no puede ser solo un punto. Es decir, `.5` y `5.` son números de JavaScript válidos, pero un punto solitario _no_ lo es. {{if interactive ```markdown @@ -991,7 +994,7 @@ if}} Primero, no olvides la barra invertida delante del punto. -Para hacer coincidir el ((signo)) opcional delante del ((número)), así como delante del ((exponente)), se puede hacer con `[+\-]?` o `(\+|-|)` (más, menos, o nada). +Si queremos hacer coincidir el ((signo)) opcional delante del ((número)), así como delante del ((exponente)), esto se puede hacer con `[+\-]?` o `(\+|-|)` (más, menos, o nada). {{index "carácter de tubería"}} diff --git a/html/02_program_structure.html b/html/02_program_structure.html index f1dacd18..cdb40c34 100644 --- a/html/02_program_structure.html +++ b/html/02_program_structure.html @@ -99,7 +99,7 @@

Nombres de enlaces

-

Los nombres de asociaciones o enlaces pueden ser cualquier secuencia de una o más letras. Podemos incluir dígitos como parte del nombre de un enlace —catch22 es un nombre válido, por ejemplo—, siempre y cuando el nombre no empiece por uno de ellos. Un nombre de enlace puede incluir signos de dólar ($) o subrayados (_), pero ningún otro caracter especial o signo de puntuación.

+

Los nombres de asociaciones o enlaces pueden ser cualquier secuencia de una o más letras. Podemos incluir dígitos como parte del nombre de un enlace —catch22 es un nombre válido, por ejemplo—, siempre y cuando el nombre no empiece por uno de ellos. Un nombre de enlace puede incluir signos de dólar ($) o subrayados (_), pero ningún otro carácter especial o signo de puntuación.

Cualquier palabra con un significado especial, como let, es una palabra clave, y no puede ser usada como nombre de una asociación. También hay una serie de palabras que están “reservadas para su uso” en futuras versiones de JavaScript, las cuales tampoco se pueden usar como nombres de asociaciones. La lista completa de palabras clave y palabras reservadas es bastante larga:

diff --git a/html/03_functions.html b/html/03_functions.html index 0bf161af..70cf061f 100644 --- a/html/03_functions.html +++ b/html/03_functions.html @@ -154,7 +154,7 @@

Funciones flecha

-

Hay una tercera notación para funciones que tiene un aspecto muy diferente a las otras. En lugar de la palabra clave function, utiliza una flecha (=>) compuesta por un signo igual y un caracter mayor que (no confundir con el operador mayor o igual, que se escribe >=):

+

Hay una tercera notación para funciones que tiene un aspecto muy diferente a las otras. En lugar de la palabra clave function, utiliza una flecha (=>) compuesta por un signo igual y un carácter mayor que (no confundir con el operador mayor o igual, que se escribe >=):

const redondearA = (n, paso) => {
   let resto = n % paso;
@@ -541,7 +541,7 @@ 

Contando minuciosamente

-

Puedes obtener el N-ésimo caracter, o letra, de una cadena escribiendo [N] después de la cadena (por ejemplo, cadena[2]). El valor resultante será una cadena que contiene solo un carácter (por ejemplo, "b"). El primer carácter tiene la posición 0, lo que hace que el último se encuentre en la posición cadena.length - 1. En otras palabras, una cadena de dos caracteres tiene longitud 2, y sus caracteres tienen posiciones 0 y 1.

+

Puedes obtener el N-ésimo carácter, o letra, de una cadena escribiendo [N] después de la cadena (por ejemplo, cadena[2]). El valor resultante será una cadena que contiene solo un carácter (por ejemplo, "b"). El primer carácter tiene la posición 0, lo que hace que el último se encuentre en la posición cadena.length - 1. En otras palabras, una cadena de dos caracteres tiene longitud 2, y sus caracteres tienen posiciones 0 y 1.

Escribe una función countBs (contarBs) que tome una cadena como único argumento y devuelva un número que indique cuántos caracteres B en mayúscula hay en la cadena.

@@ -556,7 +556,7 @@

Mostrar pistas...
-

Tu función necesita un bucle que mire cada carácter en la cadena. Puede recorrer un índice desde cero hasta uno menos que su longitud (< cadena.length). Si el caracter en la posición actual es el mismo que el que la función está buscando, agrega 1 a una variable contadora. Una vez que el bucle ha terminado, el contador puede ser devuelto.

+

Tu función necesita un bucle que mire cada carácter en la cadena. Puede recorrer un índice desde cero hasta uno menos que su longitud (< cadena.length). Si el carácter en la posición actual es el mismo que el que la función está buscando, agrega 1 a una variable contadora. Una vez que el bucle ha terminado, el contador puede ser devuelto.

Ten cuidado de que todas las asociaciones utilizadas en la función sean locales a la función, declarándolas correctamente con la palabra clave let o const.

diff --git a/html/04_data.html b/html/04_data.html index 7e6c292d..d07583a8 100644 --- a/html/04_data.html +++ b/html/04_data.html @@ -475,7 +475,7 @@

console.log(" de acuerdo \n ".trim()); // → de acuerdo

-

La función rellenarConCeros del capítulo anterior también existe como un método. Se llama padStart y recibe la longitud deseada y el caracter de relleno como argumentos:

+

La función rellenarConCeros del capítulo anterior también existe como un método. Se llama padStart y recibe la longitud deseada y el carácter de relleno como argumentos:

console.log(String(6).padStart(3, "0"));
 // → 006
diff --git a/html/05_higher_order.html b/html/05_higher_order.html index 8f85d351..a356ed8f 100644 --- a/html/05_higher_order.html +++ b/html/05_higher_order.html @@ -340,18 +340,18 @@

Pero, ¿cómo obtenemos los códigos de caracteres en una cadena?

-

En el Capítulo 1 mencioné que las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estos se llaman unidades de código. Al principio, se suponía que un código de caracter Unicode cabía dentro de tal unidad (lo que te da algo más de 65000 caracteres). Cuando quedó claro que eso no iba a ser suficiente, mucha gente se mostró reacia a la necesidad de usar más memoria por caracter. Para abordar estas preocupaciones, se inventó UTF-16, el formato que usan las cadenas de JavaScript. Describe la mayoría de los caracteres comunes usando una única unidad de código de 16 bits, pero usa un par de dos unidades de dicho tipo para otros.

+

En el Capítulo 1 mencioné que las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estos se llaman unidades de código. Al principio, se suponía que un código de carácter Unicode cabía dentro de tal unidad (lo que te da algo más de 65000 caracteres). Cuando quedó claro que eso no iba a ser suficiente, mucha gente se mostró reacia a la necesidad de usar más memoria por carácter. Para abordar estas preocupaciones, se inventó UTF-16, el formato que usan las cadenas de JavaScript. Describe la mayoría de los caracteres comunes usando una única unidad de código de 16 bits, pero usa un par de dos unidades de dicho tipo para otros.

UTF-16 generalmente se considera una mala idea hoy en día. Parece casi diseñado intencionalmente para provocar errores. Es fácil escribir programas que asuman que las unidades de código y los caracteres son lo mismo. Y si tu lenguaje no utiliza caracteres de dos unidades, eso parecerá funcionar perfectamente. Pero tan pronto como alguien intente usar dicho programa con algunos caracteres menos comunes como los chinos, fallará. Por suerte, con la llegada de los emoji, todo el mundo ha comenzado a usar caracteres de dos unidades, y tratar con tales problemas se está haciendo más llevadero.

Lamentablemente, las operaciones obvias en las cadenas de JavaScript, como obtener su longitud a través de la propiedad length y acceder a su contenido usando corchetes cuadrados, tratan solo con unidades de código.

-
// Dos caracteres emoji, caballo y zapato
+
// Dos caracteres emoji, caballo y zapato
 let caballoZapato = "🐴👟";
 console.log(caballoZapato.length);
 // → 4
 console.log(caballoZapato[0]);
-// → (Mitad de caracter inválida)
+// → (Mitad de carácter inválida)
 console.log(caballoZapato.charCodeAt(0));
 // → 55357 (Código de la mitad de caracter)
 console.log(caballoZapato.codePointAt(0));
@@ -361,14 +361,14 @@ 

En el capítulo anterior, mencioné que un bucle for/of también se puede usar en cadenas. Al igual que codePointAt, este tipo de bucle se introdujo en un momento en que la gente era muy consciente de los problemas con UTF-16. Cuando lo usas para recorrer una cadena, te proporciona caracteres reales, no unidades de código:

-
let rosaDragón = "🌹🐉";
-for (let caracter of rosaDragón) {
+
let rosaDragón = "🌹🐉";
+for (let carácter of rosaDragón) {
   console.log(caracter);
 }
 // → 🌹
 // → 🐉
-

Si tienes un caracter (que será una cadena de una o dos unidades de código), puedes usar codePointAt(0) para obtener su código.

+

Si tienes un carácter (que será una cadena de una o dos unidades de código), puedes usar codePointAt(0) para obtener su código.

Reconociendo texto

@@ -397,9 +397,9 @@

Usando contarPor, podemos escribir la función que nos dice qué sistemas de escritura se utilizan en un fragmento de texto:

-
function sistemasTexto(texto) {
-  let sistemas = contarPor(texto, caracter => {
-    let sistema = sistemaCaracteres(caracter.codePointAt(0));
+
function sistemasTexto(texto) {
+  let sistemas = contarPor(texto, carácter => {
+    let sistema = sistemaCaracteres(carácter.codePointAt(0));
     return sistema ? sistema.name : "ninguno";
   }).filter(({nombre}) => nombre != "ninguno");
 
diff --git a/html/09_regexp.html b/html/09_regexp.html
index cfa8cac3..4120a89d 100644
--- a/html/09_regexp.html
+++ b/html/09_regexp.html
@@ -14,7 +14,7 @@ 

Expresiones regulares

-

Algunas personas, cuando se enfrentan a un problema, piensan '¡Ya sé, usaré expresiones regulares!’ Ahora tienen dos problemas.

+

Hay gente que, cuando se enfrenta a un problema, piensa '¡Ya sé, usaré expresiones regulares!’ Ahora tienen dos problemas.

Jamie Zawinski
@@ -24,19 +24,19 @@

Expresiones regulares

Cuando cortas en contra de la veta de la madera, se necesita mucha fuerza. Cuando programas en contra de la veta del problema, se necesita mucho código.

-
Master Yuan-Ma, El Libro de la Programación
+
Master Yuan-Ma, The Book of Programming
Ilustración de un sistema de ferrocarril que representa la estructura sintáctica de las expresiones regulares
-

Las herramientas y técnicas de programación sobreviven y se propagan de manera caótica y evolutiva. No siempre ganan las mejores o brillantes, sino aquellas que funcionan lo suficientemente bien dentro del nicho correcto o que se integran con otra pieza exitosa de tecnología.

+

Las herramientas y técnicas de programación sobreviven y se propagan de manera caótica y evolutiva. No siempre ganan las mejores o más brillantes, sino aquellas que funcionan lo suficientemente bien dentro del nicho correcto o que, por casualidad, están integradas en algún componente tecnológico exitoso.

-

En este capítulo, discutiré una de esas herramientas, expresiones regulares. Las expresiones regulares son una forma de describir patrónes en datos de cadena. Forman un pequeño lenguaje separado que es parte de JavaScript y muchos otros lenguajes y sistemas.

+

En este capítulo, discutiré una de esas herramientas: las expresiones regulares. Las expresiones regulares son una forma de describir patrónes en datos de tipo cadena. Forman un pequeño lenguaje separado que es parte de JavaScript y muchos otros lenguajes y sistemas.

-

Las expresiones regulares son tanto terriblemente incómodas como extremadamente útiles. Su sintaxis es críptica y la interfaz de programación que JavaScript proporciona para ellas es torpe. Pero son una herramienta poderosa para inspeccionar y procesar cadenas. Comprender adecuadamente las expresiones regulares te hará un programador más efectivo.

+

Las expresiones regulares son tanto terriblemente incómodas como extremadamente útiles. Su sintaxis es críptica y la interfaz de programación que JavaScript proporciona para ellas es torpe. Pero son una herramienta poderosa para inspeccionar y procesar cadenas. Comprender adecuadamente las expresiones regulares hará de ti un programador más efectivo.

Creando una expresión regular

-

Una expresión regular es un tipo de objeto. Puede ser construido con el constructor RegExp o escrito como un valor literal al encerrar un patrón entre caracteres de barra diagonal (/).

+

Una expresión regular es un tipo de objeto. Se puede construir con el constructor RegExp o escrito como un valor literal al encerrar un patrón entre caracteres de barra hacia adelante (/).

let re1 = new RegExp("abc");
 let re2 = /abc/;
@@ -45,11 +45,11 @@

Cuando se utiliza el constructor RegExp, el patrón se escribe como una cadena normal, por lo que se aplican las reglas habituales para las barras invertidas.

-

La segunda notación, donde el patrón aparece entre caracteres de barra diagonal, trata las barras invertidas de manera un poco diferente. Primero, dado que una barra diagonal termina el patrón, debemos poner una barra invertida antes de cualquier barra diagonal que queramos que sea parte del patrón. Además, las barras invertidas que no forman parte de códigos de caracteres especiales (como \n) serán preservadas, en lugar de ser ignoradas como lo son en las cadenas, y cambian el significado del patrón. Algunos caracteres, como signos de interrogación y signos de más, tienen significados especiales en las expresiones regulares y deben ser precedidos por una barra invertida si se desea representar el propio carácter.

+

La segunda notación, donde el patrón aparece entre caracteres de barra diagonal, trata las barras invertidas de manera un poco diferente. Primero, dado que el patrón termina con una barra diagonal, debemos poner una barra invertida antes de cualquier barra diagonal que queramos que sea parte del patrón. Además, las barras invertidas que no forman parte de códigos de caracteres especiales (como \n) serán preservadas, en lugar de ser ignoradas como lo son en las cadenas, y cambian el significado del patrón. Algunos caracteres, como signos de interrogación y signos de suma, tienen significados especiales en las expresiones regulares y deben ser precedidos por una barra invertida si se desea representar el propio carácter.

-
let aPlus = /A\+/;
+
let unMás = /Un\+/;
-

Pruebas de coincidencias

+

Testeo para coincidencias

Los objetos de expresiones regulares tienen varios métodos. El más simple es test. Si le pasas una cadena, devolverá un Booleano indicándote si la cadena contiene una coincidencia con el patrón de la expresión.

@@ -58,13 +58,13 @@

/abc/.test("abxde")); // → false

-

Una expresión regular que consiste solo en caracteres no especiales simplemente representa esa secuencia de caracteres. Si abc aparece en cualquier parte de la cadena contra la cual estamos probando (no solo al principio), test devolverá true.

+

Una expresión regular que consiste solo en caracteres no especiales simplemente representa esa secuencia de caracteres. Si abc aparece en cualquier parte de la cadena contra la cual estamos testeando (no solo al principio), test devolverá true.

Conjuntos de caracteres

Descubrir si una cadena contiene abc también se podría hacer con una llamada a indexOf. Las expresiones regulares son útiles porque nos permiten describir patrones más complicados.

-

Digamos que queremos hacer coincidir cualquier número. En una expresión regular, poner un conjunto de caracteres entre corchetes hace que esa parte de la expresión coincida con cualquiera de los caracteres entre los corchetes.

+

Digamos que queremos recoger cualquier número. En una expresión regular, poner un conjunto de caracteres entre corchetes hace que esa parte de la expresión coincida con cualquiera de los caracteres entre los corchetes.

Ambas expresiones siguientes hacen coincidir todas las cadenas que contienen un dígito:

@@ -73,13 +73,13 @@

/[0-9]/.test("in 1992")); // → true

-

Dentro de corchetes, un guion (-) entre dos caracteres se puede usar para indicar un rango de caracteres, donde el orden es determinado por el número del carácter en el Unicode. Los caracteres del 0 al 9 están uno al lado del otro en este orden (códigos 48 a 57), por lo que [0-9] abarca todos ellos y coincide con cualquier dígito.

+

Dentro de corchetes, se puede usar un guion (-) entre dos caracteres para indicar un rango de caracteres, donde el orden es determinado por el número del carácter en la codificación Unicode. Los caracteres del 0 al 9 están uno al lado del otro en este orden (códigos 48 a 57), por lo que [0-9] abarca todos ellos y coincide con cualquier dígito.

-

Varios grupos comunes de caracteres tienen sus propias abreviaturas incorporadas. Los dígitos son uno de ellos: \d significa lo mismo que [0-9].

+

Algunos grupos comunes de caracteres tienen sus propias abreviaturas incorporadas. Los dígitos son uno de ellos: \d significa lo mismo que [0-9].

- + @@ -111,27 +111,27 @@

Así que podrías hacer coincidir un formato de fecha y hora como 01-30-2003 15:20 con la siguiente expresión:

-
let dateTime = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/;
-console.log(dateTime.test("01-30-2003 15:20"));
+
let fechaYHora = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/;
+console.log(fechaYHora.test("01-30-2003 15:20"));
 // → true
-console.log(dateTime.test("30-ene-2003 15:20"));
+console.log(fechaYHora.test("30-ene-2003 15:20"));
 // → false
-

¡Eso se ve completamente horrible, ¿verdad? La mitad son barras invertidas, produciendo un ruido de fondo que dificulta identificar el patrón expresado. Veremos una versión ligeramente mejorada de esta expresión más adelante.

+

Tiene una pinta terrible, ¿verdad? La mitad son barras invertidas, produciendo un ruido de fondo que dificulta identificar el patrón expresado. Veremos una versión ligeramente mejorada de esta expresión más adelante.

Estos códigos de barra invertida también se pueden usar dentro de corchetes. Por ejemplo, [\d.] significa cualquier dígito o un carácter de punto. Pero el punto en sí, entre corchetes, pierde su significado especial. Lo mismo ocurre con otros caracteres especiales, como +.

Para invertir un conjunto de caracteres, es decir, expresar que deseas hacer coincidir cualquier carácter excepto los que están en el conjunto, puedes escribir un carácter circunflejo (^) después del corchete de apertura.

-
let nonBinary = /[^01]/;
-console.log(nonBinary.test("1100100010100110"));
+
let noBinario = /[^01]/;
+console.log(noBinario.test("1100100010100110"));
 // → false
-console.log(nonBinary.test("0111010112101001"));
+console.log(noBinario.test("0111010112101001"));
 // → true

Caracteres internacionales

-

Debido a la implementación simplista inicial de JavaScript y al hecho de que este enfoque simplista luego se estableció como comportamiento estándar, las expresiones regulares de JavaScript son bastante simples en lo que respecta a los caracteres que no aparecen en el idioma inglés. Por ejemplo, según las expresiones regulares de JavaScript, un “carácter de palabra” es solo uno de los 26 caracteres del alfabeto latino (mayúsculas o minúsculas), dígitos decimales y, por alguna razón, el guion bajo. Cosas como é o β, que definitivamente son caracteres de palabra, no coincidirán con \w (y coincidirán con \W en mayúsculas, la categoría de no palabras).

+

Debido a la inicial implementación simplista de JavaScript y al hecho de que este enfoque simplista luego se estableció como comportamiento estándar, las expresiones regulares de JavaScript son bastante limitadas en lo que respecta a los caracteres que no aparecen en el idioma inglés. Por ejemplo, según las expresiones regulares de JavaScript, un “carácter de palabra” es solo uno de los 26 caracteres del alfabeto latino (mayúsculas o minúsculas), dígitos de la base 10 y, por alguna razón, el guion bajo. Cosas como é o β, que claramente son caracteres de palabra, no coincidirán con \w (y coincidirán con \W en mayúsculas, la categoría de no palabras).

Por un extraño accidente histórico, \s (espacio en blanco) no tiene este problema y coincide con todos los caracteres que el estándar Unicode considera espacios en blanco, incluidos elementos como el espacio sin ruptura y el separador de vocal mongol.

@@ -155,13 +155,13 @@

-

+
\dCualquier carácter dígito
\dCualquier carácter de dígito
\p{Script=Hangul}Cualquier carácter del guion dado (ver Capítulo 5)
\p{Script=Hangul}Cualquier carácter del sistema de escritura dado (ver Capítulo 5)
-

Usar \w para el procesamiento de texto que puede necesitar manejar texto no inglés (o incluso texto en inglés con palabras prestadas como “cliché") es una desventaja, ya que no tratará caracteres como "é" como letras. Aunque tienden a ser un poco más verbosos, los grupos de propiedades \p son más robustos.

+

Usar \w para el procesamiento de texto que puede necesitar manejar texto no inglés (o incluso texto en inglés con palabras prestadas como “cliché") es un riesgo, ya que no tratará caracteres como "é" como letras. Aunque tienden a ser un poco más verbosos, los grupos de propiedades \p son más robustos.

console.log(/\p{L}/u.test("α"));
 // → true
@@ -203,8 +203,8 @@ 

Aquí tienes otra versión del patrón de fecha y hora que permite días, meses y horas de uno o dos dígitos. También es un poco más fácil de entender.

-
let dateTime = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/;
-console.log(dateTime.test("1-30-2003 8:45"));
+
let fechaYHora = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/;
+console.log(fechaYHora.test("1-30-2003 8:45"));
 // → true

También puedes especificar rangos abiertos al utilizar llaves omitiendo el número después de la coma. Así, {5,} significa cinco o más veces.

@@ -213,11 +213,11 @@

Para usar un operador como * o + en más de un elemento a la vez, debes utilizar paréntesis. Una parte de una expresión regular que está encerrada entre paréntesis cuenta como un solo elemento en lo que respecta a los operadores que le siguen.

-
let cartoonCrying = /boo+(hoo+)+/i;
-console.log(cartoonCrying.test("Boohoooohoohooo"));
+
let dibujitoLlorando = /boo+(hoo+)+/i;
+console.log(dibujitoLlorando.test("Boohoooohoohooo"));
 // → true
-

Los primeros y segundos caracteres + aplican solo al segundo o en boo y hoo, respectivamente. El tercer + se aplica a todo el grupo (hoo+), haciendo coincidir una o más secuencias como esa.

+

Los primeros y segundos caracteres + aplican solo a la segunda o en boo y hoo, respectivamente. El tercer + se aplica a todo el grupo (hoo+), haciendo coincidir una o más secuencias como esa.

La i al final de la expresión en el ejemplo hace que esta expresión regular ignore mayúsculas y minúsculas, lo que le permite hacer coincidir la B mayúscula en la cadena de entrada, aunque el patrón en sí está completamente en minúsculas.

@@ -231,38 +231,38 @@

// → 8

-

Un objeto devuelto por exec tiene una propiedad de index que nos dice dónde en la cadena comienza la coincidencia exitosa. Aparte de eso, el objeto parece (y de hecho es) un array de strings, cuyo primer elemento es la cadena que coincidió. En el ejemplo anterior, esta es la secuencia de dígitos que estábamos buscando.

+

Un objeto devuelto por exec tiene una propiedad de index que nos dice dónde en la cadena comienza la coincidencia exitosa. Aparte de eso, el objeto parece (y de hecho es) un array de strings, cuyo primer elemento es la cadena que coincidió. En el ejemplo anterior, esta cadena es la serie de dígitos que estábamos buscando.

Los valores de tipo string tienen un método match que se comporta de manera similar.

console.log("uno dos 100".match(/\d+/));
 // → ["100"]
-

Cuando la expresión regular contiene subexpresiones agrupadas con paréntesis, el texto que coincidió con esos grupos también aparecerá en el array. La coincidencia completa es siempre el primer elemento. El siguiente elemento es la parte coincidente con el primer grupo (el que tiene el paréntesis de apertura primero en la expresión), luego el segundo grupo, y así sucesivamente.

+

Cuando la expresión regular contiene subexpresiones agrupadas con paréntesis, el texto que coincidió con esos grupos también aparecerá en el array. La coincidencia completa es siempre el primer elemento. El siguiente elemento es la parte coincidente con el primer grupo (el que tiene el primer paréntesis de apertura en la expresión), luego el segundo grupo, y así sucesivamente.

let textoEntreComillas = /'([^']*)'/;
 console.log(textoEntreComillas.exec("ella dijo 'hola'"));
 // → ["'hola'", "hola"]
-

Cuando un grupo no termina coincidiendo en absoluto (por ejemplo, cuando está seguido por un signo de pregunta), su posición en el array de salida contendrá undefined. Y cuando un grupo coincide múltiples veces (por ejemplo, cuando está seguido por un +), solo la última coincidencia termina en el array.

+

Cuando un grupo no coincide con nada (por ejemplo, cuando está seguido por un signo de pregunta), su posición en el array de salida contendrá undefined. Y cuando un grupo coincide múltiples veces (por ejemplo, cuando está seguido por un +), solo la última coincidencia termina estando en el array.

-
console.log(/mal(mente)?/.exec("mal"));
+
console.log(/mal(amente)?/.exec("mal"));
 // → ["mal", undefined]
 console.log(/(\d)+/.exec("123"));
 // → ["123", "3"]
-

Si quieres utilizar paréntesis puramente para agrupar, sin que aparezcan en el array de coincidencias, puedes colocar ?: después del paréntesis de apertura.

+

Si quieres utilizar paréntesis solamente para agrupar, sin que aparezcan en el array de coincidencias, puedes colocar ?: después del paréntesis de apertura.

console.log(/(?:na)+/.exec("banana"));
 // → ["nana"]

Los grupos pueden ser útiles para extraer partes de una cadena. Si no solo queremos verificar si una cadena contiene una fecha sino también extraerla y construir un objeto que la represente, podemos envolver paréntesis alrededor de los patrones de dígitos y seleccionar directamente la fecha del resultado de exec.

-

Pero primero haremos un breve desvío, en el que discutiremos la forma incorporada de representar fechas y horas en JavaScript.

+

Pero primero haremos un breve paréntesis, en el que discutiremos la forma de representar fechas y horas en JavaScript.

La clase Date

-

JavaScript tiene una clase estándar para representar fechas—o, más bien, puntos en tiempo. Se llama Date. Si simplemente creas un objeto de fecha usando new, obtendrás la fecha y hora actuales.

+

JavaScript tiene una clase estándar para representar fechas —o, más bien, puntos en tiempo. Se llama Date. Si simplemente creas un objeto de fecha usando new, obtendrás la fecha y hora actuales.

console.log(new Date());
 // → Fri Feb 02 2024 18:03:06 GMT+0100 (CET)
@@ -274,11 +274,11 @@

new Date(2009, 11, 9, 12, 59, 59, 999)); // → Mié Dec 09 2009 12:59:59 GMT+0100 (CET)

-

JavaScript utiliza una convención donde los números de mes empiezan en cero (por lo que diciembre es 11), pero los números de día comienzan en uno. Esto es confuso y tonto. Ten cuidado.

+

JavaScript utiliza una convención donde los números de mes empiezan en cero (por lo que diciembre es 11), pero los números de día comienzan en uno. Esto es confuso y estúpido. Ten cuidado.

Los últimos cuatro argumentos (horas, minutos, segundos y milisegundos) son opcionales y se consideran cero cuando no se proporcionan.

-

Las marcas de tiempo se almacenan como el número de milisegundos desde el comienzo de 1970, en UTC (zona horaria). Esto sigue una convención establecida por “tiempo de Unix”, que fue inventado alrededor de esa época. Puedes usar números negativos para tiempos antes de 1970. El método getTime en un objeto de fecha retorna este número. Es grande, como te puedes imaginar.

+

Las marcas de tiempo (timestamps) se almacenan como el número de milisegundos desde el comienzo de 1970, en la zona horaria UTC. Esto sigue una convención establecida por el “tiempo Unix”, que fue inventado por esa época. Puedes usar números negativos para tiempos antes de 1970. El método getTime en un objeto de fecha retorna este número. Es grande, como te puedes imaginar.

console.log(new Date(2013, 11, 19).getTime());
 // → 1387407600000
@@ -287,49 +287,49 @@ 

Si le proporcionas un único argumento al constructor Date, ese argumento se tratará como un recuento de milisegundos. Puedes obtener el recuento actual de milisegundos creando un nuevo objeto Date y llamando a getTime en él o llamando a la función Date.now.

-

Los objetos de fecha proporcionan métodos como getFullYear, getMonth, getDate, getHours, getMinutes y getSeconds para extraer sus componentes. Además de getFullYear, también existe getYear, que te da el año menos 1900 (98 o 119) y es en su mayoría inútil.

+

Los objetos de fecha proporcionan métodos como getFullYear, getMonth, getDate, getHours, getMinutes y getSeconds para extraer sus componentes. Además de getFullYear, también existe getYear, que te da el año menos 1900 (98 o 119) y es en esencialmente inútil.

Poniendo paréntesis alrededor de las partes de la expresión que nos interesan, podemos crear un objeto de fecha a partir de una cadena.

-
function getDate(string) {
-  let [_, month, day, year] =
-    /(\d{1,2})-(\d{1,2})-(\d{4})/.exec(string);
-  return new Date(year, month - 1, day);
+
function obtenerFecha(cadena) {
+  let [_, mes, día, año] =
+    /(\d{1,2})-(\d{1,2})-(\d{4})/.exec(cadena);
+  return new Date(año, mes - 1, díaday);
 }
-console.log(getDate("1-30-2003"));
+console.log(obtenerFecha("1-30-2003"));
 // → Jue Ene 30 2003 00:00:00 GMT+0100 (CET)
-

La vinculación _ (guion bajo) se ignora y se utiliza solo para omitir el elemento de coincidencia completa en el array devuelto por exec.

+

La asociación _ (guion bajo) se ignora y se utiliza solo para omitir el elemento de coincidencia completa con la expresión regular en el array devuelto por exec.

Límites y anticipación

-

Desafortunadamente, getDate también extraerá felizmente una fecha de la cadena "100-1-30000". Una coincidencia puede ocurrir en cualquier parte de la cadena, por lo que en este caso, simplemente empezará en el segundo carácter y terminará en el antepenúltimo carácter.

+

Desafortunadamente, obtenerFecha también extraerá felizmente una fecha de la cadena "100-1-30000". Una coincidencia puede ocurrir en cualquier parte de la cadena, por lo que en este caso, simplemente empezará en el segundo carácter y terminará en el antepenúltimo carácter.

-

Si queremos asegurar que la coincidencia abarque toda la cadena, podemos agregar los marcadores ^ y $. El circunflejo coincide con el inicio de la cadena de entrada, mientras que el signo de dólar coincide con el final. Por lo tanto, /^\d+$/ coincide con una cadena que consiste completamente de uno o más dígitos, /^!/ coincide con cualquier cadena que comience con un signo de exclamación y /x^/ no coincide con ninguna cadena (no puede haber una x antes del inicio de la cadena).

+

Si queremos asegurar que la coincidencia abarque toda la cadena, podemos agregar los marcadores ^ y $. El circunflejo coincide con el inicio de la cadena de entrada, mientras que el signo de dólar coincide con el final. Por lo tanto, /^\d+$/ coincide con una cadena que consiste completamente de uno o más dígitos, /^!/ coincide con cualquier cadena que comience con un signo de exclamación y /x^/ no coincide con ninguna cadena (es imposible que haya una x antes del inicio de la cadena).

-

También existe un marcador \b, que coincide con los “límites de palabra”, posiciones que tienen un carácter de palabra a un lado y un carácter que no es de palabra al otro. Desafortunadamente, estos utilizan el mismo concepto simplista de caracteres de palabra que \w, por lo que no son muy confiables.

+

También existe un marcador \b, que coincide con los “límites de palabra”, posiciones que tienen un carácter de palabra a un lado y un carácter que no es de palabra al otro. Desafortunadamente, estos utilizan el mismo concepto simplista de caracteres de palabra que \w, por lo que no son muy fiables.

Ten en cuenta que estos marcadores no coinciden con ningún carácter real. Simplemente aseguran que se cumpla una condición determinada en el lugar donde aparecen en el patrón.

-

Las pruebas de mirar adelante hacen algo similar. Proporcionan un patrón y harán que la coincidencia falle si la entrada no coincide con ese patrón, pero en realidad no mueven la posición de la coincidencia hacia adelante. Se escriben entre (?= y ).

+

Las pruebas de anticipación hacen algo similar. Proporcionan un patrón y harán que la coincidencia falle si la entrada no coincide con ese patrón, pero en realidad no mueven la posición de la coincidencia hacia adelante. Se escriben entre (?= y ).

console.log(/a(?=e)/.exec("braeburn"));
 // → ["a"]
 console.log(/a(?! )/.exec("a b"));
 // → null
-

Observa cómo la e en el primer ejemplo es necesaria para coincidir, pero no forma parte de la cadena coincidente. La notación (?! ) expresa un mirar adelante negativo. Esto solo coincide si el patrón entre paréntesis no coincide, lo que hace que el segundo ejemplo solo coincida con caracteres “a” que no tienen un espacio después de ellos.

+

Observa cómo la e en el primer ejemplo es necesaria para coincidir, pero no forma parte de la cadena coincidente. La notación (?! ) expresa una anticipación negativa. Esto solo coincide si el patrón entre paréntesis no coincide, lo que hace que el segundo ejemplo solo coincida con caracteres “a” que no tienen un espacio después de ellos.

Patrones de elección

Digamos que queremos saber si un texto contiene no solo un número, sino un número seguido de una de las palabras pig, cow o chicken, o cualquiera de sus formas en plural.

-

Podríamos escribir tres expresiones regulares y probarlas sucesivamente, pero hay una forma más sencilla. El carácter de barra vertical (|) denota una elección entre el patrón a su izquierda y el patrón a su derecha. Así que puedo decir esto:

+

Podríamos escribir tres expresiones regulares y probarlas sucesivamente, pero hay una forma más sencilla. El carácter de barra vertical (|) denota una elección entre el patrón a su izquierda y el patrón a su derecha. Podemos usarlo en expresiones como est:

-
let animalCount = /\d+ (pig|cow|chicken)s?/;
-console.log(animalCount.test("15 pigs"));
+
let recuentoAnimal = /\d+ (pig|cow|chicken)s?/;
+console.log(recuentoAnimal.test("15 pigs"));
 // → true
-console.log(animalCount.test("15 pugs"));
+console.log(recuentoAnimal.test("15 pugs"));
 // → false

Los paréntesis se pueden utilizar para limitar la parte del patrón a la que se aplica el operador de barra, y puedes colocar varios de estos operadores uno al lado del otro para expresar una elección entre más de dos alternativas.

@@ -338,17 +338,17 @@

Conceptualmente, cuando utilizas exec o test, el motor de expresiones regulares busca una coincidencia en tu cadena tratando de ajustar primero la expresión desde el comienzo de la cadena, luego desde el segundo carácter, y así sucesivamente, hasta que encuentra una coincidencia o llega al final de la cadena. Devolverá la primera coincidencia que encuentre o fracasará en encontrar cualquier coincidencia.

-

Para hacer la coincidencia real, el motor trata a una expresión regular algo así como un diagrama de flujo. Este es el diagrama para la expresión de ganado en el ejemplo anterior:

Diagrama de ferrocarril que primero pasa por un recuadro etiquetado 'dígito', que tiene un bucle que regresa desde después de él a antes de él, y luego un recuadro para un carácter de espacio. Después de eso, el ferrocarril se divide en tres, pasando por cuadros para 'pig', 'cow' y 'chicken'. Después de estos, se reúne de nuevo y pasa por un cuadro etiquetado 's', que, al ser opcional, también tiene un ferrocarril que lo pasa por alto. Finalmente, la línea llega al estado de aceptación.
+

Para hacer lo que es la coincidencia, el motor trata las expresiones regulares de algún modo como un diagrama de flujo. Este es el diagrama para la expresión de ganado en el ejemplo anterior:

Diagrama de ferrocarril que primero pasa por un recuadro etiquetado como 'dígito', que tiene un bucle que regresa desde después de él a antes de él, y luego un recuadro para un carácter de espacio. Después de eso, el diagrama se divide en tres, pasando por cuadros para 'pig', 'cow' y 'chicken'. Después de estos, se reúne de nuevo y pasa por un cuadro etiquetado 's', que, al ser opcional, también tiene un camino que lo pasa por alto. Finalmente, la línea llega al estado de aceptación.
-

Nuestra expresión coincide si podemos encontrar un camino desde el lado izquierdo del diagrama hasta el lado derecho. Mantenemos una posición actual en la cadena, y cada vez que avanzamos a través de un recuadro, verificamos que la parte de la cadena después de nuestra posición actual coincida con ese recuadro.

+

Nuestra expresión coincide cuando podemos encontrar un camino desde el lado izquierdo del diagrama hasta el lado derecho. Mantenemos una posición actual en la cadena, y cada vez que avanzamos a través de un recuadro en el diagrama, verificamos que la parte de la cadena después de nuestra posición actual coincida con ese recuadro.

Retroceso

-

La expresión regular /^([01]+b|[\da-f]+h|\d+)$/ coincide ya sea con un número binario seguido de una b, un número hexadecimal (es decir, base 16, con las letras a a f representando los dígitos del 10 al 15) seguido de un h, o un número decimal regular sin un carácter de sufijo. Este es el diagrama correspondiente:

Diagrama de ferrocarril para la expresión regular '^([01]+b|\d+|[\da-f]+h)$'
+

La expresión regular /^([01]+b|[\da-f]+h|\d+)$/ coincide ya sea con un número binario seguido de una b, un número hexadecimal (es decir, base 16, con las letras a a f representando los dígitos del 10 al 15) seguido de un h, o un número decimal normal sin un carácter de sufijo. Este es el diagrama correspondiente:

Diagrama de ferrocarril para la expresión regular '^([01]+b|\d+|[\da-f]+h)$'
-

Al coincidir con esta expresión, a menudo sucede que se ingresa por la rama superior (binaria) aunque la entrada en realidad no contenga un número binario. Al coincidir con la cadena "103", por ejemplo, solo se aclara en el 3 que estamos en la rama incorrecta. La cadena coincide con la expresión, simplemente no con la rama en la que nos encontramos actualmente.

+

Al coincidir con esta expresión, a menudo sucede que se ingresa por la rama superior (binaria) aunque la entrada en realidad no contenga un número binario. Al coincidir con la cadena "103", por ejemplo, solo se aclara en el 3 que estamos en la rama incorrecta. La cadena coincide con la expresión, solo que no con la rama en la que nos encontramos actualmente.

-

Entonces, el coincidente retrocede. Al ingresar a una rama, recuerda su posición actual (en este caso, al principio de la cadena, justo después del primer cuadro de límite en el diagrama) para poder retroceder y probar otra rama si la actual no funciona. Para la cadena "103", después de encontrar el carácter 3, intentará la rama para los números hexadecimales, lo cual también falla porque no hay un h después del número. Entonces intenta la rama para los números decimales. Esta encaja, y se informa una coincidencia después de todo.

+

Entonces, el coincidente retrocede (o hace backtracking). Al ingresar a una rama, recuerda su posición actual (en este caso, al principio de la cadena, justo después del primer cuadro de límite en el diagrama) para poder retroceder y probar otra rama si la actual no funciona. Para la cadena "103", después de encontrar el carácter 3, intentará la rama para los números hexadecimales, lo cual también falla porque no hay un h después del número. Entonces intenta la rama para los números decimales. Esta encaja, y se informa una coincidencia después de todo.

El coincidente se detiene tan pronto como encuentra una coincidencia completa. Esto significa que si varias ramas podrían coincidir potencialmente con una cadena, solo se usa la primera (ordenada por dónde aparecen las ramas en la expresión regular).

@@ -356,7 +356,7 @@

Es posible escribir expresiones regulares que realizarán mucho retroceso. Este problema ocurre cuando un patrón puede coincidir con una parte de la entrada de muchas formas diferentes. Por ejemplo, si nos confundimos al escribir una expresión regular para los números binarios, podríamos escribir accidentalmente algo como /([01]+)+b/.

Diagrama de ferrocarril para la expresión regular '([01]+)+b'
-

Si intenta hacer coincidir una serie larga de ceros y unos sin un caracter b al final, el analizador primero pasa por el bucle interno hasta que se queda sin dígitos. Luego se da cuenta de que no hay b, por lo que retrocede una posición, pasa por el bucle externo una vez y vuelve a darse por vencido, intentando retroceder nuevamente fuera del bucle interno. Continuará intentando todas las rutas posibles a través de estos dos bucles. Esto significa que la cantidad de trabajo se duplica con cada carácter adicional. Incluso con apenas unas pocas docenas de caracteres, la coincidencia resultante tomará prácticamente para siempre.

+

Si intenta hacer coincidir una serie larga de ceros y unos sin un carácter b al final, el analizador primero pasa por el bucle interno hasta que se queda sin dígitos. Luego se da cuenta de que no hay b, por lo que retrocede una posición, pasa por el bucle externo una vez y vuelve a darse por vencido, intentando retroceder nuevamente fuera del bucle interno. Continuará intentando todas las rutas posibles a través de estos dos bucles. Esto significa que la cantidad de trabajo se duplica con cada carácter adicional. Incluso con apenas unas pocas docenas de caracteres, la coincidencia resultante llevará prácticamente una eternidad.

El método replace

@@ -387,8 +387,8 @@

Aquí tienes un ejemplo:

-
let stock = "1 limón, 2 repollos y 101 huevos";
-function menosUno(match, cantidad, unidad) {
+
let stock = "1 limón, 2 repollos y 101 huevos";
+function menosUno(coincidencia, cantidad, unidad) {
   cantidad = Number(cantidad) - 1;
   if (cantidad == 1) { // solo queda uno, se elimina la 's'
     unidad = unidad.slice(0, unidad.length - 1);
@@ -402,20 +402,20 @@ 

Esta función toma una cadena, encuentra todas las ocurrencias de un número seguido de una palabra alfanumérica, y devuelve una cadena que tiene una cantidad menos de cada una de esas ocurrencias.

-

El grupo (\d+) termina siendo el argumento amount de la función, y el grupo (\p{L}+) se asigna a unit. La función convierte amount a un número, lo cual siempre funciona ya que coincide con \d+, y realiza algunos ajustes en caso de que solo quede uno o ninguno.

+

El grupo (\d+) termina siendo el argumento cantidad de la función, y el grupo (\p{L}+) se asigna a unidad. La función convierte cantidad a un número, lo cual siempre funciona ya que coincide con \d+, y realiza algunos ajustes en caso de que solo quede uno o ninguno.

Avaricia

-

Es posible usar replace para escribir una función que elimine todos los comentarios de un fragmento de código JavaScript. Aquí tienes un primer intento:

+

Podemos usar replace para escribir una función que elimine todos los comentarios de un fragmento de código JavaScript. Aquí tienes un primer intento:

-
function stripComments(code) {
+
function quitarComentarios(code) {
   return code.replace(/\/\/.*|\/\*[^]*\*\//g, "");
 }
-console.log(stripComments("1 + /* 2 */3"));
+console.log(quitarComentarios("1 + /* 2 */3"));
 // → 1 + 3
-console.log(stripComments("x = 10;// ¡diez!"));
+console.log(quitarComentarios("x = 10;// ¡diez!"));
 // → x = 10;
-console.log(stripComments("1 /* a */+/* b */ 1"));
+console.log(quitarComentarios("1 /* a */+/* b */ 1"));
 // → 1  1

La parte antes del operador or coincide con dos caracteres de barra seguidos por cualquier cantidad de caracteres que no sean de nueva línea. La parte de comentarios de varias líneas es más compleja. Utilizamos [^] (cualquier carácter que no esté en el conjunto vacío de caracteres) como una forma de coincidir con cualquier carácter. No podemos usar simplemente un punto aquí porque los comentarios de bloque pueden continuar en una nueva línea, y el carácter de punto no coincide con caracteres de nueva línea.

@@ -424,126 +424,129 @@

La parte [^]* de la expresión, como describí en la sección sobre retroceso, primero intentará coincidir con todo lo que pueda. Si esto hace que la siguiente parte del patrón falle, el coincidente retrocede un carácter y vuelve a intentar desde ahí. En el ejemplo, el coincidente intenta primero coincidir con el resto completo de la cadena y luego retrocede desde allí. Encontrará una ocurrencia de */ después de retroceder cuatro caracteres y coincidirá con eso. Esto no es lo que queríamos, la intención era coincidir con un único comentario, no llegar hasta el final del código y encontrar el final del último comentario de bloque.

-

Debido a este comportamiento, decimos que los operadores de repetición (+, *, ?, y {}) son avariciosos, lo que significa que coinciden con todo lo que pueden y retroceden desde allí. Si colocas un signo de interrogación después de ellos (+?, *?, ??, {}?), se vuelven no avariciosos y comienzan coincidiendo con la menor cantidad posible, coincidiendo más solo cuando el patrón restante no encaja con la coincidencia más pequeña.

+

Debido a este comportamiento, decimos que los operadores de repetición (+, *, ?, y {}) son avariciosos, lo que significa que coinciden con todo lo que pueden y retroceden desde allí. Si colocas un signo de interrogación después de ellos (+?, *?, ??, {}?), se vuelven no avariciosos y comienzan coincidiendo con la menor cantidad posible, expandiéndose solo si el resto del patrón no encaja con la coincidencia más pequeña.

Y eso es exactamente lo que queremos en este caso. Al hacer que el asterisco coincida con la menor cantidad de caracteres que nos lleva a */, consumimos un comentario de bloque y nada más.

-
function stripComments(code) {
-  return code.replace(/\/\/.*|\/\*[^]*?\*\//g, "");
+
function quitarComentarios(código) {
+  return código.replace(/\/\/.*|\/\*[^]*?\*\//g, "");
 }
-console.log(stripComments("1 /* a */+/* b */ 1"));
+console.log(quitarComentarios("1 /* a */+/* b */ 1"));
 // → 1 + 1
-

Muchos errors en programas de expresión regular pueden rastrearse hasta el uso no intencionado de un operador avaricioso donde uno no avaricioso funcionaría mejor. Cuando uses un operador de repetición, prefiere la variante no avariciosa.

+

Muchos errores en programas con expresiones regulares pueden rastrearse hasta el uso no intencionado de un operador avaricioso donde uno no avaricioso encajaría mejor. Cuando uses un operador de repetición, dale preferencia a la variante no avariciosa.

Creación dinámica de objetos RegExp

-

Hay casos en los que es posible que no sepas el patrón exacto que necesitas para hacer coincidir cuando estás escribiendo tu código. Digamos que quieres probar el nombre de usuario en un fragmento de texto. Puedes construir una cadena y usar el constructor RegExp en ello. Aquí tienes un ejemplo:

+

Hay casos en los que es posible que no sepas el patrón exacto que necesitas para hacer coincidir cuando estás escribiendo tu código. Digamos que quieres testear el nombre de usuario en un fragmento de texto. Puedes construir una cadena y usar el constructor RegExp sobre ella. Aquí tienes un ejemplo:

-
let name = "harry";
-let regexp = new RegExp("(^|\\s)" + name + "($|\\s)", "gi");
+
let nombre = "harry";
+let regexp = new RegExp("(^|\\s)" + nombre + "($|\\s)", "gi");
 console.log(regexp.test("Harry es un personaje dudoso."));
 // → true
-

Al crear la parte \s de la cadena, tenemos que usar dos barras invertidas porque las estamos escribiendo en una cadena normal, no en una expresión regular entre barras. El segundo argumento del constructor RegExp contiene las opciones para la expresión regular, en este caso, "gi" para global e insensible a mayúsculas y minúsculas.

+

Al crear la parte \s de la cadena, tenemos que usar dos barras invertidas porque las estamos escribiendo en una expresión de cadena (string) normal, no en una expresión regular entre barras. El segundo argumento del constructor RegExp contiene las opciones para la expresión regular, en este caso, "gi" para global e insensible a mayúsculas y minúsculas. Este expresión captura el nombre que se le pasa, ya esté al principio o final de una cadena, o rodeado por espacios.

Pero ¿qué pasa si el nombre es "dea+hl[]rd" porque nuestro usuario es un adolescente nerd? Eso resultaría en una expresión regular absurda que en realidad no coincidiría con el nombre del usuario.

Para solucionar esto, podemos agregar barras invertidas antes de cualquier carácter que tenga un significado especial.

-
let name = "dea+hl[]rd";
-let escaped = name.replace(/[\\[.+*?(){|^$]/g, "\\$&");
-let regexp = new RegExp("(^|\\s)" + escaped + "($|\\s)",
+
let nombre = "dea+hl[]rd";
+let escapado = nombre.replace(/[\\[.+*?(){|^$]/g, "\\$&");
+let regexp = new RegExp("(^|\\s)" + escapado + "($|\\s)",
                         "gi");
-let text = "Este chico dea+hl[]rd es súper molesto.";
-console.log(regexp.test(text));
+let texto = "Este chico dea+hl[]rd es súper pesado.";
+console.log(regexp.test(texto));
 // → true
+

N. del T.: Recordemos que, dentro de [...], casi los caracteres especiales pierden su significado, exceptuando en este caso la barra invertida, \, que debe escaparse.

+
+

El método search

-

El método indexOf en las cadenas no puede ser llamado con una expresión regular. Pero hay otro método, search, que espera una expresión regular. Al igual que indexOf, devuelve el primer índice en el que se encontró la expresión, o -1 cuando no se encontró.

+

Con una expresión regular no podemos usar el método indexOf de las cadenas. Pero hay otro método, search, que espera una expresión regular. Al igual que indexOf, devuelve el primer índice en el que se encuentra la expresión, o -1 cuando no se encuentra.

console.log("  palabra".search(/\S/));
 // → 2
 console.log("    ".search(/\S/));
 // → -1
-

Desafortunadamente, no hay una forma de indicar que la coincidencia debería comenzar en un offset dado (como se puede hacer con el segundo argumento de indexOf), lo cual a menudo sería útil.

+

Desafortunadamente, no hay una forma de indicar que la coincidencia debería comenzar en un offset dado (como se puede hacer con el segundo argumento de indexOf), lo que podría ser bastante útil a veces.

La propiedad lastIndex

-

El método exec de manera similar no proporciona una forma conveniente de comenzar a buscar desde una posición dada en la cadena. Pero sí proporciona una forma inconveniente.

+

De manera parecida, el método exec no proporciona una forma conveniente de comenzar a buscar desde una posición dada en la cadena. Pero sí proporciona una forma incómoda de hacerlo.

Los objetos de expresión regular tienen propiedades. Una de esas propiedades es source, que contiene la cadena de la que se creó la expresión. Otra propiedad es lastIndex, que controla, en algunas circunstancias limitadas, desde dónde comenzará la siguiente coincidencia.

-

Estas circunstancias implican que la expresión regular debe tener la opción global (g) o pegajosa (y) activada, y la coincidencia debe ocurrir a través del método exec. Nuevamente, una solución menos confusa habría sido simplemente permitir que se pase un argumento adicional a exec, pero la confusión es una característica esencial de la interfaz de expresiones regulares de JavaScript.

+

Estas circunstancias son que la expresión regular debe tener la opción global (g) o pegajosa (y) activadas, y la coincidencia debe ocurrir a través del método exec. De nuevo, una solución menos confusa habría sido simplemente permitir que se pase un argumento adicional a exec, pero la confusión es una característica esencial de la interfaz de expresiones regulares de JavaScript.

-
let pattern = /y/g;
-pattern.lastIndex = 3;
-let match = pattern.exec("xyzzy");
-console.log(match.index);
+
let patrón = /y/g;
+patrón.lastIndex = 3;
+let coincidencia = patrón.exec("xyzzy");
+console.log(coincidencia.index);
 // → 4
-console.log(pattern.lastIndex);
+console.log(patrón.lastIndex);
 // → 5

Si la coincidencia tuvo éxito, la llamada a exec actualiza automáticamente la propiedad lastIndex para que apunte después de la coincidencia. Si no se encontró ninguna coincidencia, lastIndex se restablece a cero, que es también el valor que tiene en un objeto de expresión regular recién construido.

-

La diferencia entre las opciones global y sticky es que, cuando se habilita sticky, la coincidencia solo se producirá si comienza directamente en lastIndex, mientras que con global se buscará una posición donde pueda comenzar una coincidencia.

+

La diferencia entre las opciones global y pegajosa (sticky) es que, cuando se habilita la opción pegajosa, la coincidencia solo se produce si comienza directamente en lastIndex, mientras que con global se buscará una posición donde pueda comenzar una coincidencia.

-
let global = /abc/g;
+
let global = /abc/g;
 console.log(global.exec("xyz abc"));
 // → ["abc"]
-let sticky = /abc/y;
-console.log(sticky.exec("xyz abc"));
+let pegajosa = /abc/y;
+console.log(pegajosa.exec("xyz abc"));
 // → null

Al usar un valor de expresión regular compartido para múltiples llamadas a exec, estas actualizaciones automáticas a la propiedad lastIndex pueden causar problemas. Es posible que tu expresión regular comience accidentalmente en un índice que quedó de una llamada previa.

-
let digit = /\d/g;
-console.log(digit.exec("aquí está: 1"));
+
let dígito = /\d/g;
+console.log(dígito.exec("aquí está: 1"));
 // → ["1"]
-console.log(digit.exec("ahora: 1"));
+console.log(dígito.exec("ahora: 1"));
 // → null
-

Otro efecto interesante de la opción global es que cambia la forma en que funciona el método match en las cadenas. Cuando se llama con una expresión global, en lugar de devolver una matriz similar a la devuelta por exec, match encontrará todas las coincidencias del patrón en la cadena y devolverá una matriz que contiene las cadenas coincidentes.

+

Otro efecto interesante de la opción global es que cambia la forma en que funciona el método match en las cadenas. Cuando se llama con una expresión global, en lugar de devolver un array como el que devuelve exec, match encontrará todas las coincidencias del patrón en la cadena y devolverá un array que contiene las cadenas coincidentes.

console.log("Banana".match(/an/g));
 // → ["an", "an"]
-

Así que ten cuidado con las expresiones regulares globales. Los casos en los que son necesarias, como las llamadas a replace y los lugares donde quieres usar explícitamente lastIndex, son típicamente los únicos lugares donde las deseas utilizar.

+

Así que ten cuidado con las expresiones regulares globales. Los casos en los que son necesarias, como las llamadas a replace y los lugares donde quieres usar explícitamente lastIndex, son normalmente los únicos lugares donde querrás utilizarlas.

Obteniendo todas las coincidencias

-

Algo común que se hace es encontrar todas las coincidencias de una expresión regular en una cadena. Podemos hacer esto usando el método matchAll.

+

Algo se suele hacer es encontrar todas las coincidencias de una expresión regular en una cadena. Podemos hacer esto usando el método matchAll.

-
let input = "Una cadena con 3 números... 42 y 88.";
-let matches = input.matchAll(/\d+/g);
-for (let match of matches) {
-  console.log("Encontrado", match[0], "en", match.index);
+
let input = "Una cadena con 3 números... 42 y 88.";
+let coincidencias = input.matchAll(/\d+/g);
+for (let coincidencia of coincidencias) {
+  console.log("Encontrado", coincidencia[0], "en", coincidencia.index);
 }
 // → Encontrado 3 en 14
 //   Encontrado 42 en 33
 //   Encontrado 88 en 40
-

Este método devuelve una matriz de matrices de coincidencias. La expresión regular que se le proporciona debe tener g habilitado.

+

Este método devuelve un array de arrays de coincidencias. La expresión regular que se le proporciona debe tener g habilitado.

Analizando un archivo INI

-

Para concluir el capítulo, analizaremos un problema que requiere expresiones regulares. Imagina que estamos escribiendo un programa para recopilar automáticamente información sobre nuestros enemigos desde Internet. (En realidad, no escribiremos ese programa aquí, solo la parte que lee el archivo de configuración. Lo siento.) El archivo de configuración se ve así:

+

Para concluir el capítulo, analizaremos un problema que requiere expresiones regulares. Imagina que estamos escribiendo un programa para recopilar automáticamente información sobre nuestros enemigos desde Internet (en realidad, no escribiremos ese programa aquí, solo la parte que lee el archivo de configuración, lo siento). El archivo de configuración tiene esta pinta:

-
motorbusqueda=https://duckduckgo.com/?q=$1
+
motorbusqueda=https://duckduckgo.com/?q=$1
 rencor=9.7
 
 ; comentarios precedidos por un punto y coma...
 ; cada sección se refiere a un enemigo individual
 [larry]
-fullname=Larry Doe
-type=matón de jardín de infantes
+nombrecompleto=Larry Doe
+tipo=matón de jardín de infancia
 website=http://www.geocities.com/CapeCanaveral/11451
 
 [davaeorn]
-fullname=Davaeorn
-type=mago malvado
+nombrecompleto=Davaeorn
+tipo=mago malvado
 outputdir=/home/marijn/enemies/davaeorn

Las reglas exactas para este formato (que es un formato ampliamente utilizado, generalmente llamado un archivo INI) son las siguientes:

@@ -560,7 +563,7 @@

-

Las líneas que contienen un identificador alfanumérico seguido de un caracter = agregan una configuración a la sección actual.

+

Las líneas que contienen un identificador alfanumérico seguido de un carácter = agregan una configuración a la sección actual.

  • @@ -570,42 +573,42 @@

    Dado que el formato debe procesarse línea por línea, dividir el archivo en líneas separadas es un buen comienzo. Vimos el método split en el Capítulo 4. Sin embargo, algunos sistemas operativos utilizan no solo un carácter de nueva línea para separar líneas sino un carácter de retorno de carro seguido de una nueva línea ("\r\n"). Dado que el método split también permite una expresión regular como argumento, podemos usar una expresión regular como /\r?\n/ para dividir de una manera que permita tanto "\n" como "\r\n" entre líneas.

    -
    function parseINI(string) {
    +
    function procesaINI(cadena) {
       // Comenzar con un objeto para contener los campos de nivel superior
    -  let result = {};
    -  let section = result;
    -  for (let line of string.split(/\r?\n/)) {
    -    let match;
    -    if (match = line.match(/^(\w+)=(.*)$/)) {
    -      section[match[1]] = match[2];
    -    } else if (match = line.match(/^\[(.*)\]$/)) {
    -      section = result[match[1]] = {};
    -    } else if (!/^\s*(;|$)/.test(line)) {
    -      throw new Error("La línea '" + line + "' no es válida.");
    +  let resultado = {};
    +  let sección = resultado;
    +  for (let línea of cadena.split(/\r?\n/)) {
    +    let coincidencia;
    +    if (coincidencia = línea.match(/^(\w+)=(.*)$/)) {
    +      sección[coincidencia[1]] = coincidencia[2];
    +    } else if (coincidencia = línea.match(/^\[(.*)\]$/)) {
    +      sección = resultado[coincidencia[1]] = {};
    +    } else if (!/^\s*(;|$)/.test(línea)) {
    +      throw new Error("La línea '" + línea + "' no es válida.");
         }
       };
    -  return result;
    +  return resultado;
     }
     
    -console.log(parseINI(`
    -name=Vasilis
    -[address]
    -city=Tessaloniki`));
    -// → {name: "Vasilis", address: {city: "Tessaloniki"}}
    +console.log(procesaINI(` +nombre=Vasilis +[dirección] +ciudad=Tessaloniki`)); +// → {nombre: "Vasilis", dirección: {ciudad: "Tessaloniki"}}

    El código recorre las líneas del archivo y construye un objeto. Las propiedades en la parte superior se almacenan directamente en ese objeto, mientras que las propiedades encontradas en secciones se almacenan en un objeto de sección separado. El enlace section apunta al objeto para la sección actual.

    -

    Hay dos tipos de líneas significativas: encabezados de sección o líneas de propiedades. Cuando una línea es una propiedad regular, se almacena en la sección actual. Cuando es un encabezado de sección, se crea un nuevo objeto de sección y section se establece para apuntar a él.

    +

    Hay dos tipos de líneas significativas: encabezados de sección o líneas de propiedades. Cuando una línea es una propiedad normal, se almacena en la sección actual. Cuando es un encabezado de sección, se crea un nuevo objeto de sección y se hace que section apunte a él.

    -

    Observa el uso recurrente de ^ y $ para asegurarse de que la expresión coincida con toda la línea, no solo parte de ella. Dejarlos fuera resulta en un código que funciona en su mayor parte pero se comporta de manera extraña para algunas entradas, lo que puede ser un error difícil de rastrear.

    +

    Observa el uso recurrente de ^ y $ para asegurarse de que la expresión coincida con toda la línea, no solo parte de ella. No usarlos resultaría en un código que funciona en su mayor parte pero se comporta de manera extraña para algunas entradas, lo que podría ser un error difícil de rastrear.

    -

    ```El patrón if (match = string.match(...)) hace uso del hecho de que el valor de una expresión de asignación (=) es el valor asignado. A menudo no estás seguro de que tu llamada a match tendrá éxito, por lo que solo puedes acceder al objeto resultante dentro de una declaración if que comprueba esto. Para no romper la agradable cadena de formas de else if, asignamos el resultado de la coincidencia a un enlace y usamos inmediatamente esa asignación como la prueba para la declaración if.

    +

    El patrón if (coincidencia = string.match(...)) hace uso del hecho de que el valor de una expresión de asignación (=) es el valor asignado. A menudo no estás seguro de que tu llamada a match tendrá éxito, por lo que solo puedes acceder al objeto resultante dentro de una declaración if que comprueba esto. Para no romper la agradable cadena de formularios else if, asignamos el resultado de la coincidencia a una asociación y usamos inmediatamente esa asignación como comprobación para la declaración if.

    Si una línea no es un encabezado de sección o una propiedad, la función verifica si es un comentario o una línea vacía usando la expresión /^\s*(;|$)/ para hacer coincidir líneas que solo contienen espacio o espacio seguido de un punto y coma (haciendo que el resto de la línea sea un comentario). Cuando una línea no coincide con ninguna de las formas esperadas, la función lanza una excepción.

    Unidades de código y caracteres

    -

    Otro error de diseño que se ha estandarizado en las expresiones regulares de JavaScript es que, por defecto, operadores como . o ? trabajan en unidades de código, como se discute en el Capítulo 5, no en caracteres reales. Esto significa que los caracteres que están compuestos por dos unidades de código se comportan de manera extraña.

    +

    Otro error de diseño que se ha estandarizado en las expresiones regulares de JavaScript es que, por defecto, operadores como . o ? trabajan en unidades de código, como se discute en el Capítulo 5, y no en caracteres reales. Esto significa que los caracteres que están compuestos por dos unidades de código se comportan de manera extraña.

    console.log(/🍎{3}/.test("🍎🍎🍎"));
     // → false
    @@ -647,7 +650,7 @@ 

    Una expresión regular tiene un método test para comprobar si una cadena dada coincide con ella. También tiene un método exec que, cuando se encuentra una coincidencia, devuelve un array que contiene todos los grupos coincidentes. Dicho array tiene una propiedad index que indica dónde empezó la coincidencia.Las cadenas tienen un método match para compararlas con una expresión regular y un método search para buscar una, devolviendo solo la posición de inicio de la coincidencia. Su método replace puede reemplazar coincidencias de un patrón con una cadena o función de reemplazo.

    +

    Una expresión regular tiene un método test para comprobar si una cadena dada coincide con ella. También tiene un método exec que, cuando se encuentra una coincidencia, devuelve un array que contiene todos los grupos coincidentes. Dicho array tiene una propiedad index que indica dónde empezó la coincidencia. Las cadenas tienen un método match para compararlas con una expresión regular y un método search para buscar una, devolviendo solo la posición de inicio de la coincidencia. Su método replace puede reemplazar coincidencias de un patrón con una cadena o función de reemplazo.

    -

    Las expresiones regulares pueden tener opciones, que se escriben después de la barra de cierre. La opción i hace que la coincidencia no distinga entre mayúsculas y minúsculas. La opción g hace que la expresión sea global, lo que, entre otras cosas, hace que el método replace reemplace todas las instancias en lugar de solo la primera. La opción y la hace persistente, lo que significa que no buscará por delante ni omitirá parte de la cadena al buscar una coincidencia. La opción u activa el modo Unicode, que habilita la sintaxis \p y soluciona varios problemas en torno al manejo de caracteres que ocupan dos unidades de código.

    +

    Las expresiones regulares pueden tener opciones, que se escriben después de la barra de cierre. La opción i hace que la coincidencia no distinga entre mayúsculas y minúsculas. La opción g hace que la expresión sea global, lo que, entre otras cosas, hace que el método replace reemplace todas las instancias en lugar de solo la primera. La opción y la hace “pegajosa”, lo que significa que no buscará por delante ni omitirá parte de la cadena al buscar una coincidencia. La opción u activa el modo Unicode, que habilita la sintaxis \p y soluciona varios problemas en torno al manejo de caracteres que ocupan dos unidades de código.

    Las expresiones regulares son una herramienta afilada con un mango incómodo. Simplifican enormemente algunas tareas, pero pueden volverse rápidamente ingobernables cuando se aplican a problemas complejos. Parte de saber cómo usarlas es resistir la tentación de intentar forzar cosas que no pueden expresarse de forma clara en ellas.

    Ejercicios

    -

    Es casi inevitable que, al trabajar en estos ejercicios, te sientas confundido y frustrado por el comportamiento inexplicable de algunas expresiones regulares. A veces ayuda introducir tu expresión en una herramienta en línea como debuggex.com para ver si su visualización corresponde a lo que pretendías y para experimentar con la forma en que responde a diferentes cadenas de entrada.

    +

    Es casi inevitable que, al trabajar en estos ejercicios, te sientas confundido y frustrado por el comportamiento inexplicable de algunas expresiones regulares. A veces ayuda introducir tu expresión en una herramienta en línea como debuggex.com para ver si su visualización corresponde con lo que pretendías y para experimentar con la forma en que responde a diferentes cadenas de entrada.

    Regexp golf

    @@ -818,7 +821,7 @@

    Números nuevamente

    -

    Escribe una expresión que coincida solo con los números al estilo de JavaScript. Debe admitir un signo menos o más opcional delante del número, el punto decimal y la notación de exponente—5e-3 o 1E10—de nuevo con un signo opcional delante del exponente. También ten en cuenta que no es necesario que haya dígitos delante o después del punto, pero el número no puede ser solo un punto. Es decir, .5 y 5. son números de JavaScript válidos, pero un punto solitario no lo es.

    +

    Escribe una expresión que coincida solo con los números al estilo de JavaScript. Debe admitir un signo menos o más opcional delante del número, el punto decimal y la notación de exponente —5e-3 o 1E10— de nuevo con un signo opcional delante del exponente. También ten en cuenta que no es necesario que haya dígitos delante o después del punto, pero el número no puede ser solo un punto. Es decir, .5 y 5. son números de JavaScript válidos, pero un punto solitario no lo es.

    // Completa esta expresión regular.
     let number = /^...$/;
    @@ -841,7 +844,7 @@ 

    Primero, no olvides la barra invertida delante del punto.

    -

    Para hacer coincidir el signo opcional delante del número, así como delante del exponente, se puede hacer con [+\-]? o (\+|-|) (más, menos, o nada).

    +

    Si queremos hacer coincidir el signo opcional delante del número, así como delante del exponente, esto se puede hacer con [+\-]? o (\+|-|) (más, menos, o nada).

    La parte más complicada del ejercicio es el problema de hacer coincidir tanto "5." como ".5" sin hacer coincidir también ".". Para esto, una buena solución es usar el operador | para separar los dos casos: uno o más dígitos seguidos opcionalmente por un punto y cero o más dígitos o un punto seguido por uno o más dígitos.

    From a319e3cbca3c1bf65b29a00bf283f5cd03625700 Mon Sep 17 00:00:00 2001 From: ckdvk Date: Mon, 17 Feb 2025 03:21:53 +0800 Subject: [PATCH 21/36] =?UTF-8?q?revisado=20cap=C3=ADtulo=2010.=20Peque?= =?UTF-8?q?=C3=B1os=20cambios=20en=20cap=C3=ADtulo=209?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 09_regexp.md | 4 +- 10_modules.md | 136 +++++++++++++++++------------------ code/packages_chapter_10.js | 18 ++--- html/09_regexp.html | 4 +- html/10_modules.html | 138 ++++++++++++++++++------------------ 5 files changed, 150 insertions(+), 150 deletions(-) diff --git a/09_regexp.md b/09_regexp.md index 4d48edd1..e7e71952 100644 --- a/09_regexp.md +++ b/09_regexp.md @@ -758,7 +758,7 @@ Nuestra tarea es convertir una cadena como esta en un objeto cuyas propiedades c Dado que el formato debe procesarse ((línea)) por línea, dividir el archivo en líneas separadas es un buen comienzo. Vimos el método `split` en el [Capítulo ?](data#split). Sin embargo, algunos sistemas operativos utilizan no solo un carácter de nueva línea para separar líneas sino un carácter de retorno de carro seguido de una nueva línea (`"\r\n"`). Dado que el método `split` también permite una expresión regular como argumento, podemos usar una expresión regular como `/\r?\n/` para dividir de una manera que permita tanto `"\n"` como `"\r\n"` entre líneas. ```{startCode: true} -function procesaINI(cadena) { +function procesarINI(cadena) { // Comenzar con un objeto para contener los campos de nivel superior let resultado = {}; let sección = resultado; @@ -775,7 +775,7 @@ function procesaINI(cadena) { return resultado; } -console.log(procesaINI(` +console.log(procesarINI(` nombre=Vasilis [dirección] ciudad=Tessaloniki`)); diff --git a/10_modules.md b/10_modules.md index cd3b1674..d00a34b7 100644 --- a/10_modules.md +++ b/10_modules.md @@ -2,7 +2,7 @@ # Módulos -{{quote {author: "Tef", title: "La programación es terrible", chapter: true} +{{quote {author: "Tef", title: "programming is terrible", chapter: true} Escribe código que sea fácil de borrar, no fácil de extender @@ -14,21 +14,21 @@ quote}} {{index "organización", ["código", estructura de]}} -Idealmente, un programa tiene una estructura clara y directa. La forma en que funciona es fácil de explicar, y cada parte desempeña un papel bien definido. +Idealmente, un programa tiene una estructura clara y directa. Es fácil explicar cómo funciona y cada parte desempeña un papel bien definido. {{index "crecimiento orgánico"}} -En la práctica, los programas crecen de forma orgánica. Se añaden piezas de funcionalidad a medida que el programador identifica nuevas necesidades. Mantener un programa de esta manera bien estructurado requiere atención y trabajo constantes. Este es un trabajo que solo dará sus frutos en el futuro, la próxima vez que alguien trabaje en el programa. Por lo tanto, es tentador descuidarlo y permitir que las diversas partes del programa se enreden profundamente. +En la práctica, los programas crecen de forma orgánica. Se añaden fragmentos de funcionalidad a medida que el programador identifica nuevas necesidades. Mantener bien estructurado un programa así requiere atención y trabajo constantes. Este es un trabajo que solo dará sus frutos en el futuro, la próxima vez que alguien trabaje en el programa. Por lo tanto, es tentador descuidarlo y permitir que las diversas partes del programa se enreden profundamente. -Esto causa dos problemas prácticos. Primero, entender un sistema enredado es difícil. Si todo puede afectar a todo lo demás, es difícil ver cualquier pieza en aislamiento. Te ves obligado a construir una comprensión holística de todo el conjunto. Segundo, si deseas utilizar alguna funcionalidad de dicho programa en otra situación, puede ser más fácil reescribirla que intentar desenredarla de su contexto. +Esto causa dos problemas prácticos. Primero, entender un sistema enredado es difícil. Si todo puede afectar a todo lo demás, es difícil mirar una parte concreta por separado. Te ves obligado a construir una comprensión integral de todo el conjunto. Segundo, si deseas utilizar alguna funcionalidad de dicho programa en otra situación, puede ser más fácil reescribirla que intentar desenredarla de su contexto. -La frase "((gran bola de barro))" se usa a menudo para tales programas grandes y sin estructura. Todo se une, y al intentar sacar una pieza, todo el conjunto se desintegra y solo logras hacer un desastre. +La frase "((gran bola de barro))" se usa a menudo para tales programas grandes y sin estructura. Todo va junto y, al intentar sacar un trozo, todo el conjunto se desintegra y lo único que logras es hacer un desastre. ## Programas modulares {{index dependencia, [interfaz, "módulo"]}} -Los _módulos_ son un intento de evitar estos problemas. Un ((módulo)) es una parte de un programa que especifica en qué otras piezas se basa y qué funcionalidad proporciona para que otros módulos la utilicen (su _interfaz_). +Los _módulos_ son un intento de evitar estos problemas. Un ((módulo)) es una parte de un programa que especifica en qué otras partes se basa y qué funcionalidad proporciona para que otros módulos la utilicen (su _interfaz_). {{index "gran bola de barro"}} @@ -36,66 +36,66 @@ Las interfaces de los módulos tienen mucho en común con las interfaces de obje {{index dependencia}} -Pero la interfaz que un módulo proporciona para que otros la utilicen es solo la mitad de la historia. Un buen sistema de módulos también requiere que los módulos especifiquen qué código _ellos_ utilizan de otros módulos. Estas relaciones se llaman _dependencias_. Si el módulo A utiliza funcionalidad del módulo B, se dice que _depende_ de él. Cuando estas dependencias se especifican claramente en el propio módulo, se pueden utilizar para averiguar qué otros módulos deben estar presentes para poder utilizar un módulo dado y cargar las dependencias automáticamente. +Pero la interfaz que un módulo proporciona para que otros la utilicen es solo la mitad de la historia. Un buen sistema de módulos también requiere que los módulos especifiquen qué código utilizan _ellos_ de otros módulos. Estas relaciones se llaman _dependencias_. Si el módulo A utiliza funcionalidad del módulo B, se dice que _depende_ de él. Cuando estas dependencias se especifican claramente en el propio módulo, se pueden utilizar para averiguar qué otros módulos deben estar presentes para poder utilizar un módulo dado y cargar las dependencias automáticamente. -Cuando las formas en que los módulos interactúan entre sí son explícitas, un sistema se vuelve más como ((LEGO)), donde las piezas interactúan a través de conectores bien definidos, y menos como barro, donde todo se mezcla con todo. +Cuando las formas en que los módulos interactúan entre sí son explícitas, un sistema se vuelve más como un ((LEGO)), donde las piezas interactúan a través de conectores bien definidos y menos como barro, donde todo se mezcla con todo. ## Módulos ES {{index "ámbito global", ["vinculación", global]}} -El lenguaje original JavaScript no tenía ningún concepto de un módulo. Todos los scripts se ejecutaban en el mismo ámbito, y acceder a una función definida en otro script se hacía mediante la referencia a las vinculaciones globales creadas por ese script. Esto fomentaba activamente el enredo accidental y difícil de detectar del código e invitaba a problemas como scripts no relacionados que intentaban usar el mismo nombre de vinculación. +El lenguaje original JavaScript no tenía ningún concepto de un módulo. Todos los scripts se ejecutaban en el mismo ámbito, y acceder a una función definida en otro script se hacía mediante la referencia a las asociaciones globales creadas por ese script. Esto propiciaba un enredo accidental y difícil de detectar del código e invitaba a problemas como scripts no relacionados que intentaban usar el mismo nombre de asociación. {{index "Módulos de ES"}} -Desde ECMAScript 2015, JavaScript admite dos tipos diferentes de programas. Los _scripts_ se comportan de la manera antigua: sus vinculaciones se definen en el ámbito global y no tienen forma de referenciar directamente otros scripts. Los _módulos_ obtienen su propio ámbito separado y admiten las palabras clave `import` y `export`, que no están disponibles en los scripts, para declarar sus dependencias e interfaz. Este sistema de módulos se suele llamar _módulos de ES_ (donde "ES" significa "ECMAScript"). +Desde ECMAScript 2015, JavaScript admite dos tipos diferentes de programas. Los _scripts_ se comportan de la manera antigua: sus asociaciones se definen en el ámbito global y no tienen forma de referenciar directamente otros scripts. Los _módulos_ obtienen su propio ámbito separado y admiten las palabras clave `import` y `export`, que no están disponibles en los scripts, para declarar sus dependencias e interfaz. Este sistema de módulos se suele llamar _módulos de ES_ (donde _ES_ significa "ECMAScript"). Un programa modular está compuesto por varios de estos módulos, conectados a través de sus importaciones y exportaciones. {{index "Clase Date", "módulo weekDay"}} -Este ejemplo de módulo convierte entre nombres de días y números (como los devueltos por el método `getDay` de `Date`). Define una constante que no forma parte de su interfaz y dos funciones que sí lo son. No tiene dependencias. +Este ejemplo de módulo intercambia entre nombres de días y números (como los devueltos por el método `getDay` de `Date`). Define una constante que no forma parte de su interfaz y dos funciones que sí lo son. No tiene dependencias. ``` -const names = ["Domingo", "Lunes", "Martes", "Miércoles", +const nombres = ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"]; -export function dayName(number) { - return names[number]; +export function nombreDía(número) { + return nombres[número]; } -export function dayNumber(name) { - return names.indexOf(name); +export function númeroDía(nombre) { + return nombres.indexOf(nombre); } ``` -La palabra clave `export` se puede colocar delante de una función, clase o definición de vinculación para indicar que esa vinculación es parte de la interfaz del módulo. Esto permite que otros módulos utilicen esa vinculación importándola. +La palabra clave `export` se puede colocar delante de una función, clase o definición de asociación para indicar que esa asociación es parte de la interfaz del módulo. Esto permite que otros módulos utilicen esa asociación importándola. ```{test: no} -import {dayName} from "./dayname.js"; +import {nombreDía} from "./dayname.js"; let ahora = new Date(); -console.log(`Hoy es ${dayName(ahora.getDay())}`); +console.log(`Hoy es ${nombreDía(ahora.getDay())}`); // → Hoy es Lunes ``` {{index "palabra clave import", dependencia, "módulos de ES"}} -La palabra clave `import`, seguida de una lista de nombres de vinculación entre llaves, hace que las vinculaciones de otro módulo estén disponibles en el módulo actual. Los módulos se identifican por cadenas entre comillas. +La palabra clave `import`, seguida de una lista de nombres de asociación entre llaves, hace que las asociaciones de otro módulo estén disponibles en el módulo actual. Los módulos se identifican por cadenas entre comillas. {{index ["módulo", "resolución"], "resolución"}} -Cómo se resuelve un nombre de módulo a un programa real difiere según la plataforma. El navegador los trata como direcciones web, mientras que Node.js los resuelve a archivos. Para ejecutar un módulo, se cargan todos los demás módulos en los que depende, y las vinculaciones exportadas se ponen a disposición de los módulos que las importan. +Cómo se resuelve un nombre de módulo en un programa real difiere según la plataforma. El navegador los trata como direcciones web, mientras que Node.js los resuelve a archivos. Para ejecutar un módulo, se cargan todos los demás módulos en los que depende, y las asociaciones exportadas se ponen a disposición de los módulos que las importan. -Las declaraciones de importación y exportación no pueden aparecer dentro de funciones, bucles u otros bloques. Se resuelven de inmediato cuando se carga el módulo, independientemente de cómo se ejecute el código en el módulo, y para reflejar esto, deben aparecer solo en el cuerpo del módulo externo. +Las declaraciones de importación y exportación no pueden aparecer dentro de funciones, bucles u otros bloques. Se resuelven de inmediato cuando se carga el módulo, independientemente de cómo se ejecute el código en el módulo y, para reflejar esto, deben aparecer solo en el cuerpo externo del módulo. -Así que la interfaz de un módulo consiste en una colección de vinculaciones con nombres, a las cuales tienen acceso otros módulos que dependen de ellas. Las vinculaciones importadas se pueden renombrar para darles un nuevo nombre local utilizando `as` después de su nombre. +Así que la interfaz de un módulo consiste en una colección de asociaciones con nombres, a las cuales tienen acceso otros módulos que dependen de ellas. Las asociaciones importadas se pueden renombrar para darles un nuevo nombre local utilizando `as` después de su nombre. ``` -import {dayName as nomDeJour} from "./dayname.js"; +import {nombreDía as nomDeJour} from "./nombredia.js"; console.log(nomDeJour(3)); // → Miércoles ``` -También es posible que un módulo tenga una exportación especial llamada `default`, que a menudo se usa para módulos que solo exportan un único enlace. Para definir una exportación predeterminada, se escribe `export default` antes de una expresión, una declaración de función o una declaración de clase. +También es posible que un módulo tenga una exportación especial llamada `default`, que a menudo se usa para módulos que solo exportan _un único_ enlace. Para definir una exportación predeterminada, se escribe `export default` antes de una expresión, una declaración de función o una declaración de clase. ``` export default ["Invierno", "Primavera", "Verano", "Otoño"]; @@ -104,7 +104,7 @@ export default ["Invierno", "Primavera", "Verano", "Otoño"]; Este enlace se importa omitiendo las llaves alrededor del nombre de la importación. ``` -import nombresEstaciones from "./nombrsestaciones.js"; +import nombresEstaciones from "./nombresestaciones.js"; ``` ## Paquetes @@ -115,39 +115,39 @@ Una de las ventajas de construir un programa a partir de piezas separadas y pode {{index "parseINI function"}} -Pero, ¿cómo se configura esto? Digamos que quiero usar la función `parseINI` de [Capítulo ?](regexp#ini) en otro programa. Si está claro de qué depende la función (en este caso, nada), puedo simplemente copiar ese módulo en mi nuevo proyecto y usarlo. Pero luego, si encuentro un error en el código, probablemente lo corrija en el programa con el que estoy trabajando en ese momento y olvide corregirlo también en el otro programa. +Pero, ¿cómo se configura esto? Digamos que quiero usar la función `procesarINI` de [Capítulo ?](regexp#ini) en otro programa. Si está claro de qué depende la función (en este caso, de nada), puedo simplemente copiar ese módulo en mi nuevo proyecto y usarlo. Pero luego, si encuentro un error en el código, probablemente lo corrija en el programa con el que estoy trabajando en ese momento y olvide corregirlo también en el otro programa. {{index duplication, "copy-paste programming"}} Una vez que empieces a duplicar código, rápidamente te darás cuenta de que estás perdiendo tiempo y energía moviendo copias y manteniéndolas actualizadas. -Ahí es donde entran los _((paquete))s_. Un paquete es un fragmento de código que se puede distribuir (copiar e instalar). Puede contener uno o más módulos y tiene información sobre en qué otros paquetes depende. Un paquete también suele venir con documentación que explica qué hace para que las personas que no lo escribieron aún puedan usarlo. +Ahí es donde entran los _((paquete))s_. Un paquete es un fragmento de código que se puede distribuir (copiar e instalar). Puede contener uno o más módulos y tiene información sobre de qué otros paquetes depende. Un paquete también suele venir con documentación que explica qué hace para que las personas que no lo escribieron también puedan usarlo. -Cuando se encuentra un problema en un paquete o se añade una nueva característica, se actualiza el paquete. Ahora los programas que dependen de él (que también pueden ser paquetes) pueden copiar la nueva ((versión)) para obtener las mejoras que se hicieron en el código. +Cuando se encuentra un problema en un paquete o se añade una nueva característica, se actualiza el paquete. Entonces, los programas que dependen de él (que también pueden ser paquetes) pueden copiar la nueva ((versión)) para obtener las mejoras que se hicieron en el código. {{id modules_npm}} {{index installation, upgrading, "package manager", download, reuse}} -Trabajar de esta manera requiere ((infraestructura)). Necesitamos un lugar para almacenar y encontrar paquetes y una forma conveniente de instalar y actualizarlos. En el mundo de JavaScript, esta infraestructura es provista por ((NPM)) ([_https://npmjs.org_](https://npmjs.org)). +Trabajar de esta manera requiere ((infraestructura)). Necesitamos un lugar para almacenar y encontrar paquetes y una forma conveniente de instalarlos y actualizarlos. En el mundo de JavaScript, esta infraestructura viene dada por ((NPM)) ([_https://npmjs.org_](https://npmjs.org)). -NPM es dos cosas: un servicio en línea donde puedes descargar (y subir) paquetes y un programa (incluido con Node.js) que te ayuda a instalar y gestionarlos. +NPM es dos cosas: un servicio en línea donde puedes descargar (y subir) paquetes, y un programa (incluido con Node.js) que te ayuda a instalar y gestionarlos. {{index "ini package"}} -En el momento de la escritura, hay más de tres millones de paquetes diferentes disponibles en NPM. Una gran parte de ellos son basura, para ser honesto. Pero casi cada paquete de JavaScript útil y disponible públicamente se puede encontrar en NPM. Por ejemplo, un analizador de archivos INI, similar al que construimos en el [Capítulo ?](regexp), está disponible bajo el nombre del paquete `ini`. +En el momento en que se escribe este libro, hay más de tres millones de paquetes diferentes disponibles en NPM. Una gran parte de ellos son basura, para ser honesto. Pero casi cada paquete de JavaScript útil y disponible públicamente se puede encontrar en NPM. Por ejemplo, un analizador de archivos INI, similar al que construimos en el [Capítulo ?](regexp), está disponible bajo el nombre de paquete `ini`. {{index "command line"}} -[Capítulo ?](node) mostrará cómo instalar tales paquetes localmente usando el programa de línea de comandos `npm`. +El [Capítulo ?](node) mostrará cómo instalar tales paquetes localmente usando el programa de línea de comandos `npm`. Tener paquetes de calidad disponibles para descargar es extremadamente valioso. Significa que a menudo podemos evitar reinventar un programa que 100 personas han escrito antes y obtener una implementación sólida y bien probada con solo presionar algunas teclas. {{index mantenimiento}} -El software es barato de copiar, por lo que una vez que alguien lo ha escrito, distribuirlo a otras personas es un proceso eficiente. Pero escribirlo en primer lugar _es_ trabajo, y responder a las personas que han encontrado problemas en el código, o que desean proponer nuevas características, es incluso más trabajo. +El software es barato de copiar, por lo que una vez que alguien lo ha escrito, distribuirlo a otras personas es un proceso eficiente. Pero escribirlo desde el principio _es un trabajo_, y responder a las personas que han encontrado problemas en el código, o que desean proponer nuevas características, es incluso más trabajo. -Por defecto, eres el ((propietario de los derechos de autor)) del código que escribes, y otras personas solo pueden usarlo con tu permiso. Pero porque algunas personas son amables y porque publicar buen software puede ayudarte a volverte un poco famoso entre los programadores, muchos paquetes se publican bajo una ((licencia)) que permite explícitamente a otras personas usarlo. +Por defecto, eres el ((propietario de los derechos de autor)) del código que escribes, y otras personas solo pueden usarlo con tu permiso. Pero como algunas personas son amables y como publicar buen software puede ayudarte a volverte un poco famoso entre los programadores, muchos paquetes se publican bajo una ((licencia)) que permite explícitamente a otras personas usarlo. La mayoría del código en ((NPM)) tiene esta licencia. Algunas licencias requieren que también publiques el código que construyes sobre el paquete bajo la misma licencia. Otros son menos exigentes, simplemente requiriendo que mantengas la licencia con el código al distribuirlo. La comunidad de JavaScript mayormente utiliza este último tipo de licencia. Al usar paquetes de otras personas, asegúrate de estar al tanto de su licencia. @@ -168,11 +168,11 @@ console.log(parse("x = 10\ny = 20")); ## Módulos CommonJS -Antes de 2015, cuando el lenguaje de JavaScript no tenía un sistema de módulos integrado real, las personas ya estaban construyendo sistemas grandes en JavaScript. Para que funcionara, ellos _necesitaban_ ((módulos)). +Antes de 2015, cuando el lenguaje de JavaScript no tenía un sistema de módulos integrado real, las personas ya estaban construyendo sistemas grandes en JavaScript. Para que funcionara, _necesitaban_ ((módulos)). {{index ["función", alcance], [interfaz, "módulo"], [objeto, como "módulo"]}} -La comunidad diseñó sus propios ((sistemas de módulos)) improvisados sobre el lenguaje. Estos utilizan funciones para crear un alcance local para los módulos y objetos regulares para representar interfaces de módulos. +La comunidad diseñó sus propios ((sistemas de módulos)) improvisados sobre el lenguaje. Estos utilizan funciones para crear un alcance local para los módulos y objetos normales para representar interfaces de módulos. Inicialmente, las personas simplemente envolvían manualmente todo su módulo en una "((expresión de función invocada inmediatamente))" para crear el alcance del módulo, y asignaban sus objetos de interfaz a una única variable global. @@ -200,11 +200,11 @@ Si implementamos nuestro propio cargador de módulos, podemos hacerlo mejor. El {{index "función require", [interfaz, "módulo"], "objeto exports"}} -Un módulo CommonJS se ve como un script regular, pero tiene acceso a dos enlaces que utiliza para interactuar con otros módulos. El primero es una función llamada `require`. Cuando llamas a esto con el nombre del módulo de tu dependencia, se asegura de que el módulo esté cargado y devuelve su interfaz. El segundo es un objeto llamado `exports`, que es el objeto de interfaz para el módulo. Comienza vacío y agregas propiedades para definir los valores exportados. +Un módulo CommonJS parece un script normal, pero tiene acceso a dos asociaciones que utiliza para interactuar con otros módulos. El primero es una función llamada `require`. Cuando llamas a esto con el nombre del módulo de tu dependencia, se asegura de que el módulo esté cargado y devuelve su interfaz. El segundo es un objeto llamado `exports`, que es el objeto de interfaz para el módulo. Comienza vacío y agregas propiedades para definir los valores exportados. {{index "formatDate module", "Date class", "ordinal package", "date-names package"}} -Este módulo de ejemplo CommonJS proporciona una función de formateo de fechas. Utiliza dos ((package))s de NPM: `ordinal` para convertir números en strings como `"1st"` y `"2nd"`, y `date-names` para obtener los nombres en inglés de los días de la semana y los meses. Exporta una única función, `formatDate`, que recibe un objeto `Date` y una cadena ((template)). +Este módulo de ejemplo CommonJS proporciona una función de formateo de fechas. Utiliza dos ((paquete))s de NPM: `ordinal` para convertir números en strings como `"1st"` y `"2nd"`, y `date-names` para obtener los nombres en inglés de los días de la semana y los meses. Exporta una única función, `formatDate`, que recibe un objeto `Date` y una cadena ((template)). La cadena de template puede contener códigos que indican el formato, como `YYYY` para el año completo y `Do` para el día ordinal del mes. Puede pasársele una cadena como `"MMMM Do YYYY"` para obtener una salida como "22 de noviembre de 2017". @@ -226,7 +226,7 @@ exports.formatDate = function(date, format) { {{index "destructuring binding"}} -La interfaz de `ordinal` es una única función, mientras que `date-names` exporta un objeto que contiene múltiples cosas: `days` y `months` son arrays de nombres. La técnica de desestructuración es muy conveniente al crear enlaces para las interfaces importadas. +La interfaz de `ordinal` es una única función, mientras que `date-names` exporta un objeto que contiene múltiples cosas: `days` y `months` son arrays de nombres. La técnica de desestructuración es muy conveniente al crear asociaciones para las interfaces importadas. El módulo añade su función de interfaz a `exports` para que los módulos que dependen de él tengan acceso a ella. Podemos usar el módulo de la siguiente manera: @@ -269,7 +269,7 @@ require.cache = Object.create(null); JavaScript estándar no proporciona una función como `readFile`, pero diferentes entornos de JavaScript, como el navegador y Node.js, proporcionan sus propias formas de acceder a los archivos. El ejemplo simplemente simula que `readFile` existe. -Para evitar cargar el mismo módulo múltiples veces, `require` mantiene una tienda (caché) de módulos ya cargados. Cuando se llama, primero comprueba si el módulo solicitado ha sido cargado y, si no, lo carga. Esto implica leer el código del módulo, envolverlo en una función y llamarlo. +Para evitar cargar el mismo módulo múltiples veces, `require` mantiene un almacenamiento (caché) de módulos ya cargados. Cuando se llama, primero comprueba si el módulo solicitado ha sido cargado y, si no, lo carga. Esto implica leer el código del módulo, envolverlo en una función y llamarlo. {{index "paquete ordinal", "objeto exports", "objeto module", [interfaz, "módulo"]}} @@ -277,13 +277,13 @@ Al definir `require`, `exports` como parámetros para la función de envoltura g Una diferencia importante entre este sistema y los módulos ES es que las importaciones de módulos ES suceden antes de que comience a ejecutarse el script de un módulo, mientras que `require` es una función normal, invocada cuando el módulo ya está en ejecución. A diferencia de las declaraciones `import`, las llamadas a `require` _pueden_ aparecer dentro de funciones, y el nombre de la dependencia puede ser cualquier expresión que se evalúe a una cadena, mientras que `import` solo permite cadenas simples entre comillas. -La transición de la comunidad de JavaScript desde el estilo CommonJS a los módulos ES ha sido lenta y algo complicada. Pero afortunadamente, ahora estamos en un punto en el que la mayoría de los paquetes populares en NPM proporcionan su código como módulos ES, y Node.js permite que los módulos ES importen desde módulos CommonJS. Por lo tanto, si bien el código CommonJS es algo con lo que te encontrarás, ya no hay una razón real para escribir nuevos programas en este estilo. +La transición de la comunidad de JavaScript desde el estilo CommonJS a los módulos ES ha sido lenta y algo complicada. Pero afortunadamente, ahora estamos en un punto en el que la mayoría de los paquetes populares en NPM proporcionan su código como módulos ES, y Node.js permite que los módulos ES importen desde módulos CommonJS. Por lo tanto, si bien el código CommonJS es algo con lo que te encontrarás, ya no hay una razón real para escribir nuevos programas de esta manera. ## Compilación y empaquetado {{index "compilación", "verificación de tipos"}} -Muchos paquetes de JavaScript no están, técnicamente, escritos en JavaScript. Hay extensiones, como TypeScript, el dialecto de verificación de tipos mencionado en el [Capítulo ?](error#typing), que se utilizan ampliamente. A menudo, las personas también comienzan a usar extensiones planeadas para el lenguaje mucho antes de que se agreguen a las plataformas que realmente ejecutan JavaScript. +Muchos paquetes de JavaScript no están, técnicamente, escritos en JavaScript. Hay extensiones, como TypeScript, el dialecto de verificación de tipos mencionado en el [Capítulo ?](error#typing), que se utilizan ampliamente. A menudo, la gente también comienza a usar extensiones planeadas para el lenguaje mucho antes de que se agreguen a las plataformas que realmente ejecutan JavaScript. Para hacer esto posible, _compilan_ su código, traduciéndolo desde su dialecto de JavaScript elegido a JavaScript antiguo, e incluso a una versión anterior de JavaScript, para que los navegadores puedan ejecutarlo. @@ -293,11 +293,11 @@ Incluir un programa modular que consta de 200 archivos diferentes en una ((pági {{index "tamaño del archivo"}} -Y podemos ir más allá. Aparte del número de archivos, el _tamaño_ de los archivos también determina qué tan rápido pueden ser transferidos a través de la red. Por lo tanto, la comunidad de JavaScript ha inventado _((minificador))es_. Estas son herramientas que toman un programa de JavaScript y lo hacen más pequeño al eliminar automáticamente comentarios y espacios en blanco, renombrar enlaces y reemplazar fragmentos de código con código equivalente que ocupa menos espacio. +Y podemos ir más allá. Aparte del número de archivos, el _tamaño_ de los archivos también determina qué tan rápido pueden ser transferidos a través de la red. Por lo tanto, la comunidad de JavaScript ha inventado _((minificador))es_. Estos son herramientas que toman un programa de JavaScript y lo hacen más pequeño al eliminar automáticamente comentarios y espacios en blanco, renombrar asociaciones y reemplazar fragmentos de código con código equivalente que ocupa menos espacio. {{index pipeline, herramienta}} -Por lo tanto, no es raro que el código que encuentres en un paquete de NPM o que se ejecute en una página web haya pasado por _múltiples_ etapas de transformación, convirtiéndose desde JavaScript moderno a JavaScript histórico, luego combinando los módulos en un solo archivo, y minimizando el código. No entraremos en detalles sobre estas herramientas en este libro ya que hay muchas de ellas, y cuál es popular cambia regularmente. Simplemente ten en cuenta que tales cosas existen, y búscalas cuando las necesites. +Por lo tanto, no es raro que el código que encuentres en un paquete de NPM o que se ejecute en una página web haya pasado por _múltiples_ etapas de transformación, convirtiéndose desde JavaScript moderno a JavaScript histórico, luego combinando los módulos en un solo archivo, y minimizando el código. No entraremos en detalles sobre estas herramientas en este libro ya que hay muchas de ellas, y cuál se usa más es algo que cambia regularmente. Simplemente ten en cuenta que tales cosas existen, y búscalas cuando las necesites. ## Diseño de módulos @@ -305,11 +305,11 @@ Por lo tanto, no es raro que el código que encuentres en un paquete de NPM o qu Estructurar programas es uno de los aspectos más sutiles de la programación. Cualquier funcionalidad no trivial puede ser organizada de diversas formas. -Un buen diseño de programa es subjetivo—hay compensaciones implicadas y cuestiones de gusto. La mejor manera de aprender el valor de un diseño bien estructurado es leer o trabajar en muchos programas y notar qué funciona y qué no. No asumas que un desorden doloroso es “simplemente así”. Puedes mejorar la estructura de casi todo pensando más detenidamente en ello. +Un buen diseño de programa es subjetivo —hay compensaciones implicadas y cuestiones de gusto. La mejor manera de aprender el valor de un diseño bien estructurado es leer o trabajar en muchos programas y notar qué funciona y qué no. No asumas que un código horrible es “simplemente así”. Puedes mejorar la estructura de casi todo pensando más detenidamente en ello. {{index [interfaz, "módulo"]}} -Un aspecto del diseño de módulos es la facilidad de uso. Si estás diseñando algo que se supone será utilizado por varias personas—o incluso por ti mismo, dentro de tres meses cuando ya no recuerdes los detalles de lo que hiciste—es útil que tu interfaz sea simple y predecible. +Un aspecto del diseño de módulos es la facilidad de uso. Si estás diseñando algo que se supone será utilizado por varias personas —o incluso por ti mismo, dentro de tres meses cuando ya no recuerdes los detalles de lo que hiciste— es útil que tu interfaz sea simple y predecible. {{index "paquete ini", JSON}} @@ -317,11 +317,11 @@ Eso puede significar seguir convenciones existentes. Un buen ejemplo es el paque {{index "efecto secundario", "disco duro", composabilidad}} -Incluso si no hay una función estándar o paquete ampliamente utilizado para imitar, puedes mantener tus módulos predecibles utilizando estructuras de datos simples y haciendo una sola cosa enfocada. Muchos de los módulos de análisis de archivos INI en NPM proporcionan una función que lee directamente dicho archivo desde el disco duro y lo analiza, por ejemplo. Esto hace imposible usar dichos módulos en el navegador, donde no tenemos acceso directo al sistema de archivos, y añade complejidad que hubiera sido mejor abordada _componiendo_ el módulo con alguna función de lectura de archivos. +Incluso si no hay una función estándar o paquete ampliamente utilizado para imitar, puedes mantener tus módulos predecibles utilizando estructuras de datos simples y haciendo una sola cosa muy concreta. Muchos de los módulos de análisis de archivos INI en NPM proporcionan una función que lee directamente dicho archivo desde el disco duro y lo analiza, por ejemplo. Esto hace imposible usar dichos módulos en el navegador, donde no tenemos acceso directo al sistema de archivos, y añade complejidad que hubiera sido mejor abordada _componiendo_ el módulo con alguna función de lectura de archivos. {{index "función pura"}} -Esto señala otro aspecto útil del diseño de módulos—la facilidad con la que algo puede ser compuesto con otro código. Los módulos enfocados en calcular valores son aplicables en una gama más amplia de programas que los módulos más grandes que realizan acciones complicadas con efectos secundarios. Un lector de archivos INI que insiste en leer el archivo desde el disco es inútil en un escenario donde el contenido del archivo proviene de otra fuente. +Esto señala otro aspecto útil del diseño de módulos —la facilidad con la que algo puede ser compuesto con otro código. Los módulos enfocados en calcular valores son aplicables en una gama más amplia de programas que los módulos más grandes que realizan acciones complicadas con efectos secundarios. Un lector de archivos INI que insiste en leer el archivo desde el disco es inútil en un escenario donde el contenido del archivo proviene de otra fuente. {{index "programación orientada a objetos"}} @@ -329,32 +329,32 @@ Relacionado con esto, a veces los objetos con estado son útiles o incluso neces A menudo, no se puede evitar definir nuevas estructuras de datos, ya que el estándar del lenguaje proporciona solo algunas básicas, y muchos tipos de datos deben ser más complejos que un array o un mapa. Pero cuando un array es suficiente, utiliza un array. -Un ejemplo de una estructura de datos ligeramente más compleja es el grafo de [Capítulo ?](robot). No hay una forma única obvia de representar un ((grafo)) en JavaScript. En ese capítulo, utilizamos un objeto cuyas propiedades contienen arrays de strings: los otros nodos alcanzables desde ese nodo. +Un ejemplo de una estructura de datos ligeramente más compleja es el grafo de [Capítulo ?](robot). No hay una única forma obvia de representar un ((grafo)) en JavaScript. En ese capítulo, utilizamos un objeto cuyas propiedades contienen arrays de strings: los otros nodos alcanzables desde ese nodo. -Existen varios paquetes de búsqueda de rutas en ((NPM)), pero ninguno de ellos utiliza este formato de grafo. Por lo general, permiten que las aristas del grafo tengan un peso, que es el costo o la distancia asociada a ellas. Eso no es posible en nuestra representación. +Existen varios paquetes de búsqueda de rutas en ((NPM)), pero ninguno de ellos utiliza este formato de grafo. Por lo general, permiten que las aristas del grafo tengan un peso, que es el coste o la distancia asociados a ellas. Eso no es posible en nuestra representación. {{index "Dijkstra, Edsger", pathfinding, "algoritmo de Dijkstra", "paquete dijkstrajs"}} Por ejemplo, está el paquete `dijkstrajs`. Un enfoque conocido para la búsqueda de rutas, bastante similar a nuestra función `findRoute`, se llama _algoritmo de Dijkstra_, en honor a Edsger Dijkstra, quien lo escribió por primera vez. A menudo se agrega el sufijo `js` a los nombres de los paquetes para indicar que están escritos en JavaScript. Este paquete `dijkstrajs` utiliza un formato de grafo similar al nuestro, pero en lugar de arrays, utiliza objetos cuyos valores de propiedad son números, los pesos de las aristas. -Por lo tanto, si quisiéramos usar ese paquete, deberíamos asegurarnos de que nuestro grafo esté almacenado en el formato que espera. Todas las aristas tienen el mismo peso, ya que nuestro modelo simplificado trata cada camino como teniendo el mismo coste (una vuelta). +Por lo tanto, si quisiéramos usar ese paquete, deberíamos asegurarnos de que nuestro grafo esté almacenado en el formato que espera. Todas las aristas tienen el mismo peso, ya que nuestro modelo simplificado trata cada camino como teniendo el mismo coste (un paso). ``` const {find_path} = require("dijkstrajs"); -let graph = {}; -for (let node of Object.keys(roadGraph)) { - let edges = graph[node] = {}; - for (let dest of roadGraph[node]) { - edges[dest] = 1; +let grafo = {}; +for (let nodo of Object.keys(roadGraph)) { + let aristas = grafo[nodo] = {}; + for (let dest of roadGraph[nodo]) { + aristas[dest] = 1; } } -console.log(find_path(graph, "Oficina de Correos", "Cabaña")); +console.log(find_path(grafo, "Oficina de Correos", "Cabaña")); // → ["Oficina de Correos", "Casa de Alicia", "Cabaña"] ``` -Esto puede ser una barrera para la composición: cuando varios paquetes están utilizando diferentes estructuras de datos para describir cosas similares, combinarlos es difícil. Por lo tanto, si deseas diseñar para la composabilidad, averigua qué ((estructuras de datos)) están utilizando otras personas y, cuando sea posible, sigue su ejemplo. +Esto puede ser una barrera para la composición: cuando varios paquetes están utilizando diferentes estructuras de datos para describir cosas similares, combinarlos es difícil. Por lo tanto, si deseas diseñar de cara a la composabilidad, averigua qué ((estructuras de datos)) están utilizando otras personas y, cuando sea posible, sigue su ejemplo. {{index "diseño"}} @@ -366,7 +366,7 @@ Los módulos proporcionan estructura a programas más grandes al separar el cód Dado que JavaScript históricamente no proporcionaba un sistema de módulos, se construyó el sistema CommonJS sobre él. Luego, en algún momento _obtuvo_ un sistema incorporado, que ahora coexiste incómodamente con el sistema CommonJS. -Un paquete es un fragmento de código que se puede distribuir por sí solo. NPM es un repositorio de paquetes de JavaScript. Puedes descargar todo tipo de paquetes útiles (y inútiles) desde aquí. +Un paquete es un fragmento de código que se puede distribuir por sí solo. NPM es un repositorio de paquetes de JavaScript. Puedes descargar todo tipo de paquetes útiles (e inútiles) desde aquí. ## Ejercicios @@ -376,7 +376,7 @@ Un paquete es un fragmento de código que se puede distribuir por sí solo. NPM {{id modular_robot}} -Estos son los enlaces que crea el proyecto del [Capítulo ?](robot): +Estas son las asociaciones que crea el proyecto del [Capítulo ?](robot): ```{lang: "null"} roads @@ -404,21 +404,21 @@ Esto es lo que habría hecho (pero de nuevo, no hay una única forma _correcta_ {{index "dijkstrajs package"}} -El código utilizado para construir el gráfico de carreteras se encuentra en el módulo `graph`. Como preferiría usar `dijkstrajs` de NPM en lugar de nuestro propio código de búsqueda de caminos, haremos que este construya el tipo de datos de gráfico que espera `dijkstrajs`. Este módulo exporta una única función, `buildGraph`. Haría que `buildGraph` aceptara un arreglo de arreglos de dos elementos, en lugar de cuerdas que contienen guiones, para hacer que el módulo dependa menos del formato de entrada. +El código utilizado para construir el grafo de carreteras se encuentra en el módulo `graph`. Como preferiría usar `dijkstrajs` de NPM en lugar de nuestro propio código de búsqueda de caminos, haremos que este construya el tipo de datos de grafo que espera `dijkstrajs`. Este módulo exporta una única función, `buildGraph`. Haría que `buildGraph` aceptara un array de arrays de dos elementos, en lugar de cadenas que contienen guiones, para hacer que el módulo dependa menos del formato de entrada. -El módulo `roads` contiene los datos crudos de las carreteras (el arreglo `roads`) y el enlace `roadGraph`. Este módulo depende de `./graph.js` y exporta el grafo de carreteras. +El módulo `roads` contiene los datos en bruto de las carreteras (el array `roads`) y el enlace `roadGraph`. Este módulo depende de `./graph.js` y exporta el grafo de carreteras. {{index "random-item package"}} La clase `VillageState` se encuentra en el módulo `state`. Depende del módulo `./roads` porque necesita poder verificar que una carretera dada exista. También necesita `randomPick`. Dado que es una función de tres líneas, podríamos simplemente ponerla en el módulo `state` como una función auxiliar interna. Pero `randomRobot` también la necesita. Entonces tendríamos que duplicarla o ponerla en su propio módulo. Dado que esta función existe en NPM en el paquete `random-item`, una solución razonable es hacer que ambos módulos dependan de eso. También podemos agregar la función `runRobot` a este módulo, ya que es pequeña y está relacionada con la gestión del estado. El módulo exporta tanto la clase `VillageState` como la función `runRobot`. -Finalmente, los robots, junto con los valores en los que dependen, como `mailRoute`, podrían ir en un módulo `example-robots`, que depende de `./roads` y exporta las funciones del robot. Para que `goalOrientedRobot` pueda realizar la búsqueda de rutas, este módulo también depende de `dijkstrajs`.Al externalizar cierto trabajo a módulos ((NPM)), el código se volvió un poco más pequeño. Cada módulo individual hace algo bastante simple y se puede leer por sí solo. Dividir el código en módulos a menudo sugiere mejoras adicionales en el diseño del programa. En este caso, parece un poco extraño que el `VillageState` y los robots dependan de un gráfico de caminos específico. Podría ser una mejor idea hacer que el gráfico sea un argumento del constructor de estado y hacer que los robots lo lean desde el objeto de estado, esto reduce las dependencias (lo cual siempre es bueno) y hace posible ejecutar simulaciones en mapas diferentes (lo cual es aun mejor). +Finalmente, los robots, junto con los valores en los que dependen, como `mailRoute`, podrían ir en un módulo `example-robots`, que depende de `./roads` y exporta las funciones del robot. Para que `goalOrientedRobot` pueda realizar la búsqueda de rutas, este módulo también depende de `dijkstrajs`.Al externalizar cierto trabajo a módulos ((NPM)), el código se volvió un poco más pequeño. Cada módulo individual hace algo bastante simple y se puede leer por sí solo. Dividir el código en módulos a menudo sugiere mejoras adicionales en el diseño del programa. En este caso, parece un poco extraño que el `VillageState` y los robots dependan de un grafo de caminos específico. Podría ser una mejor idea hacer que el grafo sea un argumento del constructor de estado y hacer que los robots lo lean desde el objeto de estado, esto reduce las dependencias (lo cual siempre es bueno) y hace posible ejecutar simulaciones en mapas diferentes (lo cual es aun mejor). ¿Es una buena idea utilizar módulos de NPM para cosas que podríamos haber escrito nosotros mismos? En principio, sí, para cosas no triviales como la función de búsqueda de caminos es probable que cometas errores y pierdas tiempo escribiéndolas tú mismo. Para funciones pequeñas como `random-item`, escribirlas por ti mismo es bastante fácil. Pero añadirlas donde las necesitas tiende a saturar tus módulos. -Sin embargo, tampoco debes subestimar el trabajo involucrado en _encontrar_ un paquete de NPM apropiado. Y aunque encuentres uno, podría no funcionar bien o le podrían faltar alguna característica que necesitas. Además, depender de paquetes de NPM significa que debes asegurarte de que estén instalados, debes distribuirlos con tu programa y es posible que debas actualizarlos periódicamente. +Sin embargo, tampoco debes subestimar el trabajo empleado en _encontrar_ un paquete de NPM apropiado. Y aunque encuentres uno, podría no funcionar bien o le podría faltar alguna característica que necesitas. Además, depender de paquetes de NPM significa que debes asegurarte de que estén instalados, debes distribuirlos con tu programa y es posible que debas actualizarlos periódicamente. -Así que de nuevo, esto es un compromiso, y puedes decidir de cualquier manera dependiendo de cuánto te ayude realmente un paquete dado. +Así que de nuevo, esto es un compromiso, y puedes decidirte por cualquier opción dependiendo de cuánto te ayude realmente un paquete dado. hint}} @@ -426,7 +426,7 @@ hint}} {{index "módulo de caminos (ejercicio)"}} -Escribe un módulo ES, basado en el ejemplo del [Capítulo ?](robot), que contenga el array de caminos y exporte la estructura de datos de gráfico que los representa como `roadGraph`. Debería depender de un módulo `./graph.js`, que exporta una función `buildGraph` que se utiliza para construir el gráfico. Esta función espera un array de arrays de dos elementos (los puntos de inicio y fin de los caminos). +Escribe un módulo ES, basado en el ejemplo del [Capítulo ?](robot), que contenga el array de caminos y exporte la estructura de datos de grafo que los representa como `roadGraph`. Debería depender de un módulo `./graph.js`, que exporta una función `buildGraph` que se utiliza para construir el grafo. Esta función espera un array de arrays de dos elementos (los puntos de inicio y fin de los caminos). {{if interactive @@ -457,7 +457,7 @@ if}} {{index "módulo de caminos (ejercicio)", "desestructuración", "objeto de exportaciones"}} -Dado que este es un módulo ES, debes usar `import` para acceder al módulo de gráfico. Esto se describió como exportando una función de `buildGraph`, la cual puedes seleccionar de su objeto de interfaz con una declaración de desestructuración `const`. +Dado que este es un módulo ES, debes usar `import` para acceder al módulo de grafo. Esto se describió como exportando una función de `buildGraph`, la cual puedes seleccionar de su objeto de interfaz con una declaración de desestructuración `const`. Para exportar `roadGraph`, colocas la palabra clave `export` antes de su definición. Debido a que `buildGraph` toma una estructura de datos que no coincide exactamente con `roads`, la división de las cadenas de carretera debe ocurrir en tu módulo. diff --git a/code/packages_chapter_10.js b/code/packages_chapter_10.js index b4e07bbb..41b12940 100644 --- a/code/packages_chapter_10.js +++ b/code/packages_chapter_10.js @@ -67,19 +67,19 @@ module.exports = { pm: 'PM' };`) -require.preload("./dayname.js", String.raw` -const names = ["Sunday", "Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday"]; +require.preload("./nombredia.js", String.raw` +const nombres = ["Domingo", "Lunes", "Martes", "Miércoles", + "Jueves", "Viernes", "Sábado"]; -exports.dayName = function(number) { - return names[number]; +exports.nombreDía = function(número) { + return nombres[número]; } -exports.dayNumber = function(name) { - return names.indexOf(name); +exports.númeroDía = function(nombre) { + return nombres.indexOf(nombre); }`) -require.preload("./seasonname.js", String.raw` -module.exports = ["Winter", "Spring", "Summer", "Autumn"];`) +require.preload("./nombresestaciones.js", String.raw` +module.exports = ["Invierno", "Primavera", "Verano", "Otoño"];`) /* ini 1.3.5: https://github.com/npm/ini diff --git a/html/09_regexp.html b/html/09_regexp.html index 4120a89d..1607689f 100644 --- a/html/09_regexp.html +++ b/html/09_regexp.html @@ -573,7 +573,7 @@

    Dado que el formato debe procesarse línea por línea, dividir el archivo en líneas separadas es un buen comienzo. Vimos el método split en el Capítulo 4. Sin embargo, algunos sistemas operativos utilizan no solo un carácter de nueva línea para separar líneas sino un carácter de retorno de carro seguido de una nueva línea ("\r\n"). Dado que el método split también permite una expresión regular como argumento, podemos usar una expresión regular como /\r?\n/ para dividir de una manera que permita tanto "\n" como "\r\n" entre líneas.

    -
    function procesaINI(cadena) {
    +
    function procesarINI(cadena) {
       // Comenzar con un objeto para contener los campos de nivel superior
       let resultado = {};
       let sección = resultado;
    @@ -590,7 +590,7 @@ 

    return resultado; } -console.log(procesaINI(` +console.log(procesarINI(` nombre=Vasilis [dirección] ciudad=Tessaloniki`)); diff --git a/html/10_modules.html b/html/10_modules.html index 01384ce9..9c558e11 100644 --- a/html/10_modules.html +++ b/html/10_modules.html @@ -16,100 +16,100 @@

    Módulos

    Escribe código que sea fácil de borrar, no fácil de extender

    -
    Tef, La programación es terrible
    +
    Tef, programming is terrible

  • Ilustración de un edificio complicado construido a partir de piezas modulares
    -

    Idealmente, un programa tiene una estructura clara y directa. La forma en que funciona es fácil de explicar, y cada parte desempeña un papel bien definido.

    +

    Idealmente, un programa tiene una estructura clara y directa. Es fácil explicar cómo funciona y cada parte desempeña un papel bien definido.

    -

    En la práctica, los programas crecen de forma orgánica. Se añaden piezas de funcionalidad a medida que el programador identifica nuevas necesidades. Mantener un programa de esta manera bien estructurado requiere atención y trabajo constantes. Este es un trabajo que solo dará sus frutos en el futuro, la próxima vez que alguien trabaje en el programa. Por lo tanto, es tentador descuidarlo y permitir que las diversas partes del programa se enreden profundamente.

    +

    En la práctica, los programas crecen de forma orgánica. Se añaden fragmentos de funcionalidad a medida que el programador identifica nuevas necesidades. Mantener bien estructurado un programa así requiere atención y trabajo constantes. Este es un trabajo que solo dará sus frutos en el futuro, la próxima vez que alguien trabaje en el programa. Por lo tanto, es tentador descuidarlo y permitir que las diversas partes del programa se enreden profundamente.

    -

    Esto causa dos problemas prácticos. Primero, entender un sistema enredado es difícil. Si todo puede afectar a todo lo demás, es difícil ver cualquier pieza en aislamiento. Te ves obligado a construir una comprensión holística de todo el conjunto. Segundo, si deseas utilizar alguna funcionalidad de dicho programa en otra situación, puede ser más fácil reescribirla que intentar desenredarla de su contexto.

    +

    Esto causa dos problemas prácticos. Primero, entender un sistema enredado es difícil. Si todo puede afectar a todo lo demás, es difícil mirar una parte concreta por separado. Te ves obligado a construir una comprensión integral de todo el conjunto. Segundo, si deseas utilizar alguna funcionalidad de dicho programa en otra situación, puede ser más fácil reescribirla que intentar desenredarla de su contexto.

    -

    La frase “gran bola de barro” se usa a menudo para tales programas grandes y sin estructura. Todo se une, y al intentar sacar una pieza, todo el conjunto se desintegra y solo logras hacer un desastre.

    +

    La frase “gran bola de barro” se usa a menudo para tales programas grandes y sin estructura. Todo va junto y, al intentar sacar un trozo, todo el conjunto se desintegra y lo único que logras es hacer un desastre.

    Programas modulares

    -

    Los módulos son un intento de evitar estos problemas. Un módulo es una parte de un programa que especifica en qué otras piezas se basa y qué funcionalidad proporciona para que otros módulos la utilicen (su interfaz).

    +

    Los módulos son un intento de evitar estos problemas. Un módulo es una parte de un programa que especifica en qué otras partes se basa y qué funcionalidad proporciona para que otros módulos la utilicen (su interfaz).

    Las interfaces de los módulos tienen mucho en común con las interfaces de objetos, como las vimos en el Capítulo 6. Permiten que una parte del módulo esté disponible para el mundo exterior y mantienen el resto privado.

    -

    Pero la interfaz que un módulo proporciona para que otros la utilicen es solo la mitad de la historia. Un buen sistema de módulos también requiere que los módulos especifiquen qué código ellos utilizan de otros módulos. Estas relaciones se llaman dependencias. Si el módulo A utiliza funcionalidad del módulo B, se dice que depende de él. Cuando estas dependencias se especifican claramente en el propio módulo, se pueden utilizar para averiguar qué otros módulos deben estar presentes para poder utilizar un módulo dado y cargar las dependencias automáticamente.

    +

    Pero la interfaz que un módulo proporciona para que otros la utilicen es solo la mitad de la historia. Un buen sistema de módulos también requiere que los módulos especifiquen qué código utilizan ellos de otros módulos. Estas relaciones se llaman dependencias. Si el módulo A utiliza funcionalidad del módulo B, se dice que depende de él. Cuando estas dependencias se especifican claramente en el propio módulo, se pueden utilizar para averiguar qué otros módulos deben estar presentes para poder utilizar un módulo dado y cargar las dependencias automáticamente.

    -

    Cuando las formas en que los módulos interactúan entre sí son explícitas, un sistema se vuelve más como LEGO, donde las piezas interactúan a través de conectores bien definidos, y menos como barro, donde todo se mezcla con todo.

    +

    Cuando las formas en que los módulos interactúan entre sí son explícitas, un sistema se vuelve más como un LEGO, donde las piezas interactúan a través de conectores bien definidos y menos como barro, donde todo se mezcla con todo.

    Módulos ES

    -

    El lenguaje original JavaScript no tenía ningún concepto de un módulo. Todos los scripts se ejecutaban en el mismo ámbito, y acceder a una función definida en otro script se hacía mediante la referencia a las vinculaciones globales creadas por ese script. Esto fomentaba activamente el enredo accidental y difícil de detectar del código e invitaba a problemas como scripts no relacionados que intentaban usar el mismo nombre de vinculación.

    +

    El lenguaje original JavaScript no tenía ningún concepto de un módulo. Todos los scripts se ejecutaban en el mismo ámbito, y acceder a una función definida en otro script se hacía mediante la referencia a las asociaciones globales creadas por ese script. Esto propiciaba un enredo accidental y difícil de detectar del código e invitaba a problemas como scripts no relacionados que intentaban usar el mismo nombre de asociación.

    -

    Desde ECMAScript 2015, JavaScript admite dos tipos diferentes de programas. Los scripts se comportan de la manera antigua: sus vinculaciones se definen en el ámbito global y no tienen forma de referenciar directamente otros scripts. Los módulos obtienen su propio ámbito separado y admiten las palabras clave import y export, que no están disponibles en los scripts, para declarar sus dependencias e interfaz. Este sistema de módulos se suele llamar módulos de ES (donde “ES” significa “ECMAScript”).

    +

    Desde ECMAScript 2015, JavaScript admite dos tipos diferentes de programas. Los scripts se comportan de la manera antigua: sus asociaciones se definen en el ámbito global y no tienen forma de referenciar directamente otros scripts. Los módulos obtienen su propio ámbito separado y admiten las palabras clave import y export, que no están disponibles en los scripts, para declarar sus dependencias e interfaz. Este sistema de módulos se suele llamar módulos de ES (donde ES significa “ECMAScript”).

    Un programa modular está compuesto por varios de estos módulos, conectados a través de sus importaciones y exportaciones.

    -

    Este ejemplo de módulo convierte entre nombres de días y números (como los devueltos por el método getDay de Date). Define una constante que no forma parte de su interfaz y dos funciones que sí lo son. No tiene dependencias.

    +

    Este ejemplo de módulo intercambia entre nombres de días y números (como los devueltos por el método getDay de Date). Define una constante que no forma parte de su interfaz y dos funciones que sí lo son. No tiene dependencias.

    -
    const names = ["Domingo", "Lunes", "Martes", "Miércoles",
    +
    const nombres = ["Domingo", "Lunes", "Martes", "Miércoles",
                    "Jueves", "Viernes", "Sábado"];
     
    -export function dayName(number) {
    -  return names[number];
    +export function nombreDía(número) {
    +  return nombres[número];
     }
    -export function dayNumber(name) {
    -  return names.indexOf(name);
    +export function númeroDía(nombre) {
    +  return nombres.indexOf(nombre);
     }
    -

    La palabra clave export se puede colocar delante de una función, clase o definición de vinculación para indicar que esa vinculación es parte de la interfaz del módulo. Esto permite que otros módulos utilicen esa vinculación importándola.

    +

    La palabra clave export se puede colocar delante de una función, clase o definición de asociación para indicar que esa asociación es parte de la interfaz del módulo. Esto permite que otros módulos utilicen esa asociación importándola.

    -
    import {dayName} from "./dayname.js";
    +
    import {nombreDía} from "./dayname.js";
     let ahora = new Date();
    -console.log(`Hoy es ${dayName(ahora.getDay())}`);
    +console.log(`Hoy es ${nombreDía(ahora.getDay())}`);
     // → Hoy es Lunes
    -

    La palabra clave import, seguida de una lista de nombres de vinculación entre llaves, hace que las vinculaciones de otro módulo estén disponibles en el módulo actual. Los módulos se identifican por cadenas entre comillas.

    +

    La palabra clave import, seguida de una lista de nombres de asociación entre llaves, hace que las asociaciones de otro módulo estén disponibles en el módulo actual. Los módulos se identifican por cadenas entre comillas.

    -

    Cómo se resuelve un nombre de módulo a un programa real difiere según la plataforma. El navegador los trata como direcciones web, mientras que Node.js los resuelve a archivos. Para ejecutar un módulo, se cargan todos los demás módulos en los que depende, y las vinculaciones exportadas se ponen a disposición de los módulos que las importan.

    +

    Cómo se resuelve un nombre de módulo en un programa real difiere según la plataforma. El navegador los trata como direcciones web, mientras que Node.js los resuelve a archivos. Para ejecutar un módulo, se cargan todos los demás módulos en los que depende, y las asociaciones exportadas se ponen a disposición de los módulos que las importan.

    -

    Las declaraciones de importación y exportación no pueden aparecer dentro de funciones, bucles u otros bloques. Se resuelven de inmediato cuando se carga el módulo, independientemente de cómo se ejecute el código en el módulo, y para reflejar esto, deben aparecer solo en el cuerpo del módulo externo.

    +

    Las declaraciones de importación y exportación no pueden aparecer dentro de funciones, bucles u otros bloques. Se resuelven de inmediato cuando se carga el módulo, independientemente de cómo se ejecute el código en el módulo y, para reflejar esto, deben aparecer solo en el cuerpo externo del módulo.

    -

    Así que la interfaz de un módulo consiste en una colección de vinculaciones con nombres, a las cuales tienen acceso otros módulos que dependen de ellas. Las vinculaciones importadas se pueden renombrar para darles un nuevo nombre local utilizando as después de su nombre.

    +

    Así que la interfaz de un módulo consiste en una colección de asociaciones con nombres, a las cuales tienen acceso otros módulos que dependen de ellas. Las asociaciones importadas se pueden renombrar para darles un nuevo nombre local utilizando as después de su nombre.

    -
    import {dayName as nomDeJour} from "./dayname.js";
    +
    import {nombreDía as nomDeJour} from "./nombredia.js";
     console.log(nomDeJour(3));
     // → Miércoles
    -

    También es posible que un módulo tenga una exportación especial llamada default, que a menudo se usa para módulos que solo exportan un único enlace. Para definir una exportación predeterminada, se escribe export default antes de una expresión, una declaración de función o una declaración de clase.

    +

    También es posible que un módulo tenga una exportación especial llamada default, que a menudo se usa para módulos que solo exportan un único enlace. Para definir una exportación predeterminada, se escribe export default antes de una expresión, una declaración de función o una declaración de clase.

    export default ["Invierno", "Primavera", "Verano", "Otoño"];

    Este enlace se importa omitiendo las llaves alrededor del nombre de la importación.

    -
    import nombresEstaciones from "./nombrsestaciones.js";
    +
    import nombresEstaciones from "./nombresestaciones.js";

    Paquetes

    Una de las ventajas de construir un programa a partir de piezas separadas y poder ejecutar algunas de esas piezas por separado, es que puedes aplicar la misma pieza en diferentes programas.

    -

    Pero, ¿cómo se configura esto? Digamos que quiero usar la función parseINI de Capítulo 9 en otro programa. Si está claro de qué depende la función (en este caso, nada), puedo simplemente copiar ese módulo en mi nuevo proyecto y usarlo. Pero luego, si encuentro un error en el código, probablemente lo corrija en el programa con el que estoy trabajando en ese momento y olvide corregirlo también en el otro programa.

    +

    Pero, ¿cómo se configura esto? Digamos que quiero usar la función procesarINI de Capítulo 9 en otro programa. Si está claro de qué depende la función (en este caso, de nada), puedo simplemente copiar ese módulo en mi nuevo proyecto y usarlo. Pero luego, si encuentro un error en el código, probablemente lo corrija en el programa con el que estoy trabajando en ese momento y olvide corregirlo también en el otro programa.

    Una vez que empieces a duplicar código, rápidamente te darás cuenta de que estás perdiendo tiempo y energía moviendo copias y manteniéndolas actualizadas.

    -

    Ahí es donde entran los paquetes. Un paquete es un fragmento de código que se puede distribuir (copiar e instalar). Puede contener uno o más módulos y tiene información sobre en qué otros paquetes depende. Un paquete también suele venir con documentación que explica qué hace para que las personas que no lo escribieron aún puedan usarlo.

    +

    Ahí es donde entran los paquetes. Un paquete es un fragmento de código que se puede distribuir (copiar e instalar). Puede contener uno o más módulos y tiene información sobre de qué otros paquetes depende. Un paquete también suele venir con documentación que explica qué hace para que las personas que no lo escribieron también puedan usarlo.

    -

    Cuando se encuentra un problema en un paquete o se añade una nueva característica, se actualiza el paquete. Ahora los programas que dependen de él (que también pueden ser paquetes) pueden copiar la nueva versión para obtener las mejoras que se hicieron en el código.

    +

    Cuando se encuentra un problema en un paquete o se añade una nueva característica, se actualiza el paquete. Entonces, los programas que dependen de él (que también pueden ser paquetes) pueden copiar la nueva versión para obtener las mejoras que se hicieron en el código.

    -

    Trabajar de esta manera requiere infraestructura. Necesitamos un lugar para almacenar y encontrar paquetes y una forma conveniente de instalar y actualizarlos. En el mundo de JavaScript, esta infraestructura es provista por NPM (https://npmjs.org).

    +

    Trabajar de esta manera requiere infraestructura. Necesitamos un lugar para almacenar y encontrar paquetes y una forma conveniente de instalarlos y actualizarlos. En el mundo de JavaScript, esta infraestructura viene dada por NPM (https://npmjs.org).

    -

    NPM es dos cosas: un servicio en línea donde puedes descargar (y subir) paquetes y un programa (incluido con Node.js) que te ayuda a instalar y gestionarlos.

    +

    NPM es dos cosas: un servicio en línea donde puedes descargar (y subir) paquetes, y un programa (incluido con Node.js) que te ayuda a instalar y gestionarlos.

    -

    En el momento de la escritura, hay más de tres millones de paquetes diferentes disponibles en NPM. Una gran parte de ellos son basura, para ser honesto. Pero casi cada paquete de JavaScript útil y disponible públicamente se puede encontrar en NPM. Por ejemplo, un analizador de archivos INI, similar al que construimos en el Capítulo 9, está disponible bajo el nombre del paquete ini.

    +

    En el momento en que se escribe este libro, hay más de tres millones de paquetes diferentes disponibles en NPM. Una gran parte de ellos son basura, para ser honesto. Pero casi cada paquete de JavaScript útil y disponible públicamente se puede encontrar en NPM. Por ejemplo, un analizador de archivos INI, similar al que construimos en el Capítulo 9, está disponible bajo el nombre de paquete ini.

    -

    Capítulo 20 mostrará cómo instalar tales paquetes localmente usando el programa de línea de comandos npm.

    +

    El Capítulo 20 mostrará cómo instalar tales paquetes localmente usando el programa de línea de comandos npm.

    Tener paquetes de calidad disponibles para descargar es extremadamente valioso. Significa que a menudo podemos evitar reinventar un programa que 100 personas han escrito antes y obtener una implementación sólida y bien probada con solo presionar algunas teclas.

    -

    El software es barato de copiar, por lo que una vez que alguien lo ha escrito, distribuirlo a otras personas es un proceso eficiente. Pero escribirlo en primer lugar es trabajo, y responder a las personas que han encontrado problemas en el código, o que desean proponer nuevas características, es incluso más trabajo.

    +

    El software es barato de copiar, por lo que una vez que alguien lo ha escrito, distribuirlo a otras personas es un proceso eficiente. Pero escribirlo desde el principio es un trabajo, y responder a las personas que han encontrado problemas en el código, o que desean proponer nuevas características, es incluso más trabajo.

    -

    Por defecto, eres el propietario de los derechos de autor del código que escribes, y otras personas solo pueden usarlo con tu permiso. Pero porque algunas personas son amables y porque publicar buen software puede ayudarte a volverte un poco famoso entre los programadores, muchos paquetes se publican bajo una licencia que permite explícitamente a otras personas usarlo.

    +

    Por defecto, eres el propietario de los derechos de autor del código que escribes, y otras personas solo pueden usarlo con tu permiso. Pero como algunas personas son amables y como publicar buen software puede ayudarte a volverte un poco famoso entre los programadores, muchos paquetes se publican bajo una licencia que permite explícitamente a otras personas usarlo.

    La mayoría del código en NPM tiene esta licencia. Algunas licencias requieren que también publiques el código que construyes sobre el paquete bajo la misma licencia. Otros son menos exigentes, simplemente requiriendo que mantengas la licencia con el código al distribuirlo. La comunidad de JavaScript mayormente utiliza este último tipo de licencia. Al usar paquetes de otras personas, asegúrate de estar al tanto de su licencia.

    @@ -122,9 +122,9 @@

    Módulos CommonJS

    -

    Antes de 2015, cuando el lenguaje de JavaScript no tenía un sistema de módulos integrado real, las personas ya estaban construyendo sistemas grandes en JavaScript. Para que funcionara, ellos necesitaban módulos.

    +

    Antes de 2015, cuando el lenguaje de JavaScript no tenía un sistema de módulos integrado real, las personas ya estaban construyendo sistemas grandes en JavaScript. Para que funcionara, necesitaban módulos.

    -

    La comunidad diseñó sus propios sistemas de módulos improvisados sobre el lenguaje. Estos utilizan funciones para crear un alcance local para los módulos y objetos regulares para representar interfaces de módulos.

    +

    La comunidad diseñó sus propios sistemas de módulos improvisados sobre el lenguaje. Estos utilizan funciones para crear un alcance local para los módulos y objetos normales para representar interfaces de módulos.

    Inicialmente, las personas simplemente envolvían manualmente todo su módulo en una “expresión de función invocada inmediatamente” para crear el alcance del módulo, y asignaban sus objetos de interfaz a una única variable global.

    @@ -144,9 +144,9 @@

    Si implementamos nuestro propio cargador de módulos, podemos hacerlo mejor. El enfoque más ampliamente utilizado para los módulos de JavaScript agregados se llama Módulos CommonJS. Node.js lo utilizaba desde el principio (aunque ahora también sabe cómo cargar módulos ES) y es el sistema de módulos utilizado por muchos paquetes en NPM.

    -

    Un módulo CommonJS se ve como un script regular, pero tiene acceso a dos enlaces que utiliza para interactuar con otros módulos. El primero es una función llamada require. Cuando llamas a esto con el nombre del módulo de tu dependencia, se asegura de que el módulo esté cargado y devuelve su interfaz. El segundo es un objeto llamado exports, que es el objeto de interfaz para el módulo. Comienza vacío y agregas propiedades para definir los valores exportados.

    +

    Un módulo CommonJS parece un script normal, pero tiene acceso a dos asociaciones que utiliza para interactuar con otros módulos. El primero es una función llamada require. Cuando llamas a esto con el nombre del módulo de tu dependencia, se asegura de que el módulo esté cargado y devuelve su interfaz. El segundo es un objeto llamado exports, que es el objeto de interfaz para el módulo. Comienza vacío y agregas propiedades para definir los valores exportados.

    -

    Este módulo de ejemplo CommonJS proporciona una función de formateo de fechas. Utiliza dos packages de NPM: ordinal para convertir números en strings como "1st" y "2nd", y date-names para obtener los nombres en inglés de los días de la semana y los meses. Exporta una única función, formatDate, que recibe un objeto Date y una cadena template.

    +

    Este módulo de ejemplo CommonJS proporciona una función de formateo de fechas. Utiliza dos paquetes de NPM: ordinal para convertir números en strings como "1st" y "2nd", y date-names para obtener los nombres en inglés de los días de la semana y los meses. Exporta una única función, formatDate, que recibe un objeto Date y una cadena template.

    La cadena de template puede contener códigos que indican el formato, como YYYY para el año completo y Do para el día ordinal del mes. Puede pasársele una cadena como "MMMM Do YYYY" para obtener una salida como “22 de noviembre de 2017”.

    @@ -164,7 +164,7 @@

    -

    La interfaz de ordinal es una única función, mientras que date-names exporta un objeto que contiene múltiples cosas: days y months son arrays de nombres. La técnica de desestructuración es muy conveniente al crear enlaces para las interfaces importadas.

    +

    La interfaz de ordinal es una única función, mientras que date-names exporta un objeto que contiene múltiples cosas: days y months son arrays de nombres. La técnica de desestructuración es muy conveniente al crear asociaciones para las interfaces importadas.

    El módulo añade su función de interfaz a exports para que los módulos que dependen de él tengan acceso a ella. Podemos usar el módulo de la siguiente manera:

    @@ -193,66 +193,66 @@

    JavaScript estándar no proporciona una función como readFile, pero diferentes entornos de JavaScript, como el navegador y Node.js, proporcionan sus propias formas de acceder a los archivos. El ejemplo simplemente simula que readFile existe.

    -

    Para evitar cargar el mismo módulo múltiples veces, require mantiene una tienda (caché) de módulos ya cargados. Cuando se llama, primero comprueba si el módulo solicitado ha sido cargado y, si no, lo carga. Esto implica leer el código del módulo, envolverlo en una función y llamarlo.

    +

    Para evitar cargar el mismo módulo múltiples veces, require mantiene un almacenamiento (caché) de módulos ya cargados. Cuando se llama, primero comprueba si el módulo solicitado ha sido cargado y, si no, lo carga. Esto implica leer el código del módulo, envolverlo en una función y llamarlo.

    Al definir require, exports como parámetros para la función de envoltura generada (y pasar los valores apropiados al llamarla), el cargador se asegura de que estos enlaces estén disponibles en el ámbito del módulo.

    Una diferencia importante entre este sistema y los módulos ES es que las importaciones de módulos ES suceden antes de que comience a ejecutarse el script de un módulo, mientras que require es una función normal, invocada cuando el módulo ya está en ejecución. A diferencia de las declaraciones import, las llamadas a require pueden aparecer dentro de funciones, y el nombre de la dependencia puede ser cualquier expresión que se evalúe a una cadena, mientras que import solo permite cadenas simples entre comillas.

    -

    La transición de la comunidad de JavaScript desde el estilo CommonJS a los módulos ES ha sido lenta y algo complicada. Pero afortunadamente, ahora estamos en un punto en el que la mayoría de los paquetes populares en NPM proporcionan su código como módulos ES, y Node.js permite que los módulos ES importen desde módulos CommonJS. Por lo tanto, si bien el código CommonJS es algo con lo que te encontrarás, ya no hay una razón real para escribir nuevos programas en este estilo.

    +

    La transición de la comunidad de JavaScript desde el estilo CommonJS a los módulos ES ha sido lenta y algo complicada. Pero afortunadamente, ahora estamos en un punto en el que la mayoría de los paquetes populares en NPM proporcionan su código como módulos ES, y Node.js permite que los módulos ES importen desde módulos CommonJS. Por lo tanto, si bien el código CommonJS es algo con lo que te encontrarás, ya no hay una razón real para escribir nuevos programas de esta manera.

    Compilación y empaquetado

    -

    Muchos paquetes de JavaScript no están, técnicamente, escritos en JavaScript. Hay extensiones, como TypeScript, el dialecto de verificación de tipos mencionado en el Capítulo 8, que se utilizan ampliamente. A menudo, las personas también comienzan a usar extensiones planeadas para el lenguaje mucho antes de que se agreguen a las plataformas que realmente ejecutan JavaScript.

    +

    Muchos paquetes de JavaScript no están, técnicamente, escritos en JavaScript. Hay extensiones, como TypeScript, el dialecto de verificación de tipos mencionado en el Capítulo 8, que se utilizan ampliamente. A menudo, la gente también comienza a usar extensiones planeadas para el lenguaje mucho antes de que se agreguen a las plataformas que realmente ejecutan JavaScript.

    Para hacer esto posible, compilan su código, traduciéndolo desde su dialecto de JavaScript elegido a JavaScript antiguo, e incluso a una versión anterior de JavaScript, para que los navegadores puedan ejecutarlo.

    Incluir un programa modular que consta de 200 archivos diferentes en una página web produce sus propios problemas. Si recuperar un solo archivo a través de la red lleva 50 milisegundos, cargar todo el programa lleva 10 segundos, o quizás la mitad de eso si puedes cargar varios archivos simultáneamente. Eso es mucho tiempo desperdiciado. Como recuperar un solo archivo grande tiende a ser más rápido que recuperar muchos archivos pequeños, los programadores web han comenzado a usar herramientas que combinan sus programas (que dividieron minuciosamente en módulos) en un solo archivo grande antes de publicarlo en la Web. Estas herramientas se llaman bundlers.

    -

    Y podemos ir más allá. Aparte del número de archivos, el tamaño de los archivos también determina qué tan rápido pueden ser transferidos a través de la red. Por lo tanto, la comunidad de JavaScript ha inventado minificadores. Estas son herramientas que toman un programa de JavaScript y lo hacen más pequeño al eliminar automáticamente comentarios y espacios en blanco, renombrar enlaces y reemplazar fragmentos de código con código equivalente que ocupa menos espacio.

    +

    Y podemos ir más allá. Aparte del número de archivos, el tamaño de los archivos también determina qué tan rápido pueden ser transferidos a través de la red. Por lo tanto, la comunidad de JavaScript ha inventado minificadores. Estos son herramientas que toman un programa de JavaScript y lo hacen más pequeño al eliminar automáticamente comentarios y espacios en blanco, renombrar asociaciones y reemplazar fragmentos de código con código equivalente que ocupa menos espacio.

    -

    Por lo tanto, no es raro que el código que encuentres en un paquete de NPM o que se ejecute en una página web haya pasado por múltiples etapas de transformación, convirtiéndose desde JavaScript moderno a JavaScript histórico, luego combinando los módulos en un solo archivo, y minimizando el código. No entraremos en detalles sobre estas herramientas en este libro ya que hay muchas de ellas, y cuál es popular cambia regularmente. Simplemente ten en cuenta que tales cosas existen, y búscalas cuando las necesites.

    +

    Por lo tanto, no es raro que el código que encuentres en un paquete de NPM o que se ejecute en una página web haya pasado por múltiples etapas de transformación, convirtiéndose desde JavaScript moderno a JavaScript histórico, luego combinando los módulos en un solo archivo, y minimizando el código. No entraremos en detalles sobre estas herramientas en este libro ya que hay muchas de ellas, y cuál se usa más es algo que cambia regularmente. Simplemente ten en cuenta que tales cosas existen, y búscalas cuando las necesites.

    Diseño de módulos

    Estructurar programas es uno de los aspectos más sutiles de la programación. Cualquier funcionalidad no trivial puede ser organizada de diversas formas.

    -

    Un buen diseño de programa es subjetivo—hay compensaciones implicadas y cuestiones de gusto. La mejor manera de aprender el valor de un diseño bien estructurado es leer o trabajar en muchos programas y notar qué funciona y qué no. No asumas que un desorden doloroso es “simplemente así”. Puedes mejorar la estructura de casi todo pensando más detenidamente en ello.

    +

    Un buen diseño de programa es subjetivo —hay compensaciones implicadas y cuestiones de gusto. La mejor manera de aprender el valor de un diseño bien estructurado es leer o trabajar en muchos programas y notar qué funciona y qué no. No asumas que un código horrible es “simplemente así”. Puedes mejorar la estructura de casi todo pensando más detenidamente en ello.

    -

    Un aspecto del diseño de módulos es la facilidad de uso. Si estás diseñando algo que se supone será utilizado por varias personas—o incluso por ti mismo, dentro de tres meses cuando ya no recuerdes los detalles de lo que hiciste—es útil que tu interfaz sea simple y predecible.

    +

    Un aspecto del diseño de módulos es la facilidad de uso. Si estás diseñando algo que se supone será utilizado por varias personas —o incluso por ti mismo, dentro de tres meses cuando ya no recuerdes los detalles de lo que hiciste— es útil que tu interfaz sea simple y predecible.

    Eso puede significar seguir convenciones existentes. Un buen ejemplo es el paquete ini. Este módulo imita el objeto estándar JSON al proporcionar funciones parse y stringify (para escribir un archivo INI), y, como JSON, convierte entre cadenas y objetos simples. Por lo tanto, la interfaz es pequeña y familiar, y después de haber trabajado con ella una vez, es probable que recuerdes cómo usarla.

    -

    Incluso si no hay una función estándar o paquete ampliamente utilizado para imitar, puedes mantener tus módulos predecibles utilizando estructuras de datos simples y haciendo una sola cosa enfocada. Muchos de los módulos de análisis de archivos INI en NPM proporcionan una función que lee directamente dicho archivo desde el disco duro y lo analiza, por ejemplo. Esto hace imposible usar dichos módulos en el navegador, donde no tenemos acceso directo al sistema de archivos, y añade complejidad que hubiera sido mejor abordada componiendo el módulo con alguna función de lectura de archivos.

    +

    Incluso si no hay una función estándar o paquete ampliamente utilizado para imitar, puedes mantener tus módulos predecibles utilizando estructuras de datos simples y haciendo una sola cosa muy concreta. Muchos de los módulos de análisis de archivos INI en NPM proporcionan una función que lee directamente dicho archivo desde el disco duro y lo analiza, por ejemplo. Esto hace imposible usar dichos módulos en el navegador, donde no tenemos acceso directo al sistema de archivos, y añade complejidad que hubiera sido mejor abordada componiendo el módulo con alguna función de lectura de archivos.

    -

    Esto señala otro aspecto útil del diseño de módulos—la facilidad con la que algo puede ser compuesto con otro código. Los módulos enfocados en calcular valores son aplicables en una gama más amplia de programas que los módulos más grandes que realizan acciones complicadas con efectos secundarios. Un lector de archivos INI que insiste en leer el archivo desde el disco es inútil en un escenario donde el contenido del archivo proviene de otra fuente.

    +

    Esto señala otro aspecto útil del diseño de módulos —la facilidad con la que algo puede ser compuesto con otro código. Los módulos enfocados en calcular valores son aplicables en una gama más amplia de programas que los módulos más grandes que realizan acciones complicadas con efectos secundarios. Un lector de archivos INI que insiste en leer el archivo desde el disco es inútil en un escenario donde el contenido del archivo proviene de otra fuente.

    Relacionado con esto, a veces los objetos con estado son útiles o incluso necesarios, pero si algo se puede hacer con una función, utiliza una función. Varios de los lectores de archivos INI en NPM proporcionan un estilo de interfaz que requiere que primero crees un objeto, luego cargues el archivo en tu objeto, y finalmente uses métodos especializados para acceder a los resultados. Este tipo de enfoque es común en la tradición orientada a objetos, y es terrible. En lugar de hacer una sola llamada a función y continuar, debes realizar el ritual de mover tu objeto a través de sus diversos estados. Y debido a que los datos están envueltos en un tipo de objeto especializado, todo el código que interactúa con él debe conocer ese tipo, creando interdependencias innecesarias.

    A menudo, no se puede evitar definir nuevas estructuras de datos, ya que el estándar del lenguaje proporciona solo algunas básicas, y muchos tipos de datos deben ser más complejos que un array o un mapa. Pero cuando un array es suficiente, utiliza un array.

    -

    Un ejemplo de una estructura de datos ligeramente más compleja es el grafo de Capítulo 7. No hay una forma única obvia de representar un grafo en JavaScript. En ese capítulo, utilizamos un objeto cuyas propiedades contienen arrays de strings: los otros nodos alcanzables desde ese nodo.

    +

    Un ejemplo de una estructura de datos ligeramente más compleja es el grafo de Capítulo 7. No hay una única forma obvia de representar un grafo en JavaScript. En ese capítulo, utilizamos un objeto cuyas propiedades contienen arrays de strings: los otros nodos alcanzables desde ese nodo.

    -

    Existen varios paquetes de búsqueda de rutas en NPM, pero ninguno de ellos utiliza este formato de grafo. Por lo general, permiten que las aristas del grafo tengan un peso, que es el costo o la distancia asociada a ellas. Eso no es posible en nuestra representación.

    +

    Existen varios paquetes de búsqueda de rutas en NPM, pero ninguno de ellos utiliza este formato de grafo. Por lo general, permiten que las aristas del grafo tengan un peso, que es el coste o la distancia asociados a ellas. Eso no es posible en nuestra representación.

    Por ejemplo, está el paquete dijkstrajs. Un enfoque conocido para la búsqueda de rutas, bastante similar a nuestra función findRoute, se llama algoritmo de Dijkstra, en honor a Edsger Dijkstra, quien lo escribió por primera vez. A menudo se agrega el sufijo js a los nombres de los paquetes para indicar que están escritos en JavaScript. Este paquete dijkstrajs utiliza un formato de grafo similar al nuestro, pero en lugar de arrays, utiliza objetos cuyos valores de propiedad son números, los pesos de las aristas.

    -

    Por lo tanto, si quisiéramos usar ese paquete, deberíamos asegurarnos de que nuestro grafo esté almacenado en el formato que espera. Todas las aristas tienen el mismo peso, ya que nuestro modelo simplificado trata cada camino como teniendo el mismo coste (una vuelta).

    +

    Por lo tanto, si quisiéramos usar ese paquete, deberíamos asegurarnos de que nuestro grafo esté almacenado en el formato que espera. Todas las aristas tienen el mismo peso, ya que nuestro modelo simplificado trata cada camino como teniendo el mismo coste (un paso).

    -
    const {find_path} = require("dijkstrajs");
    +
    const {find_path} = require("dijkstrajs");
     
    -let graph = {};
    -for (let node of Object.keys(roadGraph)) {
    -  let edges = graph[node] = {};
    -  for (let dest of roadGraph[node]) {
    -    edges[dest] = 1;
    +let grafo = {};
    +for (let nodo of Object.keys(roadGraph)) {
    +  let aristas = grafo[nodo] = {};
    +  for (let dest of roadGraph[nodo]) {
    +    aristas[dest] = 1;
       }
     }
     
    -console.log(find_path(graph, "Oficina de Correos", "Cabaña"));
    +console.log(find_path(grafo, "Oficina de Correos", "Cabaña"));
     // → ["Oficina de Correos", "Casa de Alicia", "Cabaña"]
    -

    Esto puede ser una barrera para la composición: cuando varios paquetes están utilizando diferentes estructuras de datos para describir cosas similares, combinarlos es difícil. Por lo tanto, si deseas diseñar para la composabilidad, averigua qué estructuras de datos están utilizando otras personas y, cuando sea posible, sigue su ejemplo.

    +

    Esto puede ser una barrera para la composición: cuando varios paquetes están utilizando diferentes estructuras de datos para describir cosas similares, combinarlos es difícil. Por lo tanto, si deseas diseñar de cara a la composabilidad, averigua qué estructuras de datos están utilizando otras personas y, cuando sea posible, sigue su ejemplo.

    Diseñar una estructura de módulo adecuada para un programa puede ser difícil. En la fase en la que aún estás explorando el problema, probando diferentes cosas para ver qué funciona, es posible que no quieras preocuparte demasiado por esto, ya que mantener todo organizado puede ser una gran distracción. Una vez que tengas algo que se sienta sólido, es un buen momento para dar un paso atrás y organizarlo.

    @@ -262,13 +262,13 @@

    Dado que JavaScript históricamente no proporcionaba un sistema de módulos, se construyó el sistema CommonJS sobre él. Luego, en algún momento obtuvo un sistema incorporado, que ahora coexiste incómodamente con el sistema CommonJS.

    -

    Un paquete es un fragmento de código que se puede distribuir por sí solo. NPM es un repositorio de paquetes de JavaScript. Puedes descargar todo tipo de paquetes útiles (y inútiles) desde aquí.

    +

    Un paquete es un fragmento de código que se puede distribuir por sí solo. NPM es un repositorio de paquetes de JavaScript. Puedes descargar todo tipo de paquetes útiles (e inútiles) desde aquí.

    Ejercicios

    Un robot modular

    -

    Estos son los enlaces que crea el proyecto del Capítulo 7:

    +

    Estas son las asociaciones que crea el proyecto del Capítulo 7:

    roads
     buildGraph
    @@ -290,25 +290,25 @@ 

    Esto es lo que habría hecho (pero de nuevo, no hay una única forma correcta de diseñar un módulo dado):

    -

    El código utilizado para construir el gráfico de carreteras se encuentra en el módulo graph. Como preferiría usar dijkstrajs de NPM en lugar de nuestro propio código de búsqueda de caminos, haremos que este construya el tipo de datos de gráfico que espera dijkstrajs. Este módulo exporta una única función, buildGraph. Haría que buildGraph aceptara un arreglo de arreglos de dos elementos, en lugar de cuerdas que contienen guiones, para hacer que el módulo dependa menos del formato de entrada.

    +

    El código utilizado para construir el grafo de carreteras se encuentra en el módulo graph. Como preferiría usar dijkstrajs de NPM en lugar de nuestro propio código de búsqueda de caminos, haremos que este construya el tipo de datos de grafo que espera dijkstrajs. Este módulo exporta una única función, buildGraph. Haría que buildGraph aceptara un array de arrays de dos elementos, en lugar de cadenas que contienen guiones, para hacer que el módulo dependa menos del formato de entrada.

    -

    El módulo roads contiene los datos crudos de las carreteras (el arreglo roads) y el enlace roadGraph. Este módulo depende de ./graph.js y exporta el grafo de carreteras.

    +

    El módulo roads contiene los datos en bruto de las carreteras (el array roads) y el enlace roadGraph. Este módulo depende de ./graph.js y exporta el grafo de carreteras.

    La clase VillageState se encuentra en el módulo state. Depende del módulo ./roads porque necesita poder verificar que una carretera dada exista. También necesita randomPick. Dado que es una función de tres líneas, podríamos simplemente ponerla en el módulo state como una función auxiliar interna. Pero randomRobot también la necesita. Entonces tendríamos que duplicarla o ponerla en su propio módulo. Dado que esta función existe en NPM en el paquete random-item, una solución razonable es hacer que ambos módulos dependan de eso. También podemos agregar la función runRobot a este módulo, ya que es pequeña y está relacionada con la gestión del estado. El módulo exporta tanto la clase VillageState como la función runRobot.

    -

    Finalmente, los robots, junto con los valores en los que dependen, como mailRoute, podrían ir en un módulo example-robots, que depende de ./roads y exporta las funciones del robot. Para que goalOrientedRobot pueda realizar la búsqueda de rutas, este módulo también depende de dijkstrajs.Al externalizar cierto trabajo a módulos NPM, el código se volvió un poco más pequeño. Cada módulo individual hace algo bastante simple y se puede leer por sí solo. Dividir el código en módulos a menudo sugiere mejoras adicionales en el diseño del programa. En este caso, parece un poco extraño que el VillageState y los robots dependan de un gráfico de caminos específico. Podría ser una mejor idea hacer que el gráfico sea un argumento del constructor de estado y hacer que los robots lo lean desde el objeto de estado, esto reduce las dependencias (lo cual siempre es bueno) y hace posible ejecutar simulaciones en mapas diferentes (lo cual es aun mejor).

    +

    Finalmente, los robots, junto con los valores en los que dependen, como mailRoute, podrían ir en un módulo example-robots, que depende de ./roads y exporta las funciones del robot. Para que goalOrientedRobot pueda realizar la búsqueda de rutas, este módulo también depende de dijkstrajs.Al externalizar cierto trabajo a módulos NPM, el código se volvió un poco más pequeño. Cada módulo individual hace algo bastante simple y se puede leer por sí solo. Dividir el código en módulos a menudo sugiere mejoras adicionales en el diseño del programa. En este caso, parece un poco extraño que el VillageState y los robots dependan de un grafo de caminos específico. Podría ser una mejor idea hacer que el grafo sea un argumento del constructor de estado y hacer que los robots lo lean desde el objeto de estado, esto reduce las dependencias (lo cual siempre es bueno) y hace posible ejecutar simulaciones en mapas diferentes (lo cual es aun mejor).

    ¿Es una buena idea utilizar módulos de NPM para cosas que podríamos haber escrito nosotros mismos? En principio, sí, para cosas no triviales como la función de búsqueda de caminos es probable que cometas errores y pierdas tiempo escribiéndolas tú mismo. Para funciones pequeñas como random-item, escribirlas por ti mismo es bastante fácil. Pero añadirlas donde las necesitas tiende a saturar tus módulos.

    -

    Sin embargo, tampoco debes subestimar el trabajo involucrado en encontrar un paquete de NPM apropiado. Y aunque encuentres uno, podría no funcionar bien o le podrían faltar alguna característica que necesitas. Además, depender de paquetes de NPM significa que debes asegurarte de que estén instalados, debes distribuirlos con tu programa y es posible que debas actualizarlos periódicamente.

    +

    Sin embargo, tampoco debes subestimar el trabajo empleado en encontrar un paquete de NPM apropiado. Y aunque encuentres uno, podría no funcionar bien o le podría faltar alguna característica que necesitas. Además, depender de paquetes de NPM significa que debes asegurarte de que estén instalados, debes distribuirlos con tu programa y es posible que debas actualizarlos periódicamente.

    -

    Así que de nuevo, esto es un compromiso, y puedes decidir de cualquier manera dependiendo de cuánto te ayude realmente un paquete dado.

    +

    Así que de nuevo, esto es un compromiso, y puedes decidirte por cualquier opción dependiendo de cuánto te ayude realmente un paquete dado.

    Módulo de caminos

    -

    Escribe un módulo ES, basado en el ejemplo del Capítulo 7, que contenga el array de caminos y exporte la estructura de datos de gráfico que los representa como roadGraph. Debería depender de un módulo ./graph.js, que exporta una función buildGraph que se utiliza para construir el gráfico. Esta función espera un array de arrays de dos elementos (los puntos de inicio y fin de los caminos).

    +

    Escribe un módulo ES, basado en el ejemplo del Capítulo 7, que contenga el array de caminos y exporte la estructura de datos de grafo que los representa como roadGraph. Debería depender de un módulo ./graph.js, que exporta una función buildGraph que se utiliza para construir el grafo. Esta función espera un array de arrays de dos elementos (los puntos de inicio y fin de los caminos).

    // Añade dependencias y exportaciones
     
    @@ -331,7 +331,7 @@ 

    Mostrar pistas...
    -

    Dado que este es un módulo ES, debes usar import para acceder al módulo de gráfico. Esto se describió como exportando una función de buildGraph, la cual puedes seleccionar de su objeto de interfaz con una declaración de desestructuración const.

    +

    Dado que este es un módulo ES, debes usar import para acceder al módulo de grafo. Esto se describió como exportando una función de buildGraph, la cual puedes seleccionar de su objeto de interfaz con una declaración de desestructuración const.

    Para exportar roadGraph, colocas la palabra clave export antes de su definición. Debido a que buildGraph toma una estructura de datos que no coincide exactamente con roads, la división de las cadenas de carretera debe ocurrir en tu módulo.

    From af767e456a469a725ce0a19c53a4e39a3ec804aa Mon Sep 17 00:00:00 2001 From: ckdvk Date: Tue, 18 Feb 2025 20:04:25 +0800 Subject: [PATCH 22/36] =?UTF-8?q?revisado=20cap=C3=ADtulo=2011.=20Retocado?= =?UTF-8?q?s=20algunas=20traducciones=20de=20otros=20cap=C3=ADtulos.=20Tam?= =?UTF-8?q?bi=C3=A9n=20c=C3=B3digo=20ejecutable=20y=20algunas=20variables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 11_async.md | 424 +++++++++++++++++++++++---------------------- 16_game.md | 2 +- 20_node.md | 8 +- 21_skillsharing.md | 2 +- code/hangar2.js | 6 +- html/11_async.html | 416 +++++++++++++++++++++++--------------------- html/20_node.html | 4 +- 7 files changed, 443 insertions(+), 419 deletions(-) diff --git a/11_async.md b/11_async.md index 12022481..c89482c0 100644 --- a/11_async.md +++ b/11_async.md @@ -13,53 +13,53 @@ quote}} {{figure {url: "img/chapter_picture_11.jpg", alt: "Ilustración que muestra dos cuervos en una rama de árbol", chapter: framed}}} -La parte central de una computadora, la parte que lleva a cabo los pasos individuales que componen nuestros programas, se llama el _((procesador))_. Los programas que hemos visto hasta ahora mantendrán ocupado al procesador hasta que hayan terminado su trabajo. La velocidad a la cual algo como un bucle que manipula números puede ser ejecutado depende casi enteramente de la velocidad del procesador y la memoria de la computadora. +La parte central de una computadora, la parte que lleva a cabo los pasos individuales que componen nuestros programas, se llama _((procesador))_. Los programas que hemos visto hasta ahora mantendrán ocupado al procesador hasta que hayan terminado su trabajo. La velocidad a la cual puede ser ejecutado algo como un bucle que manipula números depende casi enteramente de la velocidad del procesador y la memoria de la computadora. {{index [memoria, velocidad], [red, velocidad]}} -Pero muchos programas interactúan con cosas fuera del procesador. Por ejemplo, pueden comunicarse a través de una red de computadoras o solicitar datos desde el ((disco duro)), lo cual es mucho más lento que obtenerlo de la memoria. +Pero muchos programas interactúan con cosas fuera del procesador. Por ejemplo, pueden comunicarse a través de una red de computadoras o solicitar datos desde el ((disco duro)), lo cual es mucho más lento que obtenerlos de la memoria. -Cuando esto está sucediendo, sería una lástima dejar el procesador inactivo, ya que podría haber otro trabajo que podría hacer en ese tiempo. En parte, esto es manejado por tu sistema operativo, el cual cambiará el procesador entre múltiples programas en ejecución. Pero eso no ayuda cuando queremos que un _único_ programa pueda avanzar mientras espera una solicitud de red. +Cuando esto está sucediendo, sería una lástima dejar el procesador inactivo: podría haber otro trabajo que este podría hacer en ese tiempo. En parte, esto es algo que maneja tu sistema operativo, el cual irá dándole al procesador múltiples programas en ejecución, haciendo que vaya cambiando entre ellos. Pero eso no ayuda cuando queremos que un _único_ programa pueda avanzar mientras espera una solicitud de red. ## Asincronía {{index "programación sincrónica"}} -En un modelo de programación _sincrónico_, las cosas suceden una a la vez. Cuando llamas a una función que realiza una acción de larga duración, solo devuelve cuando la acción ha terminado y puede devolver el resultado. Esto detiene tu programa durante el tiempo que tome la acción. +En un modelo de programación _sincrónico_, las cosas suceden una a una. Cuando llamas a una función que realiza una acción de larga duración, esta solo retorna cuando la acción ha terminado y puede devolver su resultado. Esto detiene tu programa durante el tiempo que tome la acción. -{{index "programación asincrónica"}} +{{index "programación asíncrona"}} -Un modelo _asincrónico_ permite que múltiples cosas sucedan al mismo tiempo. Cuando inicias una acción, tu programa continúa ejecutándose. Cuando la acción termina, el programa es informado y obtiene acceso al resultado (por ejemplo, los datos leídos desde el disco). +Un modelo _asíncrono_ permite que múltiples cosas sucedan al mismo tiempo. Cuando inicias una acción, tu programa continúa ejecutándose. Cuando la acción termina, el programa es informado y obtiene acceso al resultado (por ejemplo, los datos leídos desde el disco). -Podemos comparar la programación sincrónica y asincrónica usando un pequeño ejemplo: un programa que realiza dos solicitudes a través de la ((red)) y luego combina los resultados. +Podemos comparar la programación sincrónica y asíncrona usando un pequeño ejemplo: un programa que realiza dos solicitudes a través de la ((red)) y luego combina de algún modo los resultados. {{index "programación sincrónica"}} -En un entorno sincrónico, donde la función de solicitud devuelve solo después de haber hecho su trabajo, la forma más fácil de realizar esta tarea es hacer las solicitudes una después de la otra. Esto tiene la desventaja de que la segunda solicitud se iniciará solo cuando la primera haya terminado. El tiempo total tomado será al menos la suma de los dos tiempos de respuesta. +En un entorno sincrónico, donde la función de solicitud retorna solo después de haber hecho su trabajo, la forma más fácil de realizar esta tarea es hacer las solicitudes una después de la otra. Esto tiene la desventaja de que la segunda solicitud se iniciará solo cuando la primera haya terminado. El tiempo total necesario será al menos la suma de los dos tiempos de respuesta. {{index paralelismo}} -La solución a este problema, en un sistema sincrónico, es iniciar ((hebra))s de control adicionales. Una _hebra_ es otro programa en ejecución cuya ejecución puede ser intercalada con otros programas por el sistema operativo, ya que la mayoría de las computadoras modernas contienen múltiples procesadores, múltiples hebras incluso podrían ejecutarse al mismo tiempo, en diferentes procesadores. Una segunda hebra podría iniciar la segunda solicitud, y luego ambas hebras esperan que sus resultados regresen, después de lo cual se resincronizan para combinar sus resultados. +La solución a este problema, en un sistema sincrónico, es iniciar ((hilo))s de control adicionales. Un _hilo_ es otro programa en ejecución cuya ejecución puede ser intercalada con otros programas por el sistema operativo —como la mayoría de las computadoras modernas contienen múltiples procesadores, podrían ejecutarse incluso múltiples hilos al mismo tiempo, en diferentes procesadores. Un segundo hilo podría iniciar la segunda solicitud, y luego ambos hilos podrían esperar sus resultados, después de lo cual se resincronizan para combinarlos. {{index CPU, bloqueo, "programación asíncrona", "línea de tiempo", "función de devolución de llamada"}} -En el siguiente diagrama, las líneas gruesas representan el tiempo que el programa pasa funcionando normalmente, y las líneas delgadas representan el tiempo gastado esperando a la red. En el modelo síncrono, el tiempo tomado por la red es _parte_ de la línea de tiempo para un hilo de control dado. En el modelo asíncrono, iniciar una acción en la red permite que el programa continúe ejecutándose mientras la comunicación en la red sucede junto a él, notificando al programa cuando haya terminado. +En el siguiente diagrama, las líneas gruesas representan el tiempo que el programa pasa funcionando normalmente, y las líneas delgadas representan el tiempo gastado esperando a la red. En el modelo sincrónico, el tiempo tomado por la red es _parte_ de la línea de tiempo para un hilo de control dado. En el modelo asíncrono, iniciar una acción en la red permite que el programa continúe ejecutándose mientras la comunicación en la red sucede junto a él, notificando al programa cuando haya terminado. -{{figure {url: "img/control-io.svg", alt: "Diagrama que muestra el flujo de control en programas síncronos y asíncronos. La primera parte muestra un programa síncrono, donde las fases activas y de espera del programa ocurren en una única línea secuencial. La segunda parte muestra un programa síncrono multi-hilo, con dos líneas paralelas en las cuales las partes de espera suceden una al lado de la otra, haciendo que el programa termine más rápido. La última parte muestra un programa asíncrono, donde las múltiples acciones asíncronas se ramifican desde el programa principal, el cual se detiene en algún momento y luego continúa cuando la primera cosa por la que estaba esperando finaliza.", width: "8cm"}}} +{{figure {url: "img/control-io.svg", alt: "Diagrama que muestra el flujo de control en programas sincrónicos y asíncronos. La primera parte muestra un programa sincrónico, donde las fases activas y de espera del programa ocurren en una única línea secuencial. La segunda parte muestra un programa sincrónico multi-hilo, con dos líneas paralelas en las cuales las partes de espera suceden una al lado de la otra, haciendo que el programa termine más rápido. La última parte muestra un programa asíncrono, donde las múltiples acciones asíncronas se ramifican desde el programa principal, el cual se detiene en algún momento y luego continúa cuando la primera cosa por la que estaba esperando finaliza.", width: "8cm"}}} {{index ["flujo de control", "asíncrono"], "programación asíncrona", verbosidad, rendimiento}} -Otra forma de describir la diferencia es que esperar a que las acciones terminen es _implícito_ en el modelo síncrono, mientras que es _explícito_, bajo nuestro control, en el modelo asíncrono. +Otra forma de describir la diferencia es que esperar a que las acciones terminen es _implícito_ en el modelo sincrónico, mientras que es _explícito_, bajo nuestro control, en el modelo asíncrono. -La asincronía tiene sus pros y sus contras. Facilita la expresión de programas que no encajan en el modelo de control de línea recta, pero también puede hacer que expresar programas que siguen una línea recta sea más complicado. Veremos algunas formas de reducir esta dificultad más adelante en el capítulo. +La asincronía tiene sus pros y sus contras. Facilita la expresión de programas que no encajan en el modelo de control en línea recta, pero también puede hacer que expresar programas que siguen una línea recta sea más complicado. Veremos algunas formas de reducir esta dificultad más adelante en el capítulo. -Tanto las plataformas de programación de JavaScript prominentes —((navegadores)) como ((Node.js))— hacen operaciones que podrían tardar un tiempo de forma asíncrona, en lugar de depender de ((hilos)). Dado que programar con hilos es notoriamente difícil (entender lo que hace un programa es mucho más difícil cuando está haciendo múltiples cosas a la vez), esto generalmente se considera algo bueno. +Las dos plataformas de programación de JavaScript más importantes —((navegadores)) y ((Node.js))— hacen que las operaciones que podrían tardar un tiempo sean asíncronas, en lugar de depender de ((hilos)). Dado que programar con hilos es notoriamente difícil (entender lo que hace un programa es mucho más difícil cuando está haciendo múltiples cosas a la vez), esto generalmente se considera algo bueno. -## Retrollamadas +## Callbacks {{indexsee ["función", "devolución de llamada"], "función de devolución de llamada"}} -Un enfoque para la ((programación asíncrona)) es hacer que las funciones que necesitan esperar por algo tomen un argumento adicional, una _((función de devolución de llamada))_. La función asíncrona inicia algún proceso, configura las cosas para que se llame a la función de devolución de llamada cuando el proceso termine, y luego retorna. +Un enfoque para la ((programación asíncrona)) es hacer que las funciones que necesitan esperar por algo tomen un argumento adicional, una _((función de devolución de llamada))_, o _función de callback_. La función asíncrona inicia algún proceso, configura las cosas para que se llame a la función de callback cuando el proceso termine, y luego retorna. {{index "función setTimeout", espera}} @@ -69,14 +69,14 @@ Como ejemplo, la función `setTimeout`, disponible tanto en Node.js como en los setTimeout(() => console.log("Tick"), 500); ``` -Esperar no suele ser un tipo de trabajo muy importante, pero puede ser muy útil cuando necesitas organizar que algo suceda en un momento determinado o verificar si alguna otra acción está tomando más tiempo del esperado. +Esperar no suele ser una tarea muy importante, pero puede ser muy útil cuando necesitas hacer que algo suceda en un momento determinado o verificar si alguna otra acción está tomando más tiempo del esperado. {{index "función readTextFile"}} -Otro ejemplo de una operación asincrónica común es leer un archivo desde el almacenamiento de un dispositivo. Imagina que tienes una función `readTextFile`, la cual lee el contenido de un archivo como una cadena y lo pasa a una función de devolución de llamada. +Otro ejemplo de operación asíncrona común es leer un archivo desde el almacenamiento de un dispositivo. Imagina que tienes una función `readTextFile`, la cual lee el contenido de un archivo como una cadena y lo pasa a una función de callback. ``` -readTextFile("lista_de_compras.txt", contenido => { +readTextFile("lista_compra.txt", contenido => { console.log(`Lista de Compras:\n${contenido}`); }); // → Lista de Compras: @@ -86,29 +86,29 @@ readTextFile("lista_de_compras.txt", contenido => { La función `readTextFile` no es parte del estándar de JavaScript. Veremos cómo leer archivos en el navegador y en Node.js en capítulos posteriores. -Realizar múltiples acciones asincrónicas en fila usando devoluciones de llamada significa que tienes que seguir pasando nuevas funciones para manejar la continuación de la computación después de las acciones. Así es como podría verse una función asincrónica que compara dos archivos y produce un booleano que indica si su contenido es el mismo. +Realizar múltiples acciones asíncronas en serie usando callbacks implica que debes seguir pasando nuevas funciones para gestionar la continuación del proceso después de cada acción. Esta es la pinta que tendría una función asíncrona que compara dos archivos y produce un booleano que indica si su contenido es el mismo. ``` -function compararArchivos(archivoA, archivoB, devolucionLlamada) { +function compararArchivos(archivoA, archivoB, callback) { readTextFile(archivoA, contenidoA => { readTextFile(archivoB, contenidoB => { - devolucionLlamada(contenidoA == contenidoB); + callback(contenidoA == contenidoB); }); }); } ``` -Este estilo de programación es funcional, pero el nivel de indentación aumenta con cada acción asincrónica porque terminas en otra función. Hacer cosas más complicadas, como envolver acciones asincrónicas en un bucle, puede ser incómodo. +Este estilo de programación es factible, pero el nivel de sangrado aumenta con cada acción asíncrona porque terminas estando en otra función. Cosas más complicadas, como envolver acciones asíncronas en un bucle, pueden volverse muy incómodas. -De alguna manera, la asincronía es contagiosa. Cualquier función que llame a una función que trabaja de forma asincrónica debe ser asincrónica en sí misma, utilizando una devolución de llamada u otro mecanismo similar para entregar su resultado. Llamar a una devolución de llamada es algo más complicado y propenso a errores que simplemente devolver un valor, por lo que necesitar estructurar grandes partes de tu programa de esa manera no es ideal. +De alguna manera, la asincronía es contagiosa. Cualquier función que llame a una función que trabaja de forma asíncrona debe ser asíncrona en sí misma, utilizando un callback u otro mecanismo similar para entregar su resultado. Llamar a una función callback es algo más complicado y propenso a errores que simplemente devolver un valor, por lo que crear la necesidad de estructurar grandes partes de tu programa de esa manera no es ideal. ## Promesas -Una forma ligeramente diferente de construir un programa asincrónico es hacer que las funciones asincrónicas devuelvan un objeto que represente su resultado (futuro) en lugar de pasar devoluciones de llamada por todas partes. De esta manera, tales funciones realmente devuelven algo significativo, y la estructura del programa se asemeja más a la de los programas síncronos. +Una forma ligeramente diferente de construir un programa asíncrono es hacer que las funciones asíncronas devuelvan un objeto que represente su resultado (futuro) en lugar de pasar callbacks por todas partes. De esta manera, tales funciones realmente devuelven algo con sentido, y la estructura del programa se asemeja más a la de los programas sincrónicos. -{{index "clase Promise", "programación asincrónica", "resolviendo (una promesa)", "método then", "función de devolución de llamada"}} +{{index "clase Promise", "programación asíncrona", "resolviendo (una promesa)", "método then", "función de devolución de llamada"}} -Para esto sirve la clase estándar `Promise`. Una _promesa_ es un recibo que representa un valor que aún puede no estar disponible. Proporciona un método `then` que te permite registrar una función que debe ser llamada cuando la acción por la que está esperando finalice. Cuando la promesa se _resuelve_, es decir, su valor se vuelve disponible, esas funciones (puede haber varias) son llamadas con el valor del resultado. Es posible llamar a `then` en una promesa que ya ha sido resuelta; tu función seguirá siendo llamada. +Para esto sirve la clase estándar `Promise`. Una _promesa_ es un recibo que representa un valor que aún puede no estar disponible. Proporciona un método `then` que te permite registrar una función que debe ser llamada cuando la acción por la que está esperando finalice. Cuando la promesa se _resuelve_, es decir, cuando su valor se vuelve disponible, esas funciones (puede haber varias) son llamadas con el valor del resultado. Es posible llamar a `then` en una promesa que ya ha sido resuelta —tu función aún será llamada. {{index "función Promise.resolve"}} @@ -122,66 +122,66 @@ quince.then(valor => console.log(`Obtenido ${valor}`)); {{index "Clase Promise"}} -Para crear una promesa que no se resuelva inmediatamente, puedes utilizar `Promise` como constructor. Tiene una interfaz un tanto extraña: el constructor espera una función como argumento, la cual llama inmediatamente, pasándole una función que puede utilizar para resolver la promesa. +Para crear una promesa que no se resuelva inmediatamente, puedes utilizar `Promise` como constructor. Tiene una interfaz un tanto extraña: el constructor espera una función como argumento, a la cual llama inmediatamente, pasándole como argumento una función (`resolver`, en el ejemplo) que puede utilizar para resolver la promesa. Así es como podrías crear una interfaz basada en promesas para la función `readTextFile`: -{{index "Función textFile"}} +{{index "Función archivoTexto"}} ``` -function textFile(nombreArchivo) { - return new Promise(resolve => { - readTextFile(nombreArchivo, texto => resolve(texto)); +function archivoTexto(nombreArchivo) { + return new Promise(resolver => { + readTextFile(nombreArchivo, texto => resolver(texto)); }); } -textFile("planes.txt").then(console.log); +archivoTexto("planes.txt").then(console.log); ``` -Observa cómo esta función asíncrona devuelve un valor significativo: una promesa para proporcionarte el contenido del archivo en algún momento futuro. +Observa cómo esta función asíncrona devuelve un valor con sentido: una promesa de proporcionarte el contenido del archivo en algún momento futuro. {{index "método then"}} -Una característica útil del método `then` es que él mismo devuelve otra promesa que se resuelve al valor retornado por la función de devolución de llamada o, si esa función devuelve una promesa, al valor al que esa promesa se resuelve. De esta forma, puedes "encadenar" varias llamadas a `then` para configurar una secuencia de acciones asíncronas. +Una característica útil del método `then` es que él mismo devuelve otra promesa que se resuelve al valor retornado por la función de callback o, si esa función devuelve una promesa, al valor al que esa promesa se resuelve. De esta forma, puedes "encadenar" varias llamadas a `then` para configurar una secuencia de acciones asíncronas. Esta función, la cual lee un archivo lleno de nombres de archivos y devuelve el contenido de un archivo aleatorio de esa lista, muestra este tipo de cadena asíncrona de promesas. ``` -function randomFile(archivoLista) { - return textFile(archivoLista) +function archivoAleatorio(archivoLista) { + return archivoTexto(archivoLista) .then(contenido => contenido.trim().split("\n")) .then(ls => ls[Math.floor(Math.random() * ls.length)]) - .then(nombreArchivo => textFile(nombreArchivo)); + .then(nombreArchivo => archivoTexto(nombreArchivo)); } ``` -La función devuelve el resultado de esta cadena de llamadas a `then`. La promesa inicial obtiene la lista de archivos como una cadena. La primera llamada a `then` transforma esa cadena en un array de líneas, produciendo una nueva promesa. La segunda llamada a `then` elige una línea aleatoria de eso, produciendo una tercera promesa que arroja un único nombre de archivo. La llamada final a `then` lee este archivo, de modo que el resultado de la función en su totalidad es una promesa que devuelve el contenido de un archivo aleatorio. +La función devuelve el resultado de esta cadena de llamadas a `then`. La promesa inicial obtiene la lista de archivos como una cadena. La primera llamada a `then` transforma esa cadena en un array de líneas, produciendo una nueva promesa. La segunda llamada a `then` elige una línea aleatoria del resultado de resolver esta promesa, produciendo una tercera promesa que arroja un único nombre de archivo. La llamada final a `then` lee este archivo, de modo que el resultado de la función en su totalidad es una promesa que devuelve el contenido de un archivo aleatorio. -En este código, las funciones utilizadas en las primeras dos llamadas a `then` devuelven un valor regular, que se pasará inmediatamente a la promesa devuelta por `then` cuando la función regrese. La última devuelve una promesa (`textFile(nombreArchivo)`), convirtiéndola en un paso asincrónico real. +En este código, las funciones utilizadas en las primeras dos llamadas a `then` devuelven un valor normal, que se pasará inmediatamente a la promesa devuelta por `then` cuando la función retorne. La última devuelve una promesa (`archivoTexto(nombreArchivo)`), lo que la convierte en un paso asíncrono de verdad. -También habría sido posible realizar todos estos pasos dentro de un solo callback de `then`, ya que solo el último paso es realmente asíncrono. Pero los tipos de envolturas `then` que solo realizan alguna transformación de datos síncrona son a menudo útiles, por ejemplo, cuando deseas devolver una promesa que produzca una versión procesada de algún resultado asíncrono. +También habría sido posible realizar todos estos pasos dentro de un solo callback de `then`, ya que solo el último paso es realmente asíncrono. Pero el tipo de envolturas `then` que solo realizan alguna transformación de datos sincrónica son a menudo útiles, por ejemplo, cuando deseas devolver una promesa que produzca una versión procesada de algún resultado asíncrono. ``` -function jsonFile(nombreArchivo) { - return textFile(nombreArchivo).then(JSON.parse); +function archivoJson(nombreArchivo) { + return archivoTexto(nombreArchivo).then(JSON.parse); } -jsonFile("package.json").then(console.log); +archivoJson("package.json").then(console.log); ``` En general, es útil pensar en las promesas como un mecanismo que permite al código ignorar la pregunta de cuándo va a llegar un valor. Un valor normal tiene que existir realmente antes de que podamos hacer referencia a él. Un valor prometido es un valor que _puede_ estar allí o podría aparecer en algún momento en el futuro. Las operaciones definidas en términos de promesas, al conectarlas con llamadas `then`, se ejecutan de forma asíncrona a medida que sus entradas están disponibles. -## Falla +## Fallo {{index "manejo de excepciones"}} -Las computaciones regulares de JavaScript pueden fallar al lanzar una excepción. Las computaciones asíncronas a menudo necesitan algo así. Una solicitud de red puede fallar, un archivo puede no existir, o algún código que forma parte de la computación asíncrona puede lanzar una excepción. +Un procedimiento normal de JavaScript puede fallar lanzando una excepción. Los procedimientos asíncronos a menudo necesitan algo así. Una solicitud de red puede fallar, un archivo puede no existir, o algún código que forma parte de un procedimiento asíncrono puede lanzar una excepción. {{index "función de devolución de llamada", error}} -Uno de los problemas más apremiantes con el estilo de programación asíncrona basado en devoluciones de llamada es que hace extremadamente difícil asegurarse de que las fallas se informen adecuadamente a las devoluciones de llamada. +Uno de los problemas más urgentes del estilo de programación asíncrona basado en callbacks es que hace extremadamente difícil asegurarse de que los fallos se reporten adecuadamente a las funciones de callback. -Una convención ampliamente utilizada es que el primer argumento de la devolución de llamada se utiliza para indicar que la acción falló, y el segundo contiene el valor producido por la acción cuando fue exitosa. +Una convención ampliamente utilizada es que el primer argumento de la función de callback se utiliza para indicar que la acción ha fallado, y el segundo contiene el valor producido por la acción cuando ha terminado con éxito. ``` unaFuncionAsincrona((error, valor) => { @@ -190,15 +190,15 @@ unaFuncionAsincrona((error, valor) => { }); ``` -Tales funciones de devolución de llamada siempre deben verificar si recibieron una excepción y asegurarse de que cualquier problema que causen, incluidas las excepciones lanzadas por las funciones que llaman, se capturen y se den a la función correcta. +Tales funciones de callback siempre deben verificar si recibieron una excepción y asegurarse de que cualquier problema que causen, incluidas las excepciones lanzadas por las funciones que llaman, se capturen y se den a la función correcta. {{index "rechazar (una promesa)", "resolver (una promesa)", "método then"}} -Las promesas facilitan esto. Pueden ser o bien resueltas (la acción se completó con éxito) o rechazadas (falló). Los manejadores de resolución (como se registran con `then`) se llaman solo cuando la acción es exitosa, y los rechazos se propagan a la nueva promesa que es devuelta por `then`. Cuando un manejador lanza una excepción, esto causa automáticamente que la promesa producida por la llamada a su `then` sea rechazada. Entonces, si algún elemento en una cadena de acciones asíncronas falla, el resultado de toda la cadena se marca como rechazado, y no se llaman manejadores de éxito más allá del punto donde falló. +Las promesas facilitan esto. Pueden ser o bien resueltas (la acción se completó con éxito) o rechazadas (la acción falló). Los manejadores de resolución (registrados con `then`) se llaman solo cuando la acción es exitosa, y los rechazos se propagan a la nueva promesa devuelta por `then`. Cuando un manejador lanza una excepción, esto causa automáticamente que la promesa producida por su llamada a `then` sea rechazada. Entonces, si algún elemento en una cadena de acciones asíncronas falla, el resultado de toda la cadena se marca como rechazado, y ningún manejador de éxito se ejecuta más allá del punto en el que ocurrió el fallo. {{index "función Promise.reject", "clase Promise"}} -Al igual que resolver una promesa proporciona un valor, rechazar una también lo hace, generalmente llamado el _motivo_ del rechazo. Cuando una excepción en una función manejadora causa el rechazo, el valor de la excepción se usa como el motivo. De manera similar, cuando una función manejadora devuelve una promesa que es rechazada, ese rechazo fluye hacia la siguiente promesa. Existe una función `Promise.reject` que crea una nueva promesa inmediatamente rechazada. +Al igual que resolver una promesa proporciona un valor, rechazar una también lo hace, generalmente llamado el _motivo_ del rechazo. Cuando una excepción en una función manejadora causa el rechazo, el valor de la excepción se usa como dicho motivo. De manera similar, cuando una función manejadora devuelve una promesa que es rechazada, ese rechazo fluye hacia la siguiente promesa. Existe una función `Promise.reject` que crea una nueva promesa inmediatamente rechazada. {{index "método catch"}} @@ -206,12 +206,12 @@ Para manejar explícitamente tales rechazos, las promesas tienen un método `cat {{index "método then"}} -Como un atajo, `then` también acepta un manejador de rechazo como segundo argumento, para poder instalar ambos tipos de manejadores en una sola llamada de método. +Como atajo, `then` también acepta un manejador de rechazo como segundo argumento, conque puedes instalar ambos tipos de manejadores en una sola llamada de método: `.then(manejadorDeAceptación, manejadorDeRechazo)`. -Una función pasada al constructor `Promise` recibe un segundo argumento, junto con la función de resolución, que puede usar para rechazar la nueva promesa.Cuando nuestra función `readTextFile` encuentra un problema, pasa el error a su función de devolución de llamada como segundo argumento. Nuestro envoltorio `textFile` debería realmente examinar ese argumento, de manera que un fallo cause que la promesa que devuelve sea rechazada. +Una función pasada al constructor `Promise` recibe un segundo argumento, junto con la función de resolución, que puede usar para rechazar la nueva promesa. Cuando nuestra función `readTextFile` encuentra un problema, pasa el error a su función callback como segundo argumento. Nuestro envoltorio `archivoTexto` debería realmente examinar ese argumento, de manera que un fallo cause que la promesa que devuelve sea rechazada. ```{includeCode: true} -function textFile(filename) { +function archivoTexto(filename) { return new Promise((resolve, reject) => { readTextFile(filename, (text, error) => { if (error) reject(error); @@ -224,7 +224,7 @@ function textFile(filename) { Las cadenas de valores de promesa creadas por llamadas a `then` y `catch` forman así un pipeline a través del cual se mueven los valores asíncronos o fallos. Dado que dichas cadenas se crean registrando manejadores, cada eslabón tiene asociado un manejador de éxito o un manejador de rechazo (o ambos). Los manejadores que no coinciden con el tipo de resultado (éxito o fallo) son ignorados. Pero aquellos que coinciden son llamados, y su resultado determina qué tipo de valor viene a continuación: éxito cuando devuelve un valor que no es una promesa, rechazo cuando genera una excepción, y el resultado de la promesa cuando devuelve una promesa. ```{test: no} -new Promise((_, reject) => reject(new Error("Fail"))) +new Promise((_, rechazar) => rechazar(new Error("Fail"))) .then(value => console.log("Manejador 1:", value)) .catch(reason => { console.log("Error capturado " + reason); @@ -232,74 +232,80 @@ new Promise((_, reject) => reject(new Error("Fail"))) }) .then(value => console.log("Manejador 2:", value)); // → Error capturado Error: Fail -// → Handler 2: nothing +// → Manejador 2: nada ``` -La primera función de manejador regular no es llamada, porque en ese punto del pipeline la promesa contiene un rechazo. El manejador `catch` maneja ese rechazo y devuelve un valor, que se le da a la segunda función de manejador. +{{note "**N. del T.:** nótese cómo el parámetro que se pasa al constructor `Promise` es una función con dos parámetros que no representan otra cosa que el nombre de las funciones de resolución y rechazo que espera el constructor. JavaScript ya sabe que la función cuyo nombre se pasa como primer parámetro hará lo que se necesite cuando la promesa se resuelve sin problemas, y que la función cuyo nombre se pasa como segundo parámetro hará lo propio cuando la promesa es rechazada. El nombre que les pongamos a dichos parámetros es indiferente, aunque suele usarse `resolve` para el primer caso y `reject` para el segundo o, como en este ejemplo, `_` para el primero (porque ni siquiera lo necesitamos) y `rechazar` para el segundo."}} + +El primer manejador `then` no es llamado porque, en ese punto del pipeline, la promesa contiene un rechazo. El manejador `catch` maneja ese rechazo y devuelve un valor, que se le da al segundo manejador `then`. Cuando una excepción no controlada es manejada por el entorno, los entornos de JavaScript pueden detectar cuándo un rechazo de promesa no es manejado y lo reportarán como un error. ## Carla -Es un día soleado en Berlín. La pista del antiguo aeropuerto desmantelado rebosa de ciclistas y patinadores en línea. En el césped cerca de un contenedor de basura un grupo de cuervos se agita ruidosamente, intentando convencer a un grupo de turistas de que les den sus sándwiches. +Es un día soleado en Berlín. La pista del antiguo aeropuerto desmantelado está llena de ciclistas y patinadores en línea. En el césped, cerca de un contenedor de basura, un grupo de cuervos se agita ruidosamente, intentando convencer a un grupo de turistas de que les den sus sándwiches. + +Uno de los cuervos destaca: una hembra grande, andrajosa, con algunas plumas blancas en su ala derecha. Está atrayendo a la gente con una habilidad y confianza que sugieren que ha estado haciendo esto durante mucho tiempo. Cuando un anciano se distrae con las travesuras de otro cuervo, ella se abalanza como quien no quiere la cosa, le arrebata su bollo a medio comer de la mano y se aleja planeando. -Uno de los cuervos destaca: una hembra grande andrajosa con algunas plumas blancas en su ala derecha. Está atrayendo a la gente con habilidad y confianza que sugieren que ha estado haciendo esto durante mucho tiempo. Cuando un anciano se distrae con las travesuras de otro cuervo, ella se abalanza casualmente, arrebata su bollo a medio comer de su mano y se aleja planeando. +A diferencia del resto del grupo, que parece estar feliz de pasar el día holgazaneando por ahí, el cuervo grande parece tener un propósito. Llevando su botín, vuela directamente hacia el techo del edificio del hangar, desapareciendo por un conducto de ventilación. -A diferencia del resto del grupo, que parece estar feliz de pasar el día holgazaneando aquí, el cuervo grande parece tener un propósito. Llevando su botín, vuela directamente hacia el techo del edificio del hangar, desapareciendo en una rejilla de ventilación. +Dentro del edificio, se puede escuchar un sonido peculiar: suave, pero persistente. Viene de un espacio estrecho bajo el techo de una escalera sin terminar. El cuervo está sentado allí, rodeado de sus botines robados: media docena de teléfonos inteligentes (varios de los cuales están encendidos) y un enredo de cables. Golpea rápidamente la pantalla de uno de los teléfonos con su pico. Aparecen palabras en él. Si no supieras más, pensarías que estaba escribiendo. -Dentro del edificio, se puede escuchar un sonido peculiar: suave, pero persistente. Viene de un espacio estrecho bajo el techo de una escalera sin terminar. El cuervo está sentado allí, rodeado de sus botines robados, media docena de teléfonos inteligentes (varios de los cuales están encendidos) y un enredo de cables. Golpea rápidamente la pantalla de uno de los teléfonos con su pico. Aparecen palabras en él. Si no supieras mejor, pensarías que estaba escribiendo.Este cuervo es conocido por sus pares como "cāāw-krö". Pero dado que esos sonidos no son adecuados para las cuerdas vocales humanas, la llamaremos Carla. +Este cuervo es conocido por sus iguales como "cāāw-krö". Pero dado que esos sonidos no son adecuados para las cuerdas vocales humanas, la llamaremos Carla. -Carla es un cuervo algo peculiar. En su juventud, estaba fascinada por el lenguaje humano, escuchando a la gente hasta que tuvo un buen entendimiento de lo que decían. Más tarde, su interés se trasladó a la tecnología humana, y comenzó a robar teléfonos para estudiarlos. Su proyecto actual es aprender a programar. El texto que está escribiendo en su laboratorio secreto, de hecho, es un fragmento de código JavaScript. +Carla es un cuervo algo peculiar. En su juventud, estaba fascinada por el lenguaje humano, escuchando a la gente hasta que llegó incluso a entender lo que decían. Más tarde, su interés se trasladó a la tecnología humana, y comenzó a robar teléfonos para estudiarlos. Su proyecto actual es aprender a programar. El texto que está escribiendo en su laboratorio secreto, de hecho, es un fragmento de código JavaScript. ## Infiltración {{index "Carla el cuervo"}} -A Carla le encanta Internet. Fastidiosamente, el teléfono en el que está trabajando está a punto de quedarse sin datos prepagos. El edificio tiene una red inalámbrica, pero se requiere un código para acceder a ella. +A Carla le encanta Internet. Por desgracia, el teléfono en el que está trabajando está a punto de quedarse sin datos. El edificio tiene una red inalámbrica, pero se requiere un código para acceder a ella. -Afortunadamente, los enrutadores inalámbricos en el edificio tienen 20 años y están mal protegidos. Tras investigar un poco, Carla descubre que el mecanismo de autenticación de la red tiene una falla que puede aprovechar. Al unirse a la red, un dispositivo debe enviar el código correcto de 6 dígitos. El punto de acceso responderá con un mensaje de éxito o fracaso dependiendo de si se proporciona el código correcto. Sin embargo, al enviar solo un código parcial (digamos, solo 3 dígitos), la respuesta es diferente según si esos dígitos son el inicio correcto del código o no. Cuando se envía un número incorrecto, se recibe inmediatamente un mensaje de fracaso. Cuando se envían los correctos, el punto de acceso espera más dígitos. +Afortunadamente, los rúteres inalámbricos del edificio tienen 20 años y están mal protegidos. Tras investigar un poco, Carla descubre que el mecanismo de autenticación de la red tiene un fallo que puede aprovechar. Al unirse a la red, un dispositivo debe enviar el código correcto de 6 dígitos. El punto de acceso responderá con un mensaje de éxito o fracaso dependiendo de si se proporciona el código correcto. Sin embargo, al enviar solo un código parcial (digamos, solo 3 dígitos), la respuesta es diferente según si esos dígitos son el inicio correcto del código o no. Cuando se envía un número incorrecto, se recibe inmediatamente un mensaje de fracaso. Cuando se envían los dígitos correctos, el punto de acceso espera más dígitos. -Esto hace posible acelerar enormemente la adivinación del número. Carla puede encontrar el primer dígito probando cada número a su vez, hasta que encuentre uno que no devuelva inmediatamente un fracaso. Teniendo un dígito, puede encontrar el segundo de la misma manera, y así sucesivamente, hasta que conozca todo el código de acceso. +Esto acelera enormemente el descubrimiento del número. Carla puede encontrar el primer dígito probando cada número uno a uno, hasta que encuentre uno que no devuelva inmediatamente un fracaso. Teniendo un dígito, puede encontrar el segundo de la misma manera, y así sucesivamente, hasta que conozca todo el código de acceso. Supongamos que tenemos una función `joinWifi`. Dado el nombre de la red y el código de acceso (como una cadena), intenta unirse a la red, devolviendo una promesa que se resuelve si tiene éxito, y se rechaza si la autenticación falla. Lo primero que necesitamos es una forma de envolver una promesa para que se rechace automáticamente después de transcurrir demasiado tiempo, de manera que podamos avanzar rápidamente si el punto de acceso no responde. ```{includeCode: true} -function withTimeout(promise, tiempo) { - return new Promise((resolve, reject) => { - promise.then(resolve, reject); - setTimeout(() => reject("Se agotó el tiempo"), tiempo); +function conTiempoDeEspera(promesa, tiempo) { + return new Promise((resolver, rechazar) => { + promesa.then(resolver, rechazar); + setTimeout(() => rechazar("Se agotó el tiempo"), tiempo); }); } ``` -Esto aprovecha el hecho de que una promesa solo puede resolverse o rechazarse una vez: si la promesa dada como argumento se resuelve o se rechaza primero, ese será el resultado de la promesa devuelta por `withTimeout`. Si, por otro lado, el `setTimeout` se ejecuta primero, rechazando la promesa, se ignoran cualquier llamada posterior a resolve o reject. +Esto aprovecha el hecho de que una promesa solo puede resolverse o rechazarse una vez: si la promesa dada como argumento se resuelve o se rechaza primero, ese será el resultado de la promesa devuelta por `conTiempoDeEspera`. Si, por otro lado, el `setTimeout` se ejecuta primero, rechazando la promesa, se ignora cualquier llamada posterior de resolución o rechazo. + +Para encontrar todo el código de acceso, necesitamos buscar repetidamente el siguiente dígito probando cada dígito. Si la autenticación tiene éxito, sabremos que hemos encontrado lo que buscamos. Si falla inmediatamente, sabremos que ese dígito era incorrecto y debemos probar con el siguiente. Si el tiempo de la solicitud se agota, hemos encontrado otro dígito correcto y debemos continuar agregando otro dígito. -Para encontrar todo el código de acceso, necesitamos buscar repetidamente el siguiente dígito probando cada dígito. Si la autenticación tiene éxito, sabremos que hemos encontrado lo que buscamos. Si falla inmediatamente, sabremos que ese dígito era incorrecto y debemos probar con el siguiente. Si la solicitud se agota, hemos encontrado otro dígito correcto y debemos continuar agregando otro dígito.Debido a que no puedes esperar una promesa dentro de un bucle `for`, Carla utiliza una función recursiva para llevar a cabo este proceso. En cada llamada, obtiene el código tal como lo conocemos hasta ahora, así como el siguiente dígito a probar. Dependiendo de lo que suceda, puede devolver un código terminado, o llamar de nuevo a sí misma, ya sea para comenzar a descifrar la siguiente posición en el código, o para intentarlo de nuevo con otro dígito. +Como no puedes esperar una promesa dentro de un bucle `for`, Carla utiliza una función recursiva para llevar a cabo este proceso. En cada llamada, obtiene el código tal como lo conocemos hasta ahora, así como el siguiente dígito a probar. Dependiendo de lo que suceda, puede devolver un código terminado, o llamarse de nuevo a sí misma, ya sea para comenzar a descifrar la siguiente posición en el código, o para intentarlo de nuevo con otro dígito. ```{includeCode: true} -function crackPasscode(networkID) { - function nextDigit(code, digit) { - let newCode = code + digit; - return withTimeout(joinWifi(networkID, newCode), 50) - .then(() => newCode) - .catch(failure => { - if (failure == "Timed out") { - return nextDigit(newCode, 0); - } else if (digit < 9) { - return nextDigit(code, digit + 1); +function crackearContraseña(identificadorDeRed) { + function siguienteDígito(código, dígito) { + let nuevoCódigo = código + dígito; + return conTiempoDeEspera(joinWifi(identificadorDeRed, nuevoCódigo), 50) + .then(() => nuevoCódigo) + .catch(fallo => { + if (fallo == "Se agotó el tiempo") { + return siguienteDígito(nuevoCódigo, 0); + } else if (dígito < 9) { + return siguienteDígito(código, dígito + 1); } else { - throw failure; + throw fallo; } }); } - return nextDigit("", 0); + return siguienteDígito("", 0); } ``` El punto de acceso suele responder a solicitudes de autenticación incorrectas en aproximadamente 20 milisegundos, por lo que, para estar seguros, esta función espera 50 milisegundos antes de hacer expirar una solicitud. ``` -crackPasscode("HANGAR 2").then(console.log); +crackearContraseña("HANGAR 2").then(console.log); // → 555555 ``` @@ -309,34 +315,34 @@ Carla inclina la cabeza y suspira. Esto habría sido más satisfactorio si el c {{index "Promise class", recursion}} -Incluso con promesas, este tipo de código asíncrono es molesto de escribir. Las promesas a menudo necesitan ser encadenadas de manera verbosa y arbitraria. Y nos vimos obligados a introducir una función recursiva solo para crear un bucle. +Incluso con promesas, este tipo de código asíncrono es molesto de escribir. A menudo, necesitamos encadenar promesas de manera verbosa y de aparencia arbitraria —Carla ha tenido que usar una función recursiva para crear un bucle asíncrono. {{index "synchronous programming", "asynchronous programming"}} -Lo que la función de descifrado realmente hace es completamente lineal: siempre espera a que la acción anterior se complete antes de comenzar la siguiente. En un modelo de programación síncrona, sería más sencillo de expresar. +Lo que la función `crackearContraseña` realmente hace es completamente lineal: siempre espera a que la acción anterior se complete antes de comenzar la siguiente. Sería más sencillo de expresar en un modelo de programación sincrónica. {{index "async function", "await keyword"}} -La buena noticia es que JavaScript te permite escribir código pseudo-sincrónico para describir la computación asíncrona. Una función `async` es una función que implícitamente devuelve una promesa y que puede, en su cuerpo, `await` otras promesas de una manera que _parece_ sincrónica. +La buena noticia es que JavaScript te permite escribir código pseudo-sincrónico para describir procedimientos asíncronos. Una función `async` es una función que implícitamente devuelve una promesa y que puede, en su cuerpo, esperar (`await`) otras promesas de una manera que _parece_ sincrónica. {{index "findInStorage function"}} -Podemos reescribir `crackPasscode` de la siguiente manera: +Podemos reescribir `crackearContraseña` de la siguiente manera: ``` -async function crackPasscode(networkID) { - for (let code = "";;) { - for (let digit = 0;; digit++) { - let newCode = code + digit; +async function crackearContraseña(identificadorDeRed) { + for (let código = "";;) { + for (let dígito = 0;; dígito++) { + let nuevoCódigo = código + dígito; try { - await withTimeout(joinWifi(networkID, newCode), 50); - return newCode; - } catch (failure) { - if (failure == "Timed out") { - code = newCode; + await withTimeout(joinWifi(identificadorDeRed, nuevoCódigo), 50); + return nuevoCódigo; + } catch (fallo) { + if (fallo == "Se agotó el tiempo") { + código = nuevoCódigo; break; - } else if (digit == 9) { - throw failure; + } else if (dígito == 9) { + throw fallo; } } } @@ -344,19 +350,19 @@ async function crackPasscode(networkID) { } ``` -Esta versión muestra de manera más clara la estructura de doble bucle de la función (el bucle interno prueba el dígito 0 al 9, el bucle externo añade dígitos al código de acceso). +Esta versión muestra de manera más clara la estructura de doble bucle de la función (el bucle interno prueba los dígitos del 0 al 9 y el bucle externo añade dígitos al código de acceso). {{index "async function", "return keyword", "exception handling"}} -Una función `async` está marcada con la palabra `async` antes de la palabra clave `function`. Los métodos también pueden ser marcados como `async` escribiendo `async` antes de su nombre. Cuando se llama a una función o método de esta manera, devuelve una promesa. Tan pronto como la función devuelve algo, esa promesa se resuelve. Si el cuerpo genera una excepción, la promesa es rechazada. +Una función `async` está marcada con la palabra `async` antes de la palabra clave `function`. Los métodos también se pueden marcar como `async` escribiendo `async` antes de su nombre. Cuando se llama a una función o método de esta manera, lo que se devuelve es una promesa. Tan pronto como la función devuelve algo, esa promesa se resuelve. Si el cuerpo genera una excepción, la promesa es rechazada. {{index "await keyword", ["control flow", "asincronía"]}} Dentro de una función `async`, la palabra `await` puede colocarse delante de una expresión para esperar a que una promesa se resuelva y luego continuar con la ejecución de la función. Si la promesa es rechazada, se genera una excepción en el punto del `await`. -Una función así ya no se ejecuta, como una función regular de JavaScript, de principio a fin de una sola vez. En su lugar, puede estar _congelada_ en cualquier punto que tenga un `await`, y puede continuar más tarde. +Una función de estas ya no se ejecuta de principio a fin de una vez como una función normal de JavaScript. En su lugar, puede estar _congelada_ en cualquier punto que tenga un `await`, y continuar más tarde. -Para la mayoría del código asíncrono, esta notación es más conveniente que usar directamente promesas. Aún necesitas comprender las promesas, ya que en muchos casos todavía interactúas con ellas directamente. Pero al encadenarlas, las funciones `async` suelen ser más agradables de escribir que encadenar llamadas `then`. +Para la mayoría del código asíncrono, esta notación es más conveniente que usar directamente promesas. Aún así, es necesario comprender las promesas, ya que en muchos casos interactuarás con ellas directamente de todos modos. Pero al encadenarlas, las funciones `async` suelen ser más agradables de escribir que encadenar llamadas a `then`. {{id generator}} @@ -364,20 +370,20 @@ Para la mayoría del código asíncrono, esta notación es más conveniente que {{index "async function"}} -Esta capacidad de pausar y luego reanudar funciones no es exclusiva de las funciones `async`. JavaScript también tiene una característica llamada _((generador))_ functions. Son similares, pero sin las promesas. +Esta capacidad de pausar y luego reanudar funciones no es exclusiva de las funciones `async`. JavaScript también tiene una característica llamada funciones ((generador))as (_generator functions_). Estas son parecidas a las funciones `async`, pero sin las promesas. -Cuando defines una función con `function*` (colocando un asterisco después de la palabra `function`), se convierte en un generador. Al llamar a un generador, devuelve un ((iterador)), que ya vimos en el [Capítulo ?](object). +Cuando defines una función con `function*` (colocando un asterisco después de la palabra `function`), se convierte en un generador. Al llamar a un generador, este devuelve un ((iterador)), que ya estudiamos en el [Capítulo ?](object). ``` -function* powers(n) { - for (let current = n;; current *= n) { - yield current; +function* potencias(n) { + for (let actual = n;; actual *= n) { + yield actual; } } -for (let power of powers(3)) { - if (power > 50) break; - console.log(power); +for (let potencia of potencias(3)) { + if (potencia > 50) break; + console.log(potencia); } // → 3 // → 9 @@ -386,7 +392,7 @@ for (let power of powers(3)) { {{index "next method", "yield keyword"}} -Inicialmente, al llamar a `powers`, la función se congela desde el principio. Cada vez que llamas a `next` en el iterador, la función se ejecuta hasta que encuentra una expresión `yield`, que la pausa y hace que el valor generado se convierta en el próximo valor producido por el iterador. Cuando la función retorna (la del ejemplo nunca lo hace), el iterador ha terminado. +Inicialmente, al llamar a `potencias`, la función se congela desde el principio. Cada vez que llamas a `next` en el iterador, la función se ejecuta hasta que encuentra una expresión `yield`, que la pausa y hace que el valor generado se convierta en el próximo valor producido por el iterador. Cuando la función retorna (la del ejemplo nunca lo hace), el iterador ha terminado. Escribir iteradores a menudo es mucho más fácil cuando usas funciones generadoras. El iterador para la clase `Group` (del ejercicio en el [Capítulo ?](object#group_iterator)) se puede escribir con este generador: @@ -415,33 +421,33 @@ Tales expresiones `yield` solo pueden ocurrir directamente en la función genera {{index "await keyword"}} -Una función `async` es un tipo especial de generador. Produce una promesa al llamarla, la cual se resuelve cuando retorna (termina) y se rechaza cuando arroja una excepción. Cada vez que hace un yield (awaits) una promesa, el resultado de esa promesa (valor o excepción generada) es el resultado de la expresión `await`. +Una función `async` es un tipo especial de generador. Produce una promesa al llamarla, la cual se resuelve cuando retorna (termina) y se rechaza cuando arroja una excepción. Cada vez que hace un yield de una promesa (es decir, la espera con `await`), el resultado de esa promesa (el valor o la excepción generada) es el resultado de la expresión `await`. -## Un Proyecto de Arte de Corvidos +## Un Proyecto de Arte de Córvidos {{index "Carla la cuerva"}} Esta mañana, Carla se despertó con un ruido desconocido en la pista de aterrizaje fuera de su hangar. Saltando al borde del techo, ve que los humanos están preparando algo. Hay muchos cables eléctricos, un escenario y una especie de gran pared negra que están construyendo. -Siendo una cuerva curiosa, Carla echa un vistazo más de cerca a la pared. Parece estar compuesta por varios dispositivos grandes con frente de vidrio conectados a cables. En la parte trasera, los dispositivos dicen "LedTec SIG-5030". +Como es una cuerva curiosa, Carla echa un vistazo más de cerca a la pared. Parece estar compuesta por varios dispositivos grandes con un frontal de vidrio conectados a cables. En la parte trasera, los dispositivos dicen "LedTec SIG-5030". -Una rápida búsqueda en Internet saca a relucir un manual de usuario para estos dispositivos. Parecen ser señales de tráfico, con una matriz programable de luces LED ambarinas. La intención de los humanos probablemente sea mostrar algún tipo de información en ellas durante su evento. Curiosamente, las pantallas pueden ser programadas a través de una red inalámbrica. ¿Podría ser que estén conectadas a la red local del edificio? +Una rápida búsqueda en Internet saca a relucir un manual de usuario para estos dispositivos. Parecen ser señales de tráfico, con una matriz programable de luces LED de color ámbar. La intención de los humanos probablemente sea mostrar algún tipo de información en ellas durante su evento. Curiosamente, las pantallas pueden ser programadas a través de una red inalámbrica. ¿Será posible que estén conectadas a la red local del edificio? -Cada dispositivo en una red recibe una _dirección IP_, que otros dispositivos pueden usar para enviarle mensajes. Hablamos más sobre eso en el [Capítulo ?](browser). Carla nota que sus propios teléfonos reciben direcciones como `10.0.0.20` o `10.0.0.33`. Podría valer la pena intentar enviar mensajes a todas esas direcciones y ver si alguna responde a la interfaz descrita en el manual de las señales. +Cada dispositivo en una red recibe una _dirección IP_, que otros dispositivos pueden usar para enviarle mensajes. Hablaremos más sobre eso en el [Capítulo ?](browser). Carla se da cuenta que sus propios teléfonos reciben direcciones como `10.0.0.20` o `10.0.0.33`. Podría valer la pena intentar enviar mensajes a todas esas direcciones y ver si alguna responde a la interfaz descrita en el manual de las señales. El [Capítulo ?](http) muestra cómo hacer solicitudes reales en redes reales. En este capítulo, usaremos una función ficticia simplificada llamada `request` para la comunicación en red. Esta función toma dos argumentos: una dirección de red y un mensaje, que puede ser cualquier cosa que se pueda enviar como JSON, y devuelve una promesa que se resuelve con una respuesta de la máquina en la dirección dada, o se rechaza si hubo un problema. -Según el manual, puedes cambiar lo que se muestra en una señal SIG-5030 enviándole un mensaje con contenido como `{"command": "display", "data": [0, 0, 3, …]}`, donde `data` contiene un número por cada punto de LED, indicando su brillo; 0 significa apagado, 3 significa brillo máximo. Cada señal tiene 50 luces de ancho y 30 luces de alto, por lo que un comando de actualización debe enviar 1500 números. +Según el manual, puedes cambiar lo que se muestra en una señal SIG-5030 enviándole un mensaje con contenido como `{"command": "display", "data": [0, 0, 3, …]}`, donde `data` contiene un número por cada LED, indicando su brillo; 0 significa apagado, 3 significa brillo máximo. Cada señal tiene 50 luces de ancho y 30 luces de alto, por lo que un comando de actualización debe enviar 1500 números. Este código envía un mensaje de actualización de pantalla a todas las direcciones en la red local para ver cuál se queda. Cada uno de los números en una dirección IP puede ir de 0 a 255. En los datos que envía, activa un número de luces correspondiente al último número de la dirección de red. ``` -for (let addr = 1; addr < 256; addr++) { +for (let dir = 1; dir < 256; dir++) { let data = []; for (let n = 0; n < 1500; n++) { - data.push(n < addr ? 3 : 0); + data.push(n < dir ? 3 : 0); } - let ip = `10.0.0.${addr}`; + let ip = `10.0.0.${dir}`; request(ip, {command: "display", data}) .then(() => console.log(`Solicitud a ${ip} aceptada`)) .catch(() => {}); @@ -453,29 +459,29 @@ Dado que la mayoría de estas direcciones no existirán o no aceptarán tales me Después de haber iniciado su exploración de red, Carla regresa afuera para ver el resultado. Para su deleite, todas las pantallas ahora muestran una franja de luz en sus esquinas superiores izquierdas. Están en la red local y sí aceptan comandos. Rápidamente toma nota de los números mostrados en cada pantalla. Hay 9 pantallas, dispuestas tres en alto y tres en ancho. Tienen las siguientes direcciones de red: ```{includeCode: true} -const screenAddresses = [ +const direccionesPantalla = [ "10.0.0.44", "10.0.0.45", "10.0.0.41", "10.0.0.31", "10.0.0.40", "10.0.0.42", "10.0.0.48", "10.0.0.47", "10.0.0.46" ]; ``` -Ahora esto abre posibilidades para todo tipo de travesuras. Podría mostrar "los cuervos mandan, los humanos babean" en la pared en letras gigantes. Pero eso se siente un poco grosero. En su lugar, planea mostrar un video de un cuervo volando que cubre todas las pantallas por la noche. +Ahora esto abre posibilidades para todo tipo de travesuras. Podría mostrar "los cuervos mandan, los humanos babean" en la pared en letras gigantes. Pero eso se parece un poco grosero. En su lugar, planea mostrar a la noche un vídeo de un cuervo volando que cubra todas las pantallas. -Carla encuentra un clip de video adecuado, en el cual un segundo y medio de metraje se puede repetir para crear un video en bucle mostrando el aleteo de un cuervo. Para ajustarse a las nueve pantallas (cada una de las cuales puede mostrar 50 por 30 píxeles), Carla corta y redimensiona los videos para obtener una serie de imágenes de 150 por 90, diez por segundo. Estas luego se cortan en nueve rectángulos cada una, y se procesan para que los puntos oscuros en el video (donde está el cuervo) muestren una luz brillante, y los puntos claros (sin cuervo) permanezcan oscuros, lo que debería crear el efecto de un cuervo ámbar volando contra un fondo negro. +Carla encuentra un vídeo adecuado en el cual un segundo y medio de metraje se puede repetir para crear un vídeo en bucle mostrando el aleteo de un cuervo. Para ajustarse a las nueve pantallas (cada una de las cuales puede mostrar 50 por 30 píxeles), Carla corta y redimensiona los vídeos para obtener una serie de imágenes de 150 por 90, diez por segundo. Estas luego se cortan en nueve rectángulos cada una, y se procesan para que los puntos oscuros en el vídeo (donde está el cuervo) muestren una luz brillante, y los puntos claros (sin cuervo) permanezcan oscuros, lo que debería crear el efecto de un cuervo ámbar volando contra un fondo negro. -Ella ha configurado la variable `clipImages` para contener un array de fotogramas, donde cada fotograma se representa con un array de nueve conjuntos de píxeles, uno para cada pantalla, en el formato que los letreros esperan. +Ha configurado la variable `imágenesVídeo` para contener un array de fotogramas, donde cada fotograma se representa con un array de nueve conjuntos de píxeles, uno para cada pantalla, en el formato que los letreros esperan. -Para mostrar un único fotograma del video, Carla necesita enviar una solicitud a todas las pantallas a la vez. Pero también necesita esperar el resultado de estas solicitudes, tanto para no comenzar a enviar el siguiente fotograma antes de que el actual se haya enviado correctamente, como para notar cuando las solicitudes están fallando. +Para mostrar un único fotograma del vídeo, Carla necesita enviar una solicitud a todas las pantallas a la vez. Pero también necesita esperar el resultado de estas solicitudes, tanto para no comenzar a enviar el siguiente fotograma antes de que el actual se haya enviado correctamente, como para notar cuando las solicitudes están fallando. {{index "Promise.all function"}} -`Promise` tiene un método estático `all` que se puede usar para convertir un array de promesas en una sola promesa que se resuelve en un array de resultados. Esto proporciona una forma conveniente de que algunas acciones asíncronas sucedan al lado unas de otras, esperar a que todas terminen y luego hacer algo con sus resultados (o al menos esperar a que terminen para asegurarse de que no fallen). +`Promise` tiene un método estático `all` que se puede usar para convertir un array de promesas en una sola promesa que se resuelve en un array de resultados. Esto proporciona una forma conveniente de que algunas acciones asíncronas sucedan de manera concurrente, esperar a que todas terminen y luego hacer algo con sus resultados (o al menos esperar a que terminen para asegurarse de que no fallen). ```{includeCode: true} -function displayFrame(frame) { - return Promise.all(frame.map((data, i) => { - return request(screenAddresses[i], { +function mostrarFotograma(fotograma) { + return Promise.all(fotograma.map((data, i) => { + return request(direccionesPantalla[i], { command: "display", data }); @@ -483,62 +489,66 @@ function displayFrame(frame) { } ``` -Esto recorre las imágenes en `frame` (que es un array de arrays de datos de visualización) para crear un array de promesas de solicitud. Luego devuelve una promesa que combina todas esas promesas. +Esto recorre las imágenes en `fotograma` (que es un array de arrays de datos de visualización) para crear un array de promesas de solicitud. Luego devuelve una promesa que combina todas esas promesas. -Para poder detener un video en reproducción, el proceso está envuelto en una clase. Esta clase tiene un método asíncrono `play` que devuelve una promesa que solo se resuelve cuando la reproducción se detiene de nuevo a través del método `stop`. +Para tener la capacidad de detener un vídeo en reproducción, el proceso está envuelto en una clase. Esta clase tiene un método asíncrono `reproducir` que devuelve una promesa que solo se resuelve cuando la reproducción se detiene a través del método `parar`. ```{includeCode: true} -function wait(time) { - return new Promise(accept => setTimeout(accept, time)); +function espera(tiempo) { + return new Promise(aceptar => setTimeout(aceptar, tiempo)); } -class VideoPlayer { - constructor(frames, frameTime) { - this.frames = frames; - this.frameTime = frameTime; - this.stopped = true; +class ReproductorVídeo { + constructor(fotogramas, tiempoFotograma) { + this.fotogramas = fotogramas; + this.tiempoFotograma = tiempoFotograma; + this.parado = true; } - async play() { - this.stopped = false; - for (let i = 0; !this.stopped; i++) { - let nextFrame = wait(this.frameTime); - await displayFrame(this.frames[i % this.frames.length]); - await nextFrame; + async reproducir() { + this.parado = false; + for (let i = 0; !this.parado; i++) { + let siguienteFotograma = espera(this.tiempoFotograma); + await mostrarFotograma(this.fotogramas[i % this.fotogramas.length]); + await siguienteFotograma; } } - stop() { - this.stopped = true; + parar() { + this.parado = true; } } ``` -La función `wait` envuelve `setTimeout` en una promesa que se resuelve después del número de milisegundos especificado. Esto es útil para controlar la velocidad de reproducción. +La función `espera` envuelve `setTimeout` en una promesa que se resuelve después del número de milisegundos especificado. Esto es útil para controlar la velocidad de reproducción. ```{startCode: true} -let video = new VideoPlayer(clipImages, 100); -video.play().catch(e => { +let vídeo = new ReproductorVídeo(imágenesVídeo, 100); +vídeo.reproducir().catch(e => { console.log("La reproducción falló: " + e); }); -setTimeout(() => video.stop(), 15000); +setTimeout(() => vídeo.parar(), 15000); ``` -Durante toda la semana que dura el muro de pantalla, todas las noches, cuando está oscuro, aparece misteriosamente un enorme pájaro naranja brillante en él. +Durante toda la semana que la pantalla permanece allí, todas las noches, cuando está oscuro, aparece misteriosamente un enorme pájaro naranja brillante en ella. ## El bucle de eventos -{{index "programación asincrónica", "programación", "bucle de eventos", "línea" de tiempo}} +{{index "programación asíncrona", "programación", "bucle de eventos", "línea" de tiempo}} -Un programa asincrónico comienza ejecutando su script principal, que a menudo configurará devoluciones de llamada para ser llamadas más tarde. Ese script principal, así como las devoluciones de llamada, se ejecutan por completo de una vez, sin interrupciones. Pero entre ellos, el programa puede estar inactivo, esperando a que ocurra algo. +Un programa asíncrono comienza ejecutando su script principal, que a menudo configurará callbacks para ser llamados más tarde. Ese script principal, así como las funciones de callback, se ejecutan por completo de una vez, sin interrupciones. Pero entre ellos, el programa puede estar inactivo, esperando a que ocurra algo. {{index "función setTimeout"}} -Por lo tanto, las devoluciones de llamada no son llamadas directamente por el código que las programó. Si llamo a `setTimeout` desde dentro de una función, esa función ya habrá retornado en el momento en que se llame a la función de devolución de llamada. Y cuando la devolución de llamada regresa, el control no vuelve a la función que lo programó. +Por lo tanto, las funciones de callback no son llamadas directamente por el código que las programó. Si llamo a `setTimeout` desde dentro de una función, esa función ya habrá retornado en el momento en que se llame a la función de callback de `setTimeout`. Y cuando la función de callback retorna, el control no vuelve a la función que lo programó. {{index "clase Promise", palabra clave "catch", "manejo de excepciones"}} -El comportamiento asincrónico ocurre en su propia función vacía ((pila de llamadas)). Esta es una de las razones por las que, sin promesas, gestionar excepciones en código asincrónico es tan difícil. Dado que cada devolución de llamada comienza con una pila de llamadas en su mayoría vacía, sus manejadores de `catch` no estarán en la pila cuando lancen una excepción. +El comportamiento asíncrono ocurre en su propia ((pila de llamadas)) vacía. + +{{note "**N. del T.:** Esto último quiere decir que el comportamiento asíncrono en JavaScript no bloquea la ejecución: el código asíncrono se ejecuta una vez vaciada la pila de llamadas actual."}} + +Esta es una de las razones por las que, sin promesas, gestionar excepciones en código asíncrono es tan difícil. Como cada callback comienza con una pila de llamadas en su mayoría vacía, sus manejadores de `catch` no estarán en la pila cuando lancen una excepción. ``` try { @@ -553,24 +563,24 @@ try { {{index hilo, cola}} -No importa cuán cerca ocurran eventos, como tiempos de espera o solicitudes entrantes, un entorno JavaScript ejecutará solo un programa a la vez. Puedes pensar en esto como ejecutar un gran bucle _alrededor_ de tu programa, llamado el _bucle de eventos_. Cuando no hay nada que hacer, ese bucle se pausa. Pero a medida que llegan eventos, se agregan a una cola y su código se ejecuta uno tras otro. Debido a que no se ejecutan dos cosas al mismo tiempo, un código lento puede retrasar el manejo de otros eventos. +No importa cuán cerca ocurran los eventos (como por ejemplo tiempos de espera o solicitudes entrantes), un entorno JavaScript ejecutará solo un programa a la vez. Puedes imaginártelo como un gran bucle, llamado el _bucle de eventos_, que se ejecuta _alrededor_ de tu programa. Cuando no hay nada que hacer, ese bucle se pausa. Pero a medida que llegan eventos, se agregan a una cola y su código se ejecuta uno tras otro. Como no se ejecutan dos cosas al mismo tiempo, un código lento puede retrasar el manejo de otros eventos. -Este ejemplo establece un tiempo de espera pero luego se demora hasta después del momento previsto para el tiempo de espera, provocando que el tiempo de espera sea tardío. +Este ejemplo establece un tiempo de espera pero luego se demora hasta después del momento previsto para el tiempo de espera, provocando que el tiempo de espera se alargue y termine más tarde de la cuenta. ``` -let start = Date.now(); +let comienzo = Date.now(); setTimeout(() => { - console.log("El tiempo de espera se ejecutó en", Date.now() - start); + console.log("El tiempo de espera se ejecutó en", Date.now() - comienzo); }, 20); -while (Date.now() < start + 50) {} -console.log("Tiempo perdido hasta", Date.now() - start); +while (Date.now() < comienzo + 50) {} +console.log("Tiempo perdido hasta", Date.now() - comienzo); // → Tiempo perdido hasta 50 // → El tiempo de espera se ejecutó en 55 ``` {{index "resolviendo (una promesa)", "rechazando (una promesa)", "clase Promise"}} -Las promesas siempre se resuelven o se rechazan como un nuevo evento. Incluso si una promesa ya está resuelta, esperarla hará que su devolución de llamada se ejecute después de que termine el script actual, en lugar de inmediatamente. +Las promesas siempre se resuelven o se rechazan como un nuevo evento. Incluso si una promesa ya está resuelta, esperarla hará que su callback se ejecute después de que termine el script actual, en lugar de inmediatamente. ``` Promise.resolve("Hecho").then(console.log); @@ -581,22 +591,22 @@ console.log("¡Yo primero!"); En capítulos posteriores veremos varios tipos de eventos que se ejecutan en el bucle de eventos. -## Errores asincrónicos +## Errores asíncronos -{{index "programación asincrónica", [estado, transiciones]}} +{{index "programación asíncrona", [estado, transiciones]}} -Cuando tu programa se ejecuta de forma síncrona, de una sola vez, no hay cambios de estado ocurriendo excepto aquellos que el programa mismo realiza. Para programas asíncronos esto es diferente, pueden tener _brechas_ en su ejecución durante las cuales otro código puede correr. +Cuando tu programa se ejecuta de forma sincrónica, de una sola vez, no hay cambios de estado ocurriendo excepto aquellos que el programa mismo realiza. Para programas asíncronos esto es diferente: pueden tener _brechas_ en su ejecución durante las cuales otro código puede correr. -Veamos un ejemplo. Esta es una función que intenta reportar el tamaño de cada archivo en un arreglo de archivos, asegurándose de leerlos todos al mismo tiempo en lugar de en secuencia. +Veamos un ejemplo. Esta es una función que intenta reportar el tamaño de cada archivo en un array de archivos, asegurándose de leerlos todos al mismo tiempo en lugar de secuencialmente. -{{index "función fileSizes"}} +{{index "función tamañosArchivos"}} ```{includeCode: true} -async function fileSizes(files) { +async function tamañosArchivos(archivos) { let lista = ""; - await Promise.all(files.map(async fileName => { - lista += fileName + ": " + - (await textFile(fileName)).length + "\n"; + await Promise.all(archivos.map(async nombreArchivo => { + lista += nombreArchivo + ": " + + (await archivoTexto(nombreArchivo)).length + "\n"; })); return lista; } @@ -604,18 +614,18 @@ async function fileSizes(files) { {{index "función async"}} -La parte `async fileName =>` muestra cómo también se pueden hacer ((arrow function))s `async` colocando la palabra `async` delante de ellas. +La parte `async nombreArchivo =>` muestra cómo también se pueden hacer ((arrow function))s `async` (funciones flecha asíncronas) colocando la palabra `async` delante de ellas. {{index "función Promise.all"}} -El código no parece ser sospechoso de inmediato... mapea la función flecha `async` sobre el arreglo de nombres, creando un arreglo de promesas, y luego usa `Promise.all` para esperar a todas ellas antes de devolver la lista que construyen. +El código no parece sospechoso de inmediato... mapea la función flecha `async` sobre el array de nombres, creando un array de promesas, y luego usa `Promise.all` para esperar a todas ellas antes de devolver la lista que construyen. -Pero está totalmente roto. Siempre devolverá solo una línea de salida, enumerando el archivo que tardó más en leer. +Sin embargo, el programa está totalmente roto. Siempre devolverá solo una línea de salida, enumerando el archivo que tardó más en leer. {{if interactive ``` -fileSizes(["plans.txt", "shopping_list.txt"]) +tamañosArchivos(["planes.txt", "lista_compra.txt"]) .then(console.log); ``` @@ -629,29 +639,31 @@ El problema radica en el operador `+=`, que toma el valor _actual_ de `lista` en {{index "palabra clave await"}} -Pero entre el momento en que comienza a ejecutarse la instrucción y el momento en que termina, hay una brecha asincrónica. La expresión `map` se ejecuta antes de que se agregue cualquier cosa a la lista, por lo que cada uno de los operadores `+=` comienza desde una cadena vacía y termina, cuando termina su recuperación de almacenamiento, estableciendo `lista` en el resultado de agregar su línea a la cadena vacía. +Pero entre el momento en que comienza a ejecutarse la instrucción y el momento en que termina, hay una brecha asíncrona. La expresión `map` se ejecuta antes de que se agregue cualquier cosa a la lista, por lo que cada uno de los operadores `+=` comienza desde una cadena vacía y acaba, cuando recupera la información del almacenamiento, estableciendo `lista` en el resultado de agregar su línea a la cadena vacía. {{index "efecto secundario"}} -Esto podría haberse evitado fácilmente devolviendo las líneas de las promesas mapeadas y llamando a `join` en el resultado de `Promise.all`, en lugar de construir la lista cambiando un enlace. Como suele ser, calcular nuevos valores es menos propenso a errores que cambiar valores existentes. +Esto podría haberse evitado fácilmente devolviendo las líneas de las promesas mapeadas y llamando a `join` en el resultado de `Promise.all`, en lugar de construir la lista cambiando una variable. Como de costumbre, calcular nuevos valores es menos propenso a errores que cambiar valores existentes. {{index "función fileSizes"}} ``` -async function fileSizes(files) { - let líneas = files.map(async fileName => { - return fileName + ": " + - (await textFile(fileName)).length; +async function tamañosArchivos(archivos) { + let líneas = archivos.map(async nombreArchivo => { + return nombreArchivo + ": " + + (await archivoTexto(nombreArchivo)).length; }); return (await Promise.all(líneas)).join("\n"); } ``` -Errores como este son fáciles de cometer, especialmente al usar `await`, y debes ser consciente de dónde ocurren las brechas en tu código. Una ventaja de la asincronía _explícita_ de JavaScript (ya sea a través de devoluciones de llamada, promesas o `await`) es que identificar estas brechas es relativamente fácil. +Errores como este son fáciles de cometer, especialmente al usar `await`, y debes ser consciente de dónde ocurren las brechas en tu código. Una ventaja de la asincronía _explícita_ de JavaScript (ya sea a través de callbacks, promesas o `await`) es que identificar estas brechas es relativamente fácil. ## Resumen -La programación asincrónica hace posible expresar la espera de acciones de larga duración sin congelar todo el programa. Los entornos de JavaScript típicamente implementan este estilo de programación utilizando devoluciones de llamada, funciones que se llaman cuando las acciones se completan. Un bucle de eventos programa estas devoluciones de llamada para que se llamen cuando sea apropiado, una tras otra, de modo que su ejecución no se superponga.La programación de forma asíncrona se facilita gracias a las promesas, que son objetos que representan acciones que podrían completarse en el futuro, y las funciones `async`, que te permiten escribir un programa asíncrono como si fuera sincrónico. +La programación asíncrona hace posible expresar la espera de acciones de larga duración sin congelar todo el programa. Los entornos de JavaScript típicamente implementan este estilo de programación utilizando callbacks, funciones que se llaman cuando las acciones se completan. Un bucle de eventos programa estas funciones de callback para que se llamen cuando sea apropiado, una tras otra, de modo que su ejecución no se superponga. + +La programación asíncrona se facilita gracias a las promesas, que son objetos que representan acciones que podrían completarse en el futuro, y las funciones `async`, que te permiten escribir un programa asíncrono como si fuera sincrónico. ## Ejercicios @@ -659,11 +671,11 @@ La programación asincrónica hace posible expresar la espera de acciones de lar {{index "momentos de tranquilidad (ejercicio)", "cámara de seguridad", "Carla la urraca", "función async"}} -Hay una cámara de seguridad cerca del laboratorio de Carla que se activa con un sensor de movimiento. Está conectada a la red y comienza a enviar un flujo de video cuando está activa. Como prefiere no ser descubierta, Carla ha configurado un sistema que detecta este tipo de tráfico de red inalámbrico y enciende una luz en su guarida cada vez que hay actividad afuera, para que ella sepa cuándo mantenerse en silencio. +Hay una cámara de seguridad cerca del laboratorio de Carla que se activa con un sensor de movimiento. Está conectada a la red y comienza a enviar un flujo de video cuando está activa. Como prefiere no ser descubierta, Carla ha configurado un sistema que detecta este tipo de tráfico de red inalámbrico y enciende una luz en su guarida cada vez que hay actividad afuera, de modo que sepa cuándo mantenerse en silencio. {{index "clase Date", "función Date.now", marca de tiempo}} -También ha estado registrando los momentos en que la cámara se activa desde hace un tiempo, y quiere utilizar esta información para visualizar qué momentos, en una semana promedio, tienden a ser tranquilos y cuáles tienden a ser ocupados. El registro se almacena en archivos que contienen un número de marca de tiempo por línea (como devuelto por `Date.now()`). +También ha estado registrando los momentos en que la cámara se activa desde hace un tiempo, y quiere utilizar esta información para visualizar qué momentos, en una semana promedio, tienden a ser tranquilos y cuáles tienden a no serlo. El registro se almacena en archivos que contienen un número de marca de tiempo por línea (como devuelto por `Date.now()`). ```{lang: null} 1695709940692 @@ -679,7 +691,7 @@ La función `activityGraph`, proporcionada por el sandbox, resume dicha tabla en Utiliza la función `textFile` definida anteriormente, que al recibir un nombre de archivo devuelve una promesa que se resuelve en el contenido del archivo. Recuerda que `new Date(marcaDeTiempo)` crea un objeto `Date` para ese momento, que tiene métodos `getDay` y `getHours` que devuelven el día de la semana y la hora del día. -Ambos tipos de archivos, la lista de archivos de registro y los propios archivos de registro, tienen cada dato en su propia línea, separados por caracteres de nueva línea (`"\n"`). +Ambos tipos de archivos —la lista de archivos de registro y los propios archivos de registro— tienen cada dato en una línea, separados por caracteres de nueva línea (`"\n"`). {{if interactive @@ -699,15 +711,15 @@ if}} {{index "momentos de tranquilidad (ejercicio)", "método split", "función textFile", "clase Date"}} -Necesitarás convertir el contenido de estos archivos en un array. La forma más fácil de hacerlo es utilizando el método `split` en la cadena producida por `textFile`. Ten en cuenta que para los archivos de registro, eso seguirá dándote un array de cadenas, que debes convertir a números antes de pasarlos a `new Date`. +Necesitarás convertir el contenido de estos archivos en un array. La forma más fácil de hacerlo es utilizando el método `split` en la cadena producida por `textFile`. Ten en cuenta que para los archivos de registro, eso te dará un array de cadenas, que debes convertir a números antes de pasarlos a `new Date`. -Resumir todos los puntos temporales en una tabla de horas se puede hacer creando una tabla (array) que contenga un número para cada hora del día. Luego puedes recorrer todos los marca de tiempos (sobre los archivos de registro y los números en cada archivo de registro) y, para cada uno, si sucedió en el día correcto, toma la hora en que ocurrió y suma uno al número correspondiente en la tabla. +Resumir todos los puntos temporales en una tabla de horas se puede hacer creando una tabla (array) que contenga un número para cada hora del día. Luego puedes recorrer todas las marcas de tiempo (de los archivos de registro y los números en cada archivo de registro) y, para cada uno, si sucedió en el día correcto, tomar la hora en que ocurrió y sumar uno al número correspondiente en la tabla. {{index "función async", "palabra clave await", "clase Promise"}} -Asegúrate de usar `await` en el resultado de las funciones asíncronas antes de hacer cualquier cosa con él, o terminarás con una `Promise` donde esperabas un string. +Asegúrate de usar `await` en el resultado de las funciones asíncronas antes de hacer cualquier cosa con él, o terminarás con una `Promise` donde esperabas tener un string. -hinting}} +hint}} ### Promesas Reales @@ -731,40 +743,40 @@ if}} {{index "función async", "palabra clave await", rendimiento}} -En este estilo, usar `Promise.all` será más conveniente que intentar modelar un bucle sobre los archivos de registro. En la función `async`, simplemente usar `await` en un bucle es más simple. Si leer un archivo toma un tiempo, ¿cuál de estos dos enfoques tomará menos tiempo para ejecutarse? +En este estilo, usar `Promise.all` será más conveniente que intentar modelar un bucle sobre los archivos de registro. En la función `async`, simplemente usar `await` en un bucle es más simple. Si leer un archivo lleva un tiempo, ¿cuál de estos dos enfoques necesitará menos tiempo para ejecutarse? {{index "rechazar (una promesa)"}} -Si uno de los archivos listados en la lista de archivos tiene un error tipográfico, y falla al leerlo, ¿cómo termina ese fallo en el objeto `Promise` que retorna tu función? +Si uno de los archivos listados en la lista de archivos tiene un error tipográfico, y su lectura falla, ¿cómo termina ese fallo en el objeto `Promise` que retorna tu función? {{hint {{index "promesas reales (ejercicio)", "método then", "función textFile", "función Promise.all"}} -El enfoque más directo para escribir esta función es usar una cadena de llamadas `then`. La primera promesa se produce al leer la lista de archivos de registro. El primer callback puede dividir esta lista y mapear `textFile` sobre ella para obtener una matriz de promesas para pasar a `Promise.all`. Puede devolver el objeto devuelto por `Promise.all`, para que lo que sea que eso devuelva se convierta en el resultado del valor de retorno de este primer `then`. +El enfoque más directo para escribir esta función es usar una cadena de llamadas `then`. La primera promesa se produce al leer la lista de archivos de registro. El primer callback puede dividir esta lista y mapear `textFile` sobre ella para obtener un array de promesas para pasar a `Promise.all`. Puede devolver el objeto devuelto por `Promise.all`, para que lo que sea que eso devuelva se convierta en el resultado del valor de retorno de este primer `then`. -{{index "programación asincrónica"}} +{{index "programación asíncrona"}} -Ahora tenemos una promesa que devuelve un array de archivos de registro. Podemos llamar a `then` nuevamente en eso, y poner la lógica de conteo de marcas de tiempo allí. Algo así: +Ahora tenemos una promesa que devuelve un array de archivos de registro. Podemos llamar a `then` nuevamente en eso, y poner la lógica de recuento de marcas de tiempo allí. Algo así: ```{test: no} function activityTable(día) { - return textoArchivo("registros_camara.txt").then(archivos => { - return Promise.all(archivos.split("\n").map(textoArchivo)); + return archivoTexto("registros_camara.txt").then(archivos => { + return Promise.all(archivos.split("\n").map(archivoTexto)); }).then(logs => { // analizar... }); } ``` -O podrías, para una programación aún mejor, poner el análisis de cada archivo dentro de `Promise.all`, para que ese trabajo pueda comenzar para el primer archivo que regresa del disco, incluso antes de que los otros archivos regresen. +O podrías, para una programación del trabajo aún mejor, poner el análisis de cada archivo dentro de `Promise.all`, para que ese trabajo pueda comenzar con el primer archivo que se reciba del disco, incluso antes de que lleguen los otros archivos. ```{test: no} function activityTable(día) { let tabla = []; // inicializar... - return textoArchivo("registros_camara.txt").then(archivos => { + return archivoTexto("registros_camara.txt").then(archivos => { return Promise.all(archivos.split("\n").map(nombre => { - return textoArchivo(nombre).then(log => { + return archivoTexto(nombre).then(log => { // analizar... }); })); @@ -774,11 +786,11 @@ function activityTable(día) { {{index "palabra clave await", "programación de planificación"}} -Lo que muestra que la forma en que estructuras tus promesas puede tener un efecto real en la forma en que se programa el trabajo. Un simple bucle con `await` hará que el proceso sea completamente lineal: espera a que se cargue cada archivo antes de continuar. `Promise.all` hace posible que varias tareas sean trabajadas conceptualmente al mismo tiempo, permitiéndoles progresar mientras los archivos aún se están cargando. Esto puede ser más rápido, pero también hace que el orden en que sucederán las cosas sea menos predecible. En este caso, donde solo vamos a estar incrementando números en una tabla, eso no es difícil de hacer de manera segura. Para otros tipos de problemas, puede ser mucho más difícil. +Esto demuestra que la forma en que estructuras tus promesas puede tener un efecto real en la forma en que se programa el trabajo. Un simple bucle con `await` hará que el proceso sea completamente lineal: espera a que se cargue cada archivo antes de continuar. `Promise.all` hace posible que varias tareas sean trabajadas conceptualmente al mismo tiempo, permitiéndoles progresar mientras los archivos aún se están cargando. Esto puede ser más rápido, pero también hace que el orden en que sucederán las cosas sea menos predecible. En este caso, donde solo vamos a estar incrementando números en una tabla, eso no es difícil de hacer de manera segura. Para otros tipos de problemas, puede ser mucho más difícil. {{index "rechazar (una promesa)", "método then"}} -Cuando un archivo en la lista no existe, la promesa devuelta por `textFile` será rechazada. Debido a que `Promise.all` se rechaza si alguna de las promesas que se le pasan falla, el valor de retorno de la devolución de llamada dada al primer `then` también será una promesa rechazada. Esto hace que la promesa devuelta por `then` falle, por lo que la devolución de llamada dada al segundo `then` ni siquiera se llama, y se devuelve una promesa rechazada desde la función. +Cuando un archivo en la lista no existe, la promesa devuelta por `archivoTexto` será rechazada. Debido a que `Promise.all` se rechaza si alguna de las promesas que se le pasan falla, el valor de retorno de la callback dada al primer `then` también será una promesa rechazada. Esto hace que la promesa devuelta por `then` falle, por lo que la callback dada al segundo `then` ni siquiera se llama, y se devuelve una promesa rechazada desde la función. hint}} @@ -788,7 +800,7 @@ hint}} Como vimos, dado un array de promesas, `Promise.all` devuelve una promesa que espera a que todas las promesas en el array finalicen. Luego tiene éxito, devolviendo un array de valores de resultado. Si una promesa en el array falla, la promesa devuelta por `all` también falla, con la razón de fallo de la promesa que falló. -Implementa algo similar tú mismo como una función regular llamada `Promise_all`. +Implementa algo similar tú mismo como una función normal llamada `Promise_all`. Recuerda que después de que una promesa tiene éxito o falla, no puede volver a tener éxito o fallar, y las llamadas posteriores a las funciones que la resuelven se ignoran. Esto puede simplificar la forma en que manejas el fallo de tu promesa. @@ -830,7 +842,7 @@ if}} {{index "función Promise.all", "clase Promise", "método then", "construyendo Promise.all (ejercicio)"}} -La función pasada al constructor `Promise` tendrá que llamar a `then` en cada una de las promesas en el array dado. Cuando una de ellas tiene éxito, dos cosas deben suceder. El valor resultante debe ser almacenado en la posición correcta de un array de resultados, y debemos verificar si esta era la última promesa pendiente y finalizar nuestra propia promesa si lo era. +La función pasada al constructor `Promise` tendrá que llamar a `then` en cada una de las promesas en el array dado. Cuando una de ellas tiene éxito, dos cosas deben suceder: el valor resultante debe ser almacenado en la posición correcta de un array de resultados, y debemos verificar si esta era la última promesa pendiente y finalizar nuestra propia promesa si lo era. {{index "variable de contador"}} @@ -838,4 +850,4 @@ Esto último se puede hacer con un contador que se inicializa con la longitud de Manejar el fallo requiere un poco de pensamiento pero resulta ser extremadamente simple. Simplemente pasa la función `reject` de la promesa contenedora a cada una de las promesas en el array como un controlador `catch` o como un segundo argumento para `then` para que un fallo en una de ellas desencadene el rechazo de toda la promesa contenedora. -pista \ No newline at end of file +}} \ No newline at end of file diff --git a/16_game.md b/16_game.md index b5aa2d7a..17bab486 100644 --- a/16_game.md +++ b/16_game.md @@ -838,7 +838,7 @@ async function runGame(plans, Display) { } ``` -{{index "programación asincrónica", "manejo de eventos"}} +{{index "programación asíncrona", "manejo de eventos"}} Debido a que hicimos que `runLevel` devuelva una promesa, `runGame` puede escribirse utilizando una función `async`, como se muestra en el [Capítulo ?](async). Devuelve otra promesa, que se resuelve cuando el jugador termina el juego. diff --git a/20_node.md b/20_node.md index 621e39c3..11fb6ea7 100644 --- a/20_node.md +++ b/20_node.md @@ -32,13 +32,13 @@ Si deseas seguir y ejecutar el código en este capítulo, necesitarás instalar Cuando se construyen sistemas que se comunican a través de la red, la forma en que gestionas la entrada y el ((output))—es decir, la lectura y escritura de datos desde y hacia la red y el ((disco duro))—puede marcar una gran diferencia en cuán rápido responde un sistema al usuario o a las solicitudes de red. -{{index ["programación asincrónica", "en Node.js"]}} +{{index ["programación asíncrona", "en Node.js"]}} -En tales programas, la programación asincrónica a menudo es útil. Permite que el programa envíe y reciba datos desde y hacia múltiples dispositivos al mismo tiempo sin una complicada gestión de hilos y sincronización. +En tales programas, la programación asíncrona a menudo es útil. Permite que el programa envíe y reciba datos desde y hacia múltiples dispositivos al mismo tiempo sin una complicada gestión de hilos y sincronización. {{index "lenguaje de programación", "Node.js", "estándar"}} -Node fue concebido inicialmente con el propósito de hacer que la programación asincrónica sea fácil y conveniente. JavaScript se presta bien a un sistema como Node. Es uno de los pocos lenguajes de programación que no tiene una forma incorporada de manejar la entrada y salida. Por lo tanto, JavaScript podría adaptarse al enfoque algo excéntrico de Node para la programación de red y sistemas de archivos sin terminar con dos interfaces inconsistentes. En 2009, cuando se diseñaba Node, la gente ya estaba realizando programación basada en callbacks en el navegador, por lo que la ((comunidad)) alrededor del lenguaje estaba acostumbrada a un estilo de programación asincrónica. +Node fue concebido inicialmente con el propósito de hacer que la programación asíncrona sea fácil y conveniente. JavaScript se presta bien a un sistema como Node. Es uno de los pocos lenguajes de programación que no tiene una forma incorporada de manejar la entrada y salida. Por lo tanto, JavaScript podría adaptarse al enfoque algo excéntrico de Node para la programación de red y sistemas de archivos sin terminar con dos interfaces inconsistentes. En 2009, cuando se diseñaba Node, la gente ya estaba realizando programación basada en callbacks en el navegador, por lo que la ((comunidad)) alrededor del lenguaje estaba acostumbrada a un estilo de programación asíncrona. ## El comando node @@ -356,7 +356,7 @@ El módulo `node:http` también provee una función `request`, que se puede usar ## Flujos -{{index "Node.js", stream, "flujo de escritura", "función de devolución de llamada", ["programación asincrónica", "en Node.js"], "método write", "método end", "clase Buffer"}} +{{index "Node.js", stream, "flujo de escritura", "función de devolución de llamada", ["programación asíncrona", "en Node.js"], "método write", "método end", "clase Buffer"}} El objeto de respuesta al que el servidor HTTP podría escribir es un ejemplo de un objeto de _flujo de escritura_, que es un concepto ampliamente usado en Node. Estos objetos tienen un método `write` al que se puede pasar una cadena o un objeto `Buffer` para escribir algo en el flujo. Su método `end` cierra el flujo y opcionalmente toma un valor para escribir en el flujo antes de cerrarlo. Ambos métodos también pueden recibir una devolución de llamada como argumento adicional, que se llamará cuando la escritura o el cierre hayan finalizado. diff --git a/21_skillsharing.md b/21_skillsharing.md index 1f8ca5ea..6ee64b03 100644 --- a/21_skillsharing.md +++ b/21_skillsharing.md @@ -688,7 +688,7 @@ async function pollTalks(update) { } ``` -{{index "función asincrónica"}} +{{index "función asíncrona"}} Esta es una función `async` para facilitar el bucle y la espera de la solicitud. Ejecuta un bucle infinito que, en cada iteración, recupera la lista de charlas, ya sea normalmente o, si esta no es la primera solicitud, con las cabeceras incluidas que la convierten en una solicitud de sondeo prolongado. diff --git a/code/hangar2.js b/code/hangar2.js index 19329d11..22998c89 100644 --- a/code/hangar2.js +++ b/code/hangar2.js @@ -47,10 +47,10 @@ var readTextFile = function() { let files = { __proto__: null, - "shopping_list.txt": "Peanut butter\nBananas", + "lista_compra.txt": "Mantequilla de cacahuete\nPlátanos", "old_shopping_list.txt": "Peanut butter\nJelly", "package.json": '{"name":"test project","author":"cāāw-krö","version":"1.1.2"}', - "plans.txt": "* Write a book\n * Figure out asynchronous chapter\n * Find an artist for the cover\n * Write the rest of the book\n\n* Don't be sad\n * Sit under tree\n * Study bugs\n", + "planes.txt": "* Escribir un libro\n * Escribir el capítulo sobre programación asíncrona\n * Encontrar un artista para la portada\n * Escribir el resto del libro\n\n* No estar triste\n * Sentarme bajo un árbol\n * Estudiar bichos\n", "camera_logs.txt": logs.map(l => l.name).join("\n") } @@ -174,7 +174,7 @@ var request = function(){ } }() -var clipImages = [ +var imágenesVídeo = [ [ " 5dc", " e9.2 2b.3 .3 2b.o.3o. 2b.o.3o. 27.2 2.o2.o2. 27.o. .Oo.oO. . 25.o. .Oo.oO.4 20.2 2.Oo .Oo.oO.2o. 20.o 2.Oo.2Oo.oO.2o. 20.O. .O2.2Oo2O2o3. 20.O2 2oOo.O2oO2o3. 20.oOo oO2oO2oO2oOo. 1e.o2O3o2O8oOo 1f.O6oOao 1e.oO11o 1eoO12o. 1c.O13o. 6. 14.O14o. 1boO15. 1bO15o. 1aoO15o 1a.O16. 18. .O15o 1boO15. 1a.O15o 1b.O15. f", diff --git a/html/11_async.html b/html/11_async.html index 1f7f6642..4e4cc052 100644 --- a/html/11_async.html +++ b/html/11_async.html @@ -20,45 +20,45 @@

    Programación Asíncrona

    Ilustración que muestra dos cuervos en una rama de árbol
    -

    La parte central de una computadora, la parte que lleva a cabo los pasos individuales que componen nuestros programas, se llama el procesador. Los programas que hemos visto hasta ahora mantendrán ocupado al procesador hasta que hayan terminado su trabajo. La velocidad a la cual algo como un bucle que manipula números puede ser ejecutado depende casi enteramente de la velocidad del procesador y la memoria de la computadora.

    +

    La parte central de una computadora, la parte que lleva a cabo los pasos individuales que componen nuestros programas, se llama procesador. Los programas que hemos visto hasta ahora mantendrán ocupado al procesador hasta que hayan terminado su trabajo. La velocidad a la cual puede ser ejecutado algo como un bucle que manipula números depende casi enteramente de la velocidad del procesador y la memoria de la computadora.

    -

    Pero muchos programas interactúan con cosas fuera del procesador. Por ejemplo, pueden comunicarse a través de una red de computadoras o solicitar datos desde el disco duro, lo cual es mucho más lento que obtenerlo de la memoria.

    +

    Pero muchos programas interactúan con cosas fuera del procesador. Por ejemplo, pueden comunicarse a través de una red de computadoras o solicitar datos desde el disco duro, lo cual es mucho más lento que obtenerlos de la memoria.

    -

    Cuando esto está sucediendo, sería una lástima dejar el procesador inactivo, ya que podría haber otro trabajo que podría hacer en ese tiempo. En parte, esto es manejado por tu sistema operativo, el cual cambiará el procesador entre múltiples programas en ejecución. Pero eso no ayuda cuando queremos que un único programa pueda avanzar mientras espera una solicitud de red.

    +

    Cuando esto está sucediendo, sería una lástima dejar el procesador inactivo: podría haber otro trabajo que este podría hacer en ese tiempo. En parte, esto es algo que maneja tu sistema operativo, el cual irá dándole al procesador múltiples programas en ejecución, haciendo que vaya cambiando entre ellos. Pero eso no ayuda cuando queremos que un único programa pueda avanzar mientras espera una solicitud de red.

    Asincronía

    -

    En un modelo de programación sincrónico, las cosas suceden una a la vez. Cuando llamas a una función que realiza una acción de larga duración, solo devuelve cuando la acción ha terminado y puede devolver el resultado. Esto detiene tu programa durante el tiempo que tome la acción.

    +

    En un modelo de programación sincrónico, las cosas suceden una a una. Cuando llamas a una función que realiza una acción de larga duración, esta solo retorna cuando la acción ha terminado y puede devolver su resultado. Esto detiene tu programa durante el tiempo que tome la acción.

    -

    Un modelo asincrónico permite que múltiples cosas sucedan al mismo tiempo. Cuando inicias una acción, tu programa continúa ejecutándose. Cuando la acción termina, el programa es informado y obtiene acceso al resultado (por ejemplo, los datos leídos desde el disco).

    +

    Un modelo asíncrono permite que múltiples cosas sucedan al mismo tiempo. Cuando inicias una acción, tu programa continúa ejecutándose. Cuando la acción termina, el programa es informado y obtiene acceso al resultado (por ejemplo, los datos leídos desde el disco).

    -

    Podemos comparar la programación sincrónica y asincrónica usando un pequeño ejemplo: un programa que realiza dos solicitudes a través de la red y luego combina los resultados.

    +

    Podemos comparar la programación sincrónica y asíncrona usando un pequeño ejemplo: un programa que realiza dos solicitudes a través de la red y luego combina de algún modo los resultados.

    -

    En un entorno sincrónico, donde la función de solicitud devuelve solo después de haber hecho su trabajo, la forma más fácil de realizar esta tarea es hacer las solicitudes una después de la otra. Esto tiene la desventaja de que la segunda solicitud se iniciará solo cuando la primera haya terminado. El tiempo total tomado será al menos la suma de los dos tiempos de respuesta.

    +

    En un entorno sincrónico, donde la función de solicitud retorna solo después de haber hecho su trabajo, la forma más fácil de realizar esta tarea es hacer las solicitudes una después de la otra. Esto tiene la desventaja de que la segunda solicitud se iniciará solo cuando la primera haya terminado. El tiempo total necesario será al menos la suma de los dos tiempos de respuesta.

    -

    La solución a este problema, en un sistema sincrónico, es iniciar hebras de control adicionales. Una hebra es otro programa en ejecución cuya ejecución puede ser intercalada con otros programas por el sistema operativo, ya que la mayoría de las computadoras modernas contienen múltiples procesadores, múltiples hebras incluso podrían ejecutarse al mismo tiempo, en diferentes procesadores. Una segunda hebra podría iniciar la segunda solicitud, y luego ambas hebras esperan que sus resultados regresen, después de lo cual se resincronizan para combinar sus resultados.

    +

    La solución a este problema, en un sistema sincrónico, es iniciar hilos de control adicionales. Un hilo es otro programa en ejecución cuya ejecución puede ser intercalada con otros programas por el sistema operativo —como la mayoría de las computadoras modernas contienen múltiples procesadores, podrían ejecutarse incluso múltiples hilos al mismo tiempo, en diferentes procesadores. Un segundo hilo podría iniciar la segunda solicitud, y luego ambos hilos podrían esperar sus resultados, después de lo cual se resincronizan para combinarlos.

    -

    En el siguiente diagrama, las líneas gruesas representan el tiempo que el programa pasa funcionando normalmente, y las líneas delgadas representan el tiempo gastado esperando a la red. En el modelo síncrono, el tiempo tomado por la red es parte de la línea de tiempo para un hilo de control dado. En el modelo asíncrono, iniciar una acción en la red permite que el programa continúe ejecutándose mientras la comunicación en la red sucede junto a él, notificando al programa cuando haya terminado.

    Diagrama que muestra el flujo de control en programas síncronos y asíncronos. La primera parte muestra un programa síncrono, donde las fases activas y de espera del programa ocurren en una única línea secuencial. La segunda parte muestra un programa síncrono multi-hilo, con dos líneas paralelas en las cuales las partes de espera suceden una al lado de la otra, haciendo que el programa termine más rápido. La última parte muestra un programa asíncrono, donde las múltiples acciones asíncronas se ramifican desde el programa principal, el cual se detiene en algún momento y luego continúa cuando la primera cosa por la que estaba esperando finaliza.
    +

    En el siguiente diagrama, las líneas gruesas representan el tiempo que el programa pasa funcionando normalmente, y las líneas delgadas representan el tiempo gastado esperando a la red. En el modelo sincrónico, el tiempo tomado por la red es parte de la línea de tiempo para un hilo de control dado. En el modelo asíncrono, iniciar una acción en la red permite que el programa continúe ejecutándose mientras la comunicación en la red sucede junto a él, notificando al programa cuando haya terminado.

    Diagrama que muestra el flujo de control en programas sincrónicos y asíncronos. La primera parte muestra un programa sincrónico, donde las fases activas y de espera del programa ocurren en una única línea secuencial. La segunda parte muestra un programa sincrónico multi-hilo, con dos líneas paralelas en las cuales las partes de espera suceden una al lado de la otra, haciendo que el programa termine más rápido. La última parte muestra un programa asíncrono, donde las múltiples acciones asíncronas se ramifican desde el programa principal, el cual se detiene en algún momento y luego continúa cuando la primera cosa por la que estaba esperando finaliza.
    -

    Otra forma de describir la diferencia es que esperar a que las acciones terminen es implícito en el modelo síncrono, mientras que es explícito, bajo nuestro control, en el modelo asíncrono.

    +

    Otra forma de describir la diferencia es que esperar a que las acciones terminen es implícito en el modelo sincrónico, mientras que es explícito, bajo nuestro control, en el modelo asíncrono.

    -

    La asincronía tiene sus pros y sus contras. Facilita la expresión de programas que no encajan en el modelo de control de línea recta, pero también puede hacer que expresar programas que siguen una línea recta sea más complicado. Veremos algunas formas de reducir esta dificultad más adelante en el capítulo.

    +

    La asincronía tiene sus pros y sus contras. Facilita la expresión de programas que no encajan en el modelo de control en línea recta, pero también puede hacer que expresar programas que siguen una línea recta sea más complicado. Veremos algunas formas de reducir esta dificultad más adelante en el capítulo.

    -

    Tanto las plataformas de programación de JavaScript prominentes —navegadores como Node.js— hacen operaciones que podrían tardar un tiempo de forma asíncrona, en lugar de depender de hilos. Dado que programar con hilos es notoriamente difícil (entender lo que hace un programa es mucho más difícil cuando está haciendo múltiples cosas a la vez), esto generalmente se considera algo bueno.

    +

    Las dos plataformas de programación de JavaScript más importantes —navegadores y Node.js— hacen que las operaciones que podrían tardar un tiempo sean asíncronas, en lugar de depender de hilos. Dado que programar con hilos es notoriamente difícil (entender lo que hace un programa es mucho más difícil cuando está haciendo múltiples cosas a la vez), esto generalmente se considera algo bueno.

    -

    Retrollamadas

    +

    Callbacks

    -

    Un enfoque para la programación asíncrona es hacer que las funciones que necesitan esperar por algo tomen un argumento adicional, una función de devolución de llamada. La función asíncrona inicia algún proceso, configura las cosas para que se llame a la función de devolución de llamada cuando el proceso termine, y luego retorna.

    +

    Un enfoque para la programación asíncrona es hacer que las funciones que necesitan esperar por algo tomen un argumento adicional, una función de devolución de llamada, o función de callback. La función asíncrona inicia algún proceso, configura las cosas para que se llame a la función de callback cuando el proceso termine, y luego retorna.

    Como ejemplo, la función setTimeout, disponible tanto en Node.js como en los navegadores, espera un número dado de milisegundos (un segundo equivale a mil milisegundos) y luego llama a una función.

    setTimeout(() => console.log("Tick"), 500);
    -

    Esperar no suele ser un tipo de trabajo muy importante, pero puede ser muy útil cuando necesitas organizar que algo suceda en un momento determinado o verificar si alguna otra acción está tomando más tiempo del esperado.

    +

    Esperar no suele ser una tarea muy importante, pero puede ser muy útil cuando necesitas hacer que algo suceda en un momento determinado o verificar si alguna otra acción está tomando más tiempo del esperado.

    -

    Otro ejemplo de una operación asincrónica común es leer un archivo desde el almacenamiento de un dispositivo. Imagina que tienes una función readTextFile, la cual lee el contenido de un archivo como una cadena y lo pasa a una función de devolución de llamada.

    +

    Otro ejemplo de operación asíncrona común es leer un archivo desde el almacenamiento de un dispositivo. Imagina que tienes una función readTextFile, la cual lee el contenido de un archivo como una cadena y lo pasa a una función de callback.

    -
    readTextFile("lista_de_compras.txt", contenido => {
    +
    readTextFile("lista_compra.txt", contenido => {
       console.log(`Lista de Compras:\n${contenido}`);
     });
     // → Lista de Compras:
    @@ -67,25 +67,25 @@ 

    La función readTextFile no es parte del estándar de JavaScript. Veremos cómo leer archivos en el navegador y en Node.js en capítulos posteriores.

    -

    Realizar múltiples acciones asincrónicas en fila usando devoluciones de llamada significa que tienes que seguir pasando nuevas funciones para manejar la continuación de la computación después de las acciones. Así es como podría verse una función asincrónica que compara dos archivos y produce un booleano que indica si su contenido es el mismo.

    +

    Realizar múltiples acciones asíncronas en serie usando callbacks implica que debes seguir pasando nuevas funciones para gestionar la continuación del proceso después de cada acción. Esta es la pinta que tendría una función asíncrona que compara dos archivos y produce un booleano que indica si su contenido es el mismo.

    -
    function compararArchivos(archivoA, archivoB, devolucionLlamada) {
    +
    function compararArchivos(archivoA, archivoB, callback) {
       readTextFile(archivoA, contenidoA => {
         readTextFile(archivoB, contenidoB => {
    -      devolucionLlamada(contenidoA == contenidoB);
    +      callback(contenidoA == contenidoB);
         });
       });
     }
    -

    Este estilo de programación es funcional, pero el nivel de indentación aumenta con cada acción asincrónica porque terminas en otra función. Hacer cosas más complicadas, como envolver acciones asincrónicas en un bucle, puede ser incómodo.

    +

    Este estilo de programación es factible, pero el nivel de sangrado aumenta con cada acción asíncrona porque terminas estando en otra función. Cosas más complicadas, como envolver acciones asíncronas en un bucle, pueden volverse muy incómodas.

    -

    De alguna manera, la asincronía es contagiosa. Cualquier función que llame a una función que trabaja de forma asincrónica debe ser asincrónica en sí misma, utilizando una devolución de llamada u otro mecanismo similar para entregar su resultado. Llamar a una devolución de llamada es algo más complicado y propenso a errores que simplemente devolver un valor, por lo que necesitar estructurar grandes partes de tu programa de esa manera no es ideal.

    +

    De alguna manera, la asincronía es contagiosa. Cualquier función que llame a una función que trabaja de forma asíncrona debe ser asíncrona en sí misma, utilizando un callback u otro mecanismo similar para entregar su resultado. Llamar a una función callback es algo más complicado y propenso a errores que simplemente devolver un valor, por lo que crear la necesidad de estructurar grandes partes de tu programa de esa manera no es ideal.

    Promesas

    -

    Una forma ligeramente diferente de construir un programa asincrónico es hacer que las funciones asincrónicas devuelvan un objeto que represente su resultado (futuro) en lugar de pasar devoluciones de llamada por todas partes. De esta manera, tales funciones realmente devuelven algo significativo, y la estructura del programa se asemeja más a la de los programas síncronos.

    +

    Una forma ligeramente diferente de construir un programa asíncrono es hacer que las funciones asíncronas devuelvan un objeto que represente su resultado (futuro) en lugar de pasar callbacks por todas partes. De esta manera, tales funciones realmente devuelven algo con sentido, y la estructura del programa se asemeja más a la de los programas sincrónicos.

    -

    Para esto sirve la clase estándar Promise. Una promesa es un recibo que representa un valor que aún puede no estar disponible. Proporciona un método then que te permite registrar una función que debe ser llamada cuando la acción por la que está esperando finalice. Cuando la promesa se resuelve, es decir, su valor se vuelve disponible, esas funciones (puede haber varias) son llamadas con el valor del resultado. Es posible llamar a then en una promesa que ya ha sido resuelta; tu función seguirá siendo llamada.

    +

    Para esto sirve la clase estándar Promise. Una promesa es un recibo que representa un valor que aún puede no estar disponible. Proporciona un método then que te permite registrar una función que debe ser llamada cuando la acción por la que está esperando finalice. Cuando la promesa se resuelve, es decir, cuando su valor se vuelve disponible, esas funciones (puede haber varias) son llamadas con el valor del resultado. Es posible llamar a then en una promesa que ya ha sido resuelta —tu función aún será llamada.

    La forma más sencilla de crear una promesa es llamando a Promise.resolve. Esta función se asegura de que el valor que le proporcionas esté envuelto en una promesa. Si ya es una promesa, simplemente se devuelve; de lo contrario, obtienes una nueva promesa que se resuelve de inmediato con tu valor como resultado.

    @@ -93,71 +93,71 @@

    valor => console.log(`Obtenido ${valor}`)); // → Obtenido 15

    -

    Para crear una promesa que no se resuelva inmediatamente, puedes utilizar Promise como constructor. Tiene una interfaz un tanto extraña: el constructor espera una función como argumento, la cual llama inmediatamente, pasándole una función que puede utilizar para resolver la promesa.

    +

    Para crear una promesa que no se resuelva inmediatamente, puedes utilizar Promise como constructor. Tiene una interfaz un tanto extraña: el constructor espera una función como argumento, a la cual llama inmediatamente, pasándole como argumento una función (resolver, en el ejemplo) que puede utilizar para resolver la promesa.

    Así es como podrías crear una interfaz basada en promesas para la función readTextFile:

    -
    function textFile(nombreArchivo) {
    -  return new Promise(resolve => {
    -    readTextFile(nombreArchivo, texto => resolve(texto));
    +
    function archivoTexto(nombreArchivo) {
    +  return new Promise(resolver => {
    +    readTextFile(nombreArchivo, texto => resolver(texto));
       });
     }
     
    -textFile("planes.txt").then(console.log);
    +archivoTexto("planes.txt").then(console.log);
    -

    Observa cómo esta función asíncrona devuelve un valor significativo: una promesa para proporcionarte el contenido del archivo en algún momento futuro.

    +

    Observa cómo esta función asíncrona devuelve un valor con sentido: una promesa de proporcionarte el contenido del archivo en algún momento futuro.

    -

    Una característica útil del método then es que él mismo devuelve otra promesa que se resuelve al valor retornado por la función de devolución de llamada o, si esa función devuelve una promesa, al valor al que esa promesa se resuelve. De esta forma, puedes “encadenar” varias llamadas a then para configurar una secuencia de acciones asíncronas.

    +

    Una característica útil del método then es que él mismo devuelve otra promesa que se resuelve al valor retornado por la función de callback o, si esa función devuelve una promesa, al valor al que esa promesa se resuelve. De esta forma, puedes “encadenar” varias llamadas a then para configurar una secuencia de acciones asíncronas.

    Esta función, la cual lee un archivo lleno de nombres de archivos y devuelve el contenido de un archivo aleatorio de esa lista, muestra este tipo de cadena asíncrona de promesas.

    -
    function randomFile(archivoLista) {
    -  return textFile(archivoLista)
    +
    function archivoAleatorio(archivoLista) {
    +  return archivoTexto(archivoLista)
         .then(contenido => contenido.trim().split("\n"))
         .then(ls => ls[Math.floor(Math.random() * ls.length)])
    -    .then(nombreArchivo => textFile(nombreArchivo));
    +    .then(nombreArchivo => archivoTexto(nombreArchivo));
     }
    -

    La función devuelve el resultado de esta cadena de llamadas a then. La promesa inicial obtiene la lista de archivos como una cadena. La primera llamada a then transforma esa cadena en un array de líneas, produciendo una nueva promesa. La segunda llamada a then elige una línea aleatoria de eso, produciendo una tercera promesa que arroja un único nombre de archivo. La llamada final a then lee este archivo, de modo que el resultado de la función en su totalidad es una promesa que devuelve el contenido de un archivo aleatorio.

    +

    La función devuelve el resultado de esta cadena de llamadas a then. La promesa inicial obtiene la lista de archivos como una cadena. La primera llamada a then transforma esa cadena en un array de líneas, produciendo una nueva promesa. La segunda llamada a then elige una línea aleatoria del resultado de resolver esta promesa, produciendo una tercera promesa que arroja un único nombre de archivo. La llamada final a then lee este archivo, de modo que el resultado de la función en su totalidad es una promesa que devuelve el contenido de un archivo aleatorio.

    -

    En este código, las funciones utilizadas en las primeras dos llamadas a then devuelven un valor regular, que se pasará inmediatamente a la promesa devuelta por then cuando la función regrese. La última devuelve una promesa (textFile(nombreArchivo)), convirtiéndola en un paso asincrónico real.

    +

    En este código, las funciones utilizadas en las primeras dos llamadas a then devuelven un valor normal, que se pasará inmediatamente a la promesa devuelta por then cuando la función retorne. La última devuelve una promesa (archivoTexto(nombreArchivo)), lo que la convierte en un paso asíncrono de verdad.

    -

    También habría sido posible realizar todos estos pasos dentro de un solo callback de then, ya que solo el último paso es realmente asíncrono. Pero los tipos de envolturas then que solo realizan alguna transformación de datos síncrona son a menudo útiles, por ejemplo, cuando deseas devolver una promesa que produzca una versión procesada de algún resultado asíncrono.

    +

    También habría sido posible realizar todos estos pasos dentro de un solo callback de then, ya que solo el último paso es realmente asíncrono. Pero el tipo de envolturas then que solo realizan alguna transformación de datos sincrónica son a menudo útiles, por ejemplo, cuando deseas devolver una promesa que produzca una versión procesada de algún resultado asíncrono.

    -
    function jsonFile(nombreArchivo) {
    -  return textFile(nombreArchivo).then(JSON.parse);
    +
    function archivoJson(nombreArchivo) {
    +  return archivoTexto(nombreArchivo).then(JSON.parse);
     }
     
    -jsonFile("package.json").then(console.log);
    +archivoJson("package.json").then(console.log);

    En general, es útil pensar en las promesas como un mecanismo que permite al código ignorar la pregunta de cuándo va a llegar un valor. Un valor normal tiene que existir realmente antes de que podamos hacer referencia a él. Un valor prometido es un valor que puede estar allí o podría aparecer en algún momento en el futuro. Las operaciones definidas en términos de promesas, al conectarlas con llamadas then, se ejecutan de forma asíncrona a medida que sus entradas están disponibles.

    -

    Falla

    +

    Fallo

    -

    Las computaciones regulares de JavaScript pueden fallar al lanzar una excepción. Las computaciones asíncronas a menudo necesitan algo así. Una solicitud de red puede fallar, un archivo puede no existir, o algún código que forma parte de la computación asíncrona puede lanzar una excepción.

    +

    Un procedimiento normal de JavaScript puede fallar lanzando una excepción. Los procedimientos asíncronos a menudo necesitan algo así. Una solicitud de red puede fallar, un archivo puede no existir, o algún código que forma parte de un procedimiento asíncrono puede lanzar una excepción.

    -

    Uno de los problemas más apremiantes con el estilo de programación asíncrona basado en devoluciones de llamada es que hace extremadamente difícil asegurarse de que las fallas se informen adecuadamente a las devoluciones de llamada.

    +

    Uno de los problemas más urgentes del estilo de programación asíncrona basado en callbacks es que hace extremadamente difícil asegurarse de que los fallos se reporten adecuadamente a las funciones de callback.

    -

    Una convención ampliamente utilizada es que el primer argumento de la devolución de llamada se utiliza para indicar que la acción falló, y el segundo contiene el valor producido por la acción cuando fue exitosa.

    +

    Una convención ampliamente utilizada es que el primer argumento de la función de callback se utiliza para indicar que la acción ha fallado, y el segundo contiene el valor producido por la acción cuando ha terminado con éxito.

    unaFuncionAsincrona((error, valor) => {
       if (error) manejarError(error);
       else procesarValor(valor);
     });
    -

    Tales funciones de devolución de llamada siempre deben verificar si recibieron una excepción y asegurarse de que cualquier problema que causen, incluidas las excepciones lanzadas por las funciones que llaman, se capturen y se den a la función correcta.

    +

    Tales funciones de callback siempre deben verificar si recibieron una excepción y asegurarse de que cualquier problema que causen, incluidas las excepciones lanzadas por las funciones que llaman, se capturen y se den a la función correcta.

    -

    Las promesas facilitan esto. Pueden ser o bien resueltas (la acción se completó con éxito) o rechazadas (falló). Los manejadores de resolución (como se registran con then) se llaman solo cuando la acción es exitosa, y los rechazos se propagan a la nueva promesa que es devuelta por then. Cuando un manejador lanza una excepción, esto causa automáticamente que la promesa producida por la llamada a su then sea rechazada. Entonces, si algún elemento en una cadena de acciones asíncronas falla, el resultado de toda la cadena se marca como rechazado, y no se llaman manejadores de éxito más allá del punto donde falló.

    +

    Las promesas facilitan esto. Pueden ser o bien resueltas (la acción se completó con éxito) o rechazadas (la acción falló). Los manejadores de resolución (registrados con then) se llaman solo cuando la acción es exitosa, y los rechazos se propagan a la nueva promesa devuelta por then. Cuando un manejador lanza una excepción, esto causa automáticamente que la promesa producida por su llamada a then sea rechazada. Entonces, si algún elemento en una cadena de acciones asíncronas falla, el resultado de toda la cadena se marca como rechazado, y ningún manejador de éxito se ejecuta más allá del punto en el que ocurrió el fallo.

    -

    Al igual que resolver una promesa proporciona un valor, rechazar una también lo hace, generalmente llamado el motivo del rechazo. Cuando una excepción en una función manejadora causa el rechazo, el valor de la excepción se usa como el motivo. De manera similar, cuando una función manejadora devuelve una promesa que es rechazada, ese rechazo fluye hacia la siguiente promesa. Existe una función Promise.reject que crea una nueva promesa inmediatamente rechazada.

    +

    Al igual que resolver una promesa proporciona un valor, rechazar una también lo hace, generalmente llamado el motivo del rechazo. Cuando una excepción en una función manejadora causa el rechazo, el valor de la excepción se usa como dicho motivo. De manera similar, cuando una función manejadora devuelve una promesa que es rechazada, ese rechazo fluye hacia la siguiente promesa. Existe una función Promise.reject que crea una nueva promesa inmediatamente rechazada.

    Para manejar explícitamente tales rechazos, las promesas tienen un método catch que registra un manejador para ser llamado cuando la promesa es rechazada, similar a cómo los manejadores de then manejan la resolución normal. También es muy similar a then en que devuelve una nueva promesa, que se resuelve con el valor de la promesa original cuando se resuelve normalmente y con el resultado del manejador catch en caso contrario. Si un manejador de catch lanza un error, la nueva promesa también se rechaza.

    -

    Como un atajo, then también acepta un manejador de rechazo como segundo argumento, para poder instalar ambos tipos de manejadores en una sola llamada de método.

    +

    Como atajo, then también acepta un manejador de rechazo como segundo argumento, conque puedes instalar ambos tipos de manejadores en una sola llamada de método: .then(manejadorDeAceptación, manejadorDeRechazo).

    -

    Una función pasada al constructor Promise recibe un segundo argumento, junto con la función de resolución, que puede usar para rechazar la nueva promesa.Cuando nuestra función readTextFile encuentra un problema, pasa el error a su función de devolución de llamada como segundo argumento. Nuestro envoltorio textFile debería realmente examinar ese argumento, de manera que un fallo cause que la promesa que devuelve sea rechazada.

    +

    Una función pasada al constructor Promise recibe un segundo argumento, junto con la función de resolución, que puede usar para rechazar la nueva promesa. Cuando nuestra función readTextFile encuentra un problema, pasa el error a su función callback como segundo argumento. Nuestro envoltorio archivoTexto debería realmente examinar ese argumento, de manera que un fallo cause que la promesa que devuelve sea rechazada.

    -
    function textFile(filename) {
    +
    function archivoTexto(filename) {
       return new Promise((resolve, reject) => {
         readTextFile(filename, (text, error) => {
           if (error) reject(error);
    @@ -168,7 +168,7 @@ 

    Las cadenas de valores de promesa creadas por llamadas a then y catch forman así un pipeline a través del cual se mueven los valores asíncronos o fallos. Dado que dichas cadenas se crean registrando manejadores, cada eslabón tiene asociado un manejador de éxito o un manejador de rechazo (o ambos). Los manejadores que no coinciden con el tipo de resultado (éxito o fallo) son ignorados. Pero aquellos que coinciden son llamados, y su resultado determina qué tipo de valor viene a continuación: éxito cuando devuelve un valor que no es una promesa, rechazo cuando genera una excepción, y el resultado de la promesa cuando devuelve una promesa.

    -
    new Promise((_, reject) => reject(new Error("Fail")))
    +
    new Promise((_, rechazar) => rechazar(new Error("Fail")))
       .then(value => console.log("Manejador 1:", value))
       .catch(reason => {
         console.log("Error capturado " + reason);
    @@ -176,130 +176,137 @@ 

    value => console.log("Manejador 2:", value)); // → Error capturado Error: Fail -// → Handler 2: nothing

    +// → Manejador 2: nada
    + +

    N. del T.: nótese cómo el parámetro que se pasa al constructor Promise es una función con dos parámetros que no representan otra cosa que el nombre de las funciones de resolución y rechazo que espera el constructor. JavaScript ya sabe que la función cuyo nombre se pasa como primer parámetro hará lo que se necesite cuando la promesa se resuelve sin problemas, y que la función cuyo nombre se pasa como segundo parámetro hará lo propio cuando la promesa es rechazada. El nombre que les pongamos a dichos parámetros es indiferente, aunque suele usarse resolve para el primer caso y reject para el segundo o, como en este ejemplo, _ para el primero (porque ni siquiera lo necesitamos) y rechazar para el segundo.

    +
    -

    La primera función de manejador regular no es llamada, porque en ese punto del pipeline la promesa contiene un rechazo. El manejador catch maneja ese rechazo y devuelve un valor, que se le da a la segunda función de manejador.

    +

    El primer manejador then no es llamado porque, en ese punto del pipeline, la promesa contiene un rechazo. El manejador catch maneja ese rechazo y devuelve un valor, que se le da al segundo manejador then.

    Cuando una excepción no controlada es manejada por el entorno, los entornos de JavaScript pueden detectar cuándo un rechazo de promesa no es manejado y lo reportarán como un error.

    Carla

    -

    Es un día soleado en Berlín. La pista del antiguo aeropuerto desmantelado rebosa de ciclistas y patinadores en línea. En el césped cerca de un contenedor de basura un grupo de cuervos se agita ruidosamente, intentando convencer a un grupo de turistas de que les den sus sándwiches.

    +

    Es un día soleado en Berlín. La pista del antiguo aeropuerto desmantelado está llena de ciclistas y patinadores en línea. En el césped, cerca de un contenedor de basura, un grupo de cuervos se agita ruidosamente, intentando convencer a un grupo de turistas de que les den sus sándwiches.

    + +

    Uno de los cuervos destaca: una hembra grande, andrajosa, con algunas plumas blancas en su ala derecha. Está atrayendo a la gente con una habilidad y confianza que sugieren que ha estado haciendo esto durante mucho tiempo. Cuando un anciano se distrae con las travesuras de otro cuervo, ella se abalanza como quien no quiere la cosa, le arrebata su bollo a medio comer de la mano y se aleja planeando.

    -

    Uno de los cuervos destaca: una hembra grande andrajosa con algunas plumas blancas en su ala derecha. Está atrayendo a la gente con habilidad y confianza que sugieren que ha estado haciendo esto durante mucho tiempo. Cuando un anciano se distrae con las travesuras de otro cuervo, ella se abalanza casualmente, arrebata su bollo a medio comer de su mano y se aleja planeando.

    +

    A diferencia del resto del grupo, que parece estar feliz de pasar el día holgazaneando por ahí, el cuervo grande parece tener un propósito. Llevando su botín, vuela directamente hacia el techo del edificio del hangar, desapareciendo por un conducto de ventilación.

    -

    A diferencia del resto del grupo, que parece estar feliz de pasar el día holgazaneando aquí, el cuervo grande parece tener un propósito. Llevando su botín, vuela directamente hacia el techo del edificio del hangar, desapareciendo en una rejilla de ventilación.

    +

    Dentro del edificio, se puede escuchar un sonido peculiar: suave, pero persistente. Viene de un espacio estrecho bajo el techo de una escalera sin terminar. El cuervo está sentado allí, rodeado de sus botines robados: media docena de teléfonos inteligentes (varios de los cuales están encendidos) y un enredo de cables. Golpea rápidamente la pantalla de uno de los teléfonos con su pico. Aparecen palabras en él. Si no supieras más, pensarías que estaba escribiendo.

    -

    Dentro del edificio, se puede escuchar un sonido peculiar: suave, pero persistente. Viene de un espacio estrecho bajo el techo de una escalera sin terminar. El cuervo está sentado allí, rodeado de sus botines robados, media docena de teléfonos inteligentes (varios de los cuales están encendidos) y un enredo de cables. Golpea rápidamente la pantalla de uno de los teléfonos con su pico. Aparecen palabras en él. Si no supieras mejor, pensarías que estaba escribiendo.Este cuervo es conocido por sus pares como “cāāw-krö". Pero dado que esos sonidos no son adecuados para las cuerdas vocales humanas, la llamaremos Carla.

    +

    Este cuervo es conocido por sus iguales como “cāāw-krö". Pero dado que esos sonidos no son adecuados para las cuerdas vocales humanas, la llamaremos Carla.

    -

    Carla es un cuervo algo peculiar. En su juventud, estaba fascinada por el lenguaje humano, escuchando a la gente hasta que tuvo un buen entendimiento de lo que decían. Más tarde, su interés se trasladó a la tecnología humana, y comenzó a robar teléfonos para estudiarlos. Su proyecto actual es aprender a programar. El texto que está escribiendo en su laboratorio secreto, de hecho, es un fragmento de código JavaScript.

    +

    Carla es un cuervo algo peculiar. En su juventud, estaba fascinada por el lenguaje humano, escuchando a la gente hasta que llegó incluso a entender lo que decían. Más tarde, su interés se trasladó a la tecnología humana, y comenzó a robar teléfonos para estudiarlos. Su proyecto actual es aprender a programar. El texto que está escribiendo en su laboratorio secreto, de hecho, es un fragmento de código JavaScript.

    Infiltración

    -

    A Carla le encanta Internet. Fastidiosamente, el teléfono en el que está trabajando está a punto de quedarse sin datos prepagos. El edificio tiene una red inalámbrica, pero se requiere un código para acceder a ella.

    +

    A Carla le encanta Internet. Por desgracia, el teléfono en el que está trabajando está a punto de quedarse sin datos. El edificio tiene una red inalámbrica, pero se requiere un código para acceder a ella.

    -

    Afortunadamente, los enrutadores inalámbricos en el edificio tienen 20 años y están mal protegidos. Tras investigar un poco, Carla descubre que el mecanismo de autenticación de la red tiene una falla que puede aprovechar. Al unirse a la red, un dispositivo debe enviar el código correcto de 6 dígitos. El punto de acceso responderá con un mensaje de éxito o fracaso dependiendo de si se proporciona el código correcto. Sin embargo, al enviar solo un código parcial (digamos, solo 3 dígitos), la respuesta es diferente según si esos dígitos son el inicio correcto del código o no. Cuando se envía un número incorrecto, se recibe inmediatamente un mensaje de fracaso. Cuando se envían los correctos, el punto de acceso espera más dígitos.

    +

    Afortunadamente, los rúteres inalámbricos del edificio tienen 20 años y están mal protegidos. Tras investigar un poco, Carla descubre que el mecanismo de autenticación de la red tiene un fallo que puede aprovechar. Al unirse a la red, un dispositivo debe enviar el código correcto de 6 dígitos. El punto de acceso responderá con un mensaje de éxito o fracaso dependiendo de si se proporciona el código correcto. Sin embargo, al enviar solo un código parcial (digamos, solo 3 dígitos), la respuesta es diferente según si esos dígitos son el inicio correcto del código o no. Cuando se envía un número incorrecto, se recibe inmediatamente un mensaje de fracaso. Cuando se envían los dígitos correctos, el punto de acceso espera más dígitos.

    -

    Esto hace posible acelerar enormemente la adivinación del número. Carla puede encontrar el primer dígito probando cada número a su vez, hasta que encuentre uno que no devuelva inmediatamente un fracaso. Teniendo un dígito, puede encontrar el segundo de la misma manera, y así sucesivamente, hasta que conozca todo el código de acceso.

    +

    Esto acelera enormemente el descubrimiento del número. Carla puede encontrar el primer dígito probando cada número uno a uno, hasta que encuentre uno que no devuelva inmediatamente un fracaso. Teniendo un dígito, puede encontrar el segundo de la misma manera, y así sucesivamente, hasta que conozca todo el código de acceso.

    Supongamos que tenemos una función joinWifi. Dado el nombre de la red y el código de acceso (como una cadena), intenta unirse a la red, devolviendo una promesa que se resuelve si tiene éxito, y se rechaza si la autenticación falla. Lo primero que necesitamos es una forma de envolver una promesa para que se rechace automáticamente después de transcurrir demasiado tiempo, de manera que podamos avanzar rápidamente si el punto de acceso no responde.

    -
    function withTimeout(promise, tiempo) {
    -  return new Promise((resolve, reject) => {
    -    promise.then(resolve, reject);
    -    setTimeout(() => reject("Se agotó el tiempo"), tiempo);
    +
    function conTiempoDeEspera(promesa, tiempo) {
    +  return new Promise((resolver, rechazar) => {
    +    promesa.then(resolver, rechazar);
    +    setTimeout(() => rechazar("Se agotó el tiempo"), tiempo);
       });
     }
    -

    Esto aprovecha el hecho de que una promesa solo puede resolverse o rechazarse una vez: si la promesa dada como argumento se resuelve o se rechaza primero, ese será el resultado de la promesa devuelta por withTimeout. Si, por otro lado, el setTimeout se ejecuta primero, rechazando la promesa, se ignoran cualquier llamada posterior a resolve o reject.

    +

    Esto aprovecha el hecho de que una promesa solo puede resolverse o rechazarse una vez: si la promesa dada como argumento se resuelve o se rechaza primero, ese será el resultado de la promesa devuelta por conTiempoDeEspera. Si, por otro lado, el setTimeout se ejecuta primero, rechazando la promesa, se ignora cualquier llamada posterior de resolución o rechazo.

    + +

    Para encontrar todo el código de acceso, necesitamos buscar repetidamente el siguiente dígito probando cada dígito. Si la autenticación tiene éxito, sabremos que hemos encontrado lo que buscamos. Si falla inmediatamente, sabremos que ese dígito era incorrecto y debemos probar con el siguiente. Si el tiempo de la solicitud se agota, hemos encontrado otro dígito correcto y debemos continuar agregando otro dígito.

    -

    Para encontrar todo el código de acceso, necesitamos buscar repetidamente el siguiente dígito probando cada dígito. Si la autenticación tiene éxito, sabremos que hemos encontrado lo que buscamos. Si falla inmediatamente, sabremos que ese dígito era incorrecto y debemos probar con el siguiente. Si la solicitud se agota, hemos encontrado otro dígito correcto y debemos continuar agregando otro dígito.Debido a que no puedes esperar una promesa dentro de un bucle for, Carla utiliza una función recursiva para llevar a cabo este proceso. En cada llamada, obtiene el código tal como lo conocemos hasta ahora, así como el siguiente dígito a probar. Dependiendo de lo que suceda, puede devolver un código terminado, o llamar de nuevo a sí misma, ya sea para comenzar a descifrar la siguiente posición en el código, o para intentarlo de nuevo con otro dígito.

    +

    Como no puedes esperar una promesa dentro de un bucle for, Carla utiliza una función recursiva para llevar a cabo este proceso. En cada llamada, obtiene el código tal como lo conocemos hasta ahora, así como el siguiente dígito a probar. Dependiendo de lo que suceda, puede devolver un código terminado, o llamarse de nuevo a sí misma, ya sea para comenzar a descifrar la siguiente posición en el código, o para intentarlo de nuevo con otro dígito.

    -
    function crackPasscode(networkID) {
    -  function nextDigit(code, digit) {
    -    let newCode = code + digit;
    -    return withTimeout(joinWifi(networkID, newCode), 50)
    -      .then(() => newCode)
    -      .catch(failure => {
    -        if (failure == "Timed out") {
    -          return nextDigit(newCode, 0);
    -        } else if (digit < 9) {
    -          return nextDigit(code, digit + 1);
    +
    function crackearContraseña(identificadorDeRed) {
    +  function siguienteDígito(código, dígito) {
    +    let nuevoCódigo = código + dígito;
    +    return conTiempoDeEspera(joinWifi(identificadorDeRed, nuevoCódigo), 50)
    +      .then(() => nuevoCódigo)
    +      .catch(fallo => {
    +        if (fallo == "Se agotó el tiempo") {
    +          return siguienteDígito(nuevoCódigo, 0);
    +        } else if (dígito < 9) {
    +          return siguienteDígito(código, dígito + 1);
             } else {
    -          throw failure;
    +          throw fallo;
             }
           });
       }
    -  return nextDigit("", 0);
    +  return siguienteDígito("", 0);
     }

    El punto de acceso suele responder a solicitudes de autenticación incorrectas en aproximadamente 20 milisegundos, por lo que, para estar seguros, esta función espera 50 milisegundos antes de hacer expirar una solicitud.

    -
    crackPasscode("HANGAR 2").then(console.log);
    +
    crackearContraseña("HANGAR 2").then(console.log);
     // → 555555

    Carla inclina la cabeza y suspira. Esto habría sido más satisfactorio si el código hubiera sido un poco más difícil de adivinar.

    Funciones asíncronas

    -

    Incluso con promesas, este tipo de código asíncrono es molesto de escribir. Las promesas a menudo necesitan ser encadenadas de manera verbosa y arbitraria. Y nos vimos obligados a introducir una función recursiva solo para crear un bucle.

    +

    Incluso con promesas, este tipo de código asíncrono es molesto de escribir. A menudo, necesitamos encadenar promesas de manera verbosa y de aparencia arbitraria —Carla ha tenido que usar una función recursiva para crear un bucle asíncrono.

    -

    Lo que la función de descifrado realmente hace es completamente lineal: siempre espera a que la acción anterior se complete antes de comenzar la siguiente. En un modelo de programación síncrona, sería más sencillo de expresar.

    +

    Lo que la función crackearContraseña realmente hace es completamente lineal: siempre espera a que la acción anterior se complete antes de comenzar la siguiente. Sería más sencillo de expresar en un modelo de programación sincrónica.

    -

    La buena noticia es que JavaScript te permite escribir código pseudo-sincrónico para describir la computación asíncrona. Una función async es una función que implícitamente devuelve una promesa y que puede, en su cuerpo, await otras promesas de una manera que parece sincrónica.

    +

    La buena noticia es que JavaScript te permite escribir código pseudo-sincrónico para describir procedimientos asíncronos. Una función async es una función que implícitamente devuelve una promesa y que puede, en su cuerpo, esperar (await) otras promesas de una manera que parece sincrónica.

    -

    Podemos reescribir crackPasscode de la siguiente manera:

    +

    Podemos reescribir crackearContraseña de la siguiente manera:

    -
    async function crackPasscode(networkID) {
    -  for (let code = "";;) {
    -    for (let digit = 0;; digit++) {
    -      let newCode = code + digit;
    +
    async function crackearContraseña(identificadorDeRed) {
    +  for (let código = "";;) {
    +    for (let dígito = 0;; dígito++) {
    +      let nuevoCódigo = código + dígito;
           try {
    -        await withTimeout(joinWifi(networkID, newCode), 50);
    -        return newCode;
    -      } catch (failure) {
    -        if (failure == "Timed out") {
    -          code = newCode;
    +        await withTimeout(joinWifi(identificadorDeRed, nuevoCódigo), 50);
    +        return nuevoCódigo;
    +      } catch (fallo) {
    +        if (fallo == "Se agotó el tiempo") {
    +          código = nuevoCódigo;
               break;
    -        } else if (digit == 9) {
    -          throw failure;
    +        } else if (dígito == 9) {
    +          throw fallo;
             }
           }
         }
       }
     }
    -

    Esta versión muestra de manera más clara la estructura de doble bucle de la función (el bucle interno prueba el dígito 0 al 9, el bucle externo añade dígitos al código de acceso).

    +

    Esta versión muestra de manera más clara la estructura de doble bucle de la función (el bucle interno prueba los dígitos del 0 al 9 y el bucle externo añade dígitos al código de acceso).

    -

    Una función async está marcada con la palabra async antes de la palabra clave function. Los métodos también pueden ser marcados como async escribiendo async antes de su nombre. Cuando se llama a una función o método de esta manera, devuelve una promesa. Tan pronto como la función devuelve algo, esa promesa se resuelve. Si el cuerpo genera una excepción, la promesa es rechazada.

    +

    Una función async está marcada con la palabra async antes de la palabra clave function. Los métodos también se pueden marcar como async escribiendo async antes de su nombre. Cuando se llama a una función o método de esta manera, lo que se devuelve es una promesa. Tan pronto como la función devuelve algo, esa promesa se resuelve. Si el cuerpo genera una excepción, la promesa es rechazada.

    Dentro de una función async, la palabra await puede colocarse delante de una expresión para esperar a que una promesa se resuelva y luego continuar con la ejecución de la función. Si la promesa es rechazada, se genera una excepción en el punto del await.

    -

    Una función así ya no se ejecuta, como una función regular de JavaScript, de principio a fin de una sola vez. En su lugar, puede estar congelada en cualquier punto que tenga un await, y puede continuar más tarde.

    +

    Una función de estas ya no se ejecuta de principio a fin de una vez como una función normal de JavaScript. En su lugar, puede estar congelada en cualquier punto que tenga un await, y continuar más tarde.

    -

    Para la mayoría del código asíncrono, esta notación es más conveniente que usar directamente promesas. Aún necesitas comprender las promesas, ya que en muchos casos todavía interactúas con ellas directamente. Pero al encadenarlas, las funciones async suelen ser más agradables de escribir que encadenar llamadas then.

    +

    Para la mayoría del código asíncrono, esta notación es más conveniente que usar directamente promesas. Aún así, es necesario comprender las promesas, ya que en muchos casos interactuarás con ellas directamente de todos modos. Pero al encadenarlas, las funciones async suelen ser más agradables de escribir que encadenar llamadas a then.

    Generadores

    -

    Esta capacidad de pausar y luego reanudar funciones no es exclusiva de las funciones async. JavaScript también tiene una característica llamada generador functions. Son similares, pero sin las promesas.

    +

    Esta capacidad de pausar y luego reanudar funciones no es exclusiva de las funciones async. JavaScript también tiene una característica llamada funciones generadoras (generator functions). Estas son parecidas a las funciones async, pero sin las promesas.

    -

    Cuando defines una función con function* (colocando un asterisco después de la palabra function), se convierte en un generador. Al llamar a un generador, devuelve un iterador, que ya vimos en el Capítulo 6.

    +

    Cuando defines una función con function* (colocando un asterisco después de la palabra function), se convierte en un generador. Al llamar a un generador, este devuelve un iterador, que ya estudiamos en el Capítulo 6.

    -
    function* powers(n) {
    -  for (let current = n;; current *= n) {
    -    yield current;
    +
    function* potencias(n) {
    +  for (let actual = n;; actual *= n) {
    +    yield actual;
       }
     }
     
    -for (let power of powers(3)) {
    -  if (power > 50) break;
    -  console.log(power);
    +for (let potencia of potencias(3)) {
    +  if (potencia > 50) break;
    +  console.log(potencia);
     }
     // → 3
     // → 9
     // → 27
    -

    Inicialmente, al llamar a powers, la función se congela desde el principio. Cada vez que llamas a next en el iterador, la función se ejecuta hasta que encuentra una expresión yield, que la pausa y hace que el valor generado se convierta en el próximo valor producido por el iterador. Cuando la función retorna (la del ejemplo nunca lo hace), el iterador ha terminado.

    +

    Inicialmente, al llamar a potencias, la función se congela desde el principio. Cada vez que llamas a next en el iterador, la función se ejecuta hasta que encuentra una expresión yield, que la pausa y hace que el valor generado se convierta en el próximo valor producido por el iterador. Cuando la función retorna (la del ejemplo nunca lo hace), el iterador ha terminado.

    Escribir iteradores a menudo es mucho más fácil cuando usas funciones generadoras. El iterador para la clase Group (del ejercicio en el Capítulo 6) se puede escribir con este generador:

    @@ -313,30 +320,30 @@

    Tales expresiones yield solo pueden ocurrir directamente en la función generadora misma y no en una función interna que definas dentro de ella. El estado que un generador guarda, al hacer yield, es solo su entorno local y la posición donde hizo el yield.

    -

    Una función async es un tipo especial de generador. Produce una promesa al llamarla, la cual se resuelve cuando retorna (termina) y se rechaza cuando arroja una excepción. Cada vez que hace un yield (awaits) una promesa, el resultado de esa promesa (valor o excepción generada) es el resultado de la expresión await.

    +

    Una función async es un tipo especial de generador. Produce una promesa al llamarla, la cual se resuelve cuando retorna (termina) y se rechaza cuando arroja una excepción. Cada vez que hace un yield de una promesa (es decir, la espera con await), el resultado de esa promesa (el valor o la excepción generada) es el resultado de la expresión await.

    -

    Un Proyecto de Arte de Corvidos

    +

    Un Proyecto de Arte de Córvidos

    Esta mañana, Carla se despertó con un ruido desconocido en la pista de aterrizaje fuera de su hangar. Saltando al borde del techo, ve que los humanos están preparando algo. Hay muchos cables eléctricos, un escenario y una especie de gran pared negra que están construyendo.

    -

    Siendo una cuerva curiosa, Carla echa un vistazo más de cerca a la pared. Parece estar compuesta por varios dispositivos grandes con frente de vidrio conectados a cables. En la parte trasera, los dispositivos dicen “LedTec SIG-5030”.

    +

    Como es una cuerva curiosa, Carla echa un vistazo más de cerca a la pared. Parece estar compuesta por varios dispositivos grandes con un frontal de vidrio conectados a cables. En la parte trasera, los dispositivos dicen “LedTec SIG-5030”.

    -

    Una rápida búsqueda en Internet saca a relucir un manual de usuario para estos dispositivos. Parecen ser señales de tráfico, con una matriz programable de luces LED ambarinas. La intención de los humanos probablemente sea mostrar algún tipo de información en ellas durante su evento. Curiosamente, las pantallas pueden ser programadas a través de una red inalámbrica. ¿Podría ser que estén conectadas a la red local del edificio?

    +

    Una rápida búsqueda en Internet saca a relucir un manual de usuario para estos dispositivos. Parecen ser señales de tráfico, con una matriz programable de luces LED de color ámbar. La intención de los humanos probablemente sea mostrar algún tipo de información en ellas durante su evento. Curiosamente, las pantallas pueden ser programadas a través de una red inalámbrica. ¿Será posible que estén conectadas a la red local del edificio?

    -

    Cada dispositivo en una red recibe una dirección IP, que otros dispositivos pueden usar para enviarle mensajes. Hablamos más sobre eso en el Capítulo 13. Carla nota que sus propios teléfonos reciben direcciones como 10.0.0.20 o 10.0.0.33. Podría valer la pena intentar enviar mensajes a todas esas direcciones y ver si alguna responde a la interfaz descrita en el manual de las señales.

    +

    Cada dispositivo en una red recibe una dirección IP, que otros dispositivos pueden usar para enviarle mensajes. Hablaremos más sobre eso en el Capítulo 13. Carla se da cuenta que sus propios teléfonos reciben direcciones como 10.0.0.20 o 10.0.0.33. Podría valer la pena intentar enviar mensajes a todas esas direcciones y ver si alguna responde a la interfaz descrita en el manual de las señales.

    El Capítulo 18 muestra cómo hacer solicitudes reales en redes reales. En este capítulo, usaremos una función ficticia simplificada llamada request para la comunicación en red. Esta función toma dos argumentos: una dirección de red y un mensaje, que puede ser cualquier cosa que se pueda enviar como JSON, y devuelve una promesa que se resuelve con una respuesta de la máquina en la dirección dada, o se rechaza si hubo un problema.

    -

    Según el manual, puedes cambiar lo que se muestra en una señal SIG-5030 enviándole un mensaje con contenido como {"command": "display", "data": [0, 0, 3, …]}, donde data contiene un número por cada punto de LED, indicando su brillo; 0 significa apagado, 3 significa brillo máximo. Cada señal tiene 50 luces de ancho y 30 luces de alto, por lo que un comando de actualización debe enviar 1500 números.

    +

    Según el manual, puedes cambiar lo que se muestra en una señal SIG-5030 enviándole un mensaje con contenido como {"command": "display", "data": [0, 0, 3, …]}, donde data contiene un número por cada LED, indicando su brillo; 0 significa apagado, 3 significa brillo máximo. Cada señal tiene 50 luces de ancho y 30 luces de alto, por lo que un comando de actualización debe enviar 1500 números.

    Este código envía un mensaje de actualización de pantalla a todas las direcciones en la red local para ver cuál se queda. Cada uno de los números en una dirección IP puede ir de 0 a 255. En los datos que envía, activa un número de luces correspondiente al último número de la dirección de red.

    -
    for (let addr = 1; addr < 256; addr++) {
    +
    for (let dir = 1; dir < 256; dir++) {
       let data = [];
       for (let n = 0; n < 1500; n++) {
    -    data.push(n < addr ? 3 : 0);
    +    data.push(n < dir ? 3 : 0);
       }
    -  let ip = `10.0.0.${addr}`;
    +  let ip = `10.0.0.${dir}`;
       request(ip, {command: "display", data})
         .then(() => console.log(`Solicitud a ${ip} aceptada`))
         .catch(() => {});
    @@ -346,77 +353,82 @@ 

    Después de haber iniciado su exploración de red, Carla regresa afuera para ver el resultado. Para su deleite, todas las pantallas ahora muestran una franja de luz en sus esquinas superiores izquierdas. Están en la red local y sí aceptan comandos. Rápidamente toma nota de los números mostrados en cada pantalla. Hay 9 pantallas, dispuestas tres en alto y tres en ancho. Tienen las siguientes direcciones de red:

    -
    const screenAddresses = [
    +
    const direccionesPantalla = [
       "10.0.0.44", "10.0.0.45", "10.0.0.41",
       "10.0.0.31", "10.0.0.40", "10.0.0.42",
       "10.0.0.48", "10.0.0.47", "10.0.0.46"
     ];
    -

    Ahora esto abre posibilidades para todo tipo de travesuras. Podría mostrar “los cuervos mandan, los humanos babean” en la pared en letras gigantes. Pero eso se siente un poco grosero. En su lugar, planea mostrar un video de un cuervo volando que cubre todas las pantallas por la noche.

    +

    Ahora esto abre posibilidades para todo tipo de travesuras. Podría mostrar “los cuervos mandan, los humanos babean” en la pared en letras gigantes. Pero eso se parece un poco grosero. En su lugar, planea mostrar a la noche un vídeo de un cuervo volando que cubra todas las pantallas.

    -

    Carla encuentra un clip de video adecuado, en el cual un segundo y medio de metraje se puede repetir para crear un video en bucle mostrando el aleteo de un cuervo. Para ajustarse a las nueve pantallas (cada una de las cuales puede mostrar 50 por 30 píxeles), Carla corta y redimensiona los videos para obtener una serie de imágenes de 150 por 90, diez por segundo. Estas luego se cortan en nueve rectángulos cada una, y se procesan para que los puntos oscuros en el video (donde está el cuervo) muestren una luz brillante, y los puntos claros (sin cuervo) permanezcan oscuros, lo que debería crear el efecto de un cuervo ámbar volando contra un fondo negro.

    +

    Carla encuentra un vídeo adecuado en el cual un segundo y medio de metraje se puede repetir para crear un vídeo en bucle mostrando el aleteo de un cuervo. Para ajustarse a las nueve pantallas (cada una de las cuales puede mostrar 50 por 30 píxeles), Carla corta y redimensiona los vídeos para obtener una serie de imágenes de 150 por 90, diez por segundo. Estas luego se cortan en nueve rectángulos cada una, y se procesan para que los puntos oscuros en el vídeo (donde está el cuervo) muestren una luz brillante, y los puntos claros (sin cuervo) permanezcan oscuros, lo que debería crear el efecto de un cuervo ámbar volando contra un fondo negro.

    -

    Ella ha configurado la variable clipImages para contener un array de fotogramas, donde cada fotograma se representa con un array de nueve conjuntos de píxeles, uno para cada pantalla, en el formato que los letreros esperan.

    +

    Ha configurado la variable imágenesVídeo para contener un array de fotogramas, donde cada fotograma se representa con un array de nueve conjuntos de píxeles, uno para cada pantalla, en el formato que los letreros esperan.

    -

    Para mostrar un único fotograma del video, Carla necesita enviar una solicitud a todas las pantallas a la vez. Pero también necesita esperar el resultado de estas solicitudes, tanto para no comenzar a enviar el siguiente fotograma antes de que el actual se haya enviado correctamente, como para notar cuando las solicitudes están fallando.

    +

    Para mostrar un único fotograma del vídeo, Carla necesita enviar una solicitud a todas las pantallas a la vez. Pero también necesita esperar el resultado de estas solicitudes, tanto para no comenzar a enviar el siguiente fotograma antes de que el actual se haya enviado correctamente, como para notar cuando las solicitudes están fallando.

    -

    Promise tiene un método estático all que se puede usar para convertir un array de promesas en una sola promesa que se resuelve en un array de resultados. Esto proporciona una forma conveniente de que algunas acciones asíncronas sucedan al lado unas de otras, esperar a que todas terminen y luego hacer algo con sus resultados (o al menos esperar a que terminen para asegurarse de que no fallen).

    +

    Promise tiene un método estático all que se puede usar para convertir un array de promesas en una sola promesa que se resuelve en un array de resultados. Esto proporciona una forma conveniente de que algunas acciones asíncronas sucedan de manera concurrente, esperar a que todas terminen y luego hacer algo con sus resultados (o al menos esperar a que terminen para asegurarse de que no fallen).

    -
    function displayFrame(frame) {
    -  return Promise.all(frame.map((data, i) => {
    -    return request(screenAddresses[i], {
    +
    function mostrarFotograma(fotograma) {
    +  return Promise.all(fotograma.map((data, i) => {
    +    return request(direccionesPantalla[i], {
           command: "display",
           data
         });
       }));
     }
    -

    Esto recorre las imágenes en frame (que es un array de arrays de datos de visualización) para crear un array de promesas de solicitud. Luego devuelve una promesa que combina todas esas promesas.

    +

    Esto recorre las imágenes en fotograma (que es un array de arrays de datos de visualización) para crear un array de promesas de solicitud. Luego devuelve una promesa que combina todas esas promesas.

    -

    Para poder detener un video en reproducción, el proceso está envuelto en una clase. Esta clase tiene un método asíncrono play que devuelve una promesa que solo se resuelve cuando la reproducción se detiene de nuevo a través del método stop.

    +

    Para tener la capacidad de detener un vídeo en reproducción, el proceso está envuelto en una clase. Esta clase tiene un método asíncrono reproducir que devuelve una promesa que solo se resuelve cuando la reproducción se detiene a través del método parar.

    -
    function wait(time) {
    -  return new Promise(accept => setTimeout(accept, time));
    +
    function espera(tiempo) {
    +  return new Promise(aceptar => setTimeout(aceptar, tiempo));
     }
     
    -class VideoPlayer {
    -  constructor(frames, frameTime) {
    -    this.frames = frames;
    -    this.frameTime = frameTime;
    -    this.stopped = true;
    +class ReproductorVídeo {
    +  constructor(fotogramas, tiempoFotograma) {
    +    this.fotogramas = fotogramas;
    +    this.tiempoFotograma = tiempoFotograma;
    +    this.parado = true;
       }
     
    -  async play() {
    -    this.stopped = false;
    -    for (let i = 0; !this.stopped; i++) {
    -      let nextFrame = wait(this.frameTime);
    -      await displayFrame(this.frames[i % this.frames.length]);
    -      await nextFrame;
    +  async reproducir() {
    +    this.parado = false;
    +    for (let i = 0; !this.parado; i++) {
    +      let siguienteFotograma = espera(this.tiempoFotograma);
    +      await mostrarFotograma(this.fotogramas[i % this.fotogramas.length]);
    +      await siguienteFotograma;
         }
       }
     
    -  stop() {
    -    this.stopped = true;
    +  parar() {
    +    this.parado = true;
       }
     }
    -

    La función wait envuelve setTimeout en una promesa que se resuelve después del número de milisegundos especificado. Esto es útil para controlar la velocidad de reproducción.

    +

    La función espera envuelve setTimeout en una promesa que se resuelve después del número de milisegundos especificado. Esto es útil para controlar la velocidad de reproducción.

    -
    let video = new VideoPlayer(clipImages, 100);
    -video.play().catch(e => {
    +
    let vídeo = new ReproductorVídeo(imágenesVídeo, 100);
    +vídeo.reproducir().catch(e => {
       console.log("La reproducción falló: " + e);
     });
    -setTimeout(() => video.stop(), 15000);
    +setTimeout(() => vídeo.parar(), 15000);
    -

    Durante toda la semana que dura el muro de pantalla, todas las noches, cuando está oscuro, aparece misteriosamente un enorme pájaro naranja brillante en él.

    +

    Durante toda la semana que la pantalla permanece allí, todas las noches, cuando está oscuro, aparece misteriosamente un enorme pájaro naranja brillante en ella.

    El bucle de eventos

    -

    Un programa asincrónico comienza ejecutando su script principal, que a menudo configurará devoluciones de llamada para ser llamadas más tarde. Ese script principal, así como las devoluciones de llamada, se ejecutan por completo de una vez, sin interrupciones. Pero entre ellos, el programa puede estar inactivo, esperando a que ocurra algo.

    +

    Un programa asíncrono comienza ejecutando su script principal, que a menudo configurará callbacks para ser llamados más tarde. Ese script principal, así como las funciones de callback, se ejecutan por completo de una vez, sin interrupciones. Pero entre ellos, el programa puede estar inactivo, esperando a que ocurra algo.

    -

    Por lo tanto, las devoluciones de llamada no son llamadas directamente por el código que las programó. Si llamo a setTimeout desde dentro de una función, esa función ya habrá retornado en el momento en que se llame a la función de devolución de llamada. Y cuando la devolución de llamada regresa, el control no vuelve a la función que lo programó.

    +

    Por lo tanto, las funciones de callback no son llamadas directamente por el código que las programó. Si llamo a setTimeout desde dentro de una función, esa función ya habrá retornado en el momento en que se llame a la función de callback de setTimeout. Y cuando la función de callback retorna, el control no vuelve a la función que lo programó.

    -

    El comportamiento asincrónico ocurre en su propia función vacía pila de llamadas. Esta es una de las razones por las que, sin promesas, gestionar excepciones en código asincrónico es tan difícil. Dado que cada devolución de llamada comienza con una pila de llamadas en su mayoría vacía, sus manejadores de catch no estarán en la pila cuando lancen una excepción.

    +

    El comportamiento asíncrono ocurre en su propia pila de llamadas vacía.

    + +

    N. del T.: Esto último quiere decir que el comportamiento asíncrono en JavaScript no bloquea la ejecución: el código asíncrono se ejecuta una vez vaciada la pila de llamadas actual.

    +
    + +

    Esta es una de las razones por las que, sin promesas, gestionar excepciones en código asíncrono es tan difícil. Como cada callback comienza con una pila de llamadas en su mayoría vacía, sus manejadores de catch no estarán en la pila cuando lancen una excepción.

    try {
       setTimeout(() => {
    @@ -427,20 +439,20 @@ 

    "Atrapado", e); }

    -

    No importa cuán cerca ocurran eventos, como tiempos de espera o solicitudes entrantes, un entorno JavaScript ejecutará solo un programa a la vez. Puedes pensar en esto como ejecutar un gran bucle alrededor de tu programa, llamado el bucle de eventos. Cuando no hay nada que hacer, ese bucle se pausa. Pero a medida que llegan eventos, se agregan a una cola y su código se ejecuta uno tras otro. Debido a que no se ejecutan dos cosas al mismo tiempo, un código lento puede retrasar el manejo de otros eventos.

    +

    No importa cuán cerca ocurran los eventos (como por ejemplo tiempos de espera o solicitudes entrantes), un entorno JavaScript ejecutará solo un programa a la vez. Puedes imaginártelo como un gran bucle, llamado el bucle de eventos, que se ejecuta alrededor de tu programa. Cuando no hay nada que hacer, ese bucle se pausa. Pero a medida que llegan eventos, se agregan a una cola y su código se ejecuta uno tras otro. Como no se ejecutan dos cosas al mismo tiempo, un código lento puede retrasar el manejo de otros eventos.

    -

    Este ejemplo establece un tiempo de espera pero luego se demora hasta después del momento previsto para el tiempo de espera, provocando que el tiempo de espera sea tardío.

    +

    Este ejemplo establece un tiempo de espera pero luego se demora hasta después del momento previsto para el tiempo de espera, provocando que el tiempo de espera se alargue y termine más tarde de la cuenta.

    -
    let start = Date.now();
    +
    let comienzo = Date.now();
     setTimeout(() => {
    -  console.log("El tiempo de espera se ejecutó en", Date.now() - start);
    +  console.log("El tiempo de espera se ejecutó en", Date.now() - comienzo);
     }, 20);
    -while (Date.now() < start + 50) {}
    -console.log("Tiempo perdido hasta", Date.now() - start);
    +while (Date.now() < comienzo + 50) {}
    +console.log("Tiempo perdido hasta", Date.now() - comienzo);
     // → Tiempo perdido hasta 50
     // → El tiempo de espera se ejecutó en 55
    -

    Las promesas siempre se resuelven o se rechazan como un nuevo evento. Incluso si una promesa ya está resuelta, esperarla hará que su devolución de llamada se ejecute después de que termine el script actual, en lugar de inmediatamente.

    +

    Las promesas siempre se resuelven o se rechazan como un nuevo evento. Incluso si una promesa ya está resuelta, esperarla hará que su callback se ejecute después de que termine el script actual, en lugar de inmediatamente.

    Promise.resolve("Hecho").then(console.log);
     console.log("¡Yo primero!");
    @@ -449,59 +461,61 @@ 

    En capítulos posteriores veremos varios tipos de eventos que se ejecutan en el bucle de eventos.

    -

    Errores asincrónicos

    +

    Errores asíncronos

    -

    Cuando tu programa se ejecuta de forma síncrona, de una sola vez, no hay cambios de estado ocurriendo excepto aquellos que el programa mismo realiza. Para programas asíncronos esto es diferente, pueden tener brechas en su ejecución durante las cuales otro código puede correr.

    +

    Cuando tu programa se ejecuta de forma sincrónica, de una sola vez, no hay cambios de estado ocurriendo excepto aquellos que el programa mismo realiza. Para programas asíncronos esto es diferente: pueden tener brechas en su ejecución durante las cuales otro código puede correr.

    -

    Veamos un ejemplo. Esta es una función que intenta reportar el tamaño de cada archivo en un arreglo de archivos, asegurándose de leerlos todos al mismo tiempo en lugar de en secuencia.

    +

    Veamos un ejemplo. Esta es una función que intenta reportar el tamaño de cada archivo en un array de archivos, asegurándose de leerlos todos al mismo tiempo en lugar de secuencialmente.

    -
    async function fileSizes(files) {
    +
    async function tamañosArchivos(archivos) {
       let lista = "";
    -  await Promise.all(files.map(async fileName => {
    -    lista += fileName + ": " +
    -      (await textFile(fileName)).length + "\n";
    +  await Promise.all(archivos.map(async nombreArchivo => {
    +    lista += nombreArchivo + ": " +
    +      (await archivoTexto(nombreArchivo)).length + "\n";
       }));
       return lista;
     }
    -

    La parte async fileName => muestra cómo también se pueden hacer arrow functions async colocando la palabra async delante de ellas.

    +

    La parte async nombreArchivo => muestra cómo también se pueden hacer arrow functions async (funciones flecha asíncronas) colocando la palabra async delante de ellas.

    -

    El código no parece ser sospechoso de inmediato... mapea la función flecha async sobre el arreglo de nombres, creando un arreglo de promesas, y luego usa Promise.all para esperar a todas ellas antes de devolver la lista que construyen.

    +

    El código no parece sospechoso de inmediato... mapea la función flecha async sobre el array de nombres, creando un array de promesas, y luego usa Promise.all para esperar a todas ellas antes de devolver la lista que construyen.

    -

    Pero está totalmente roto. Siempre devolverá solo una línea de salida, enumerando el archivo que tardó más en leer.

    +

    Sin embargo, el programa está totalmente roto. Siempre devolverá solo una línea de salida, enumerando el archivo que tardó más en leer.

    -
    fileSizes(["plans.txt", "shopping_list.txt"])
    +
    tamañosArchivos(["planes.txt", "lista_compra.txt"])
       .then(console.log);

    ¿Puedes descubrir por qué?

    El problema radica en el operador +=, que toma el valor actual de lista en el momento en que comienza a ejecutarse la instrucción y luego, cuando el await termina, establece el enlace lista como ese valor más la cadena agregada.

    -

    Pero entre el momento en que comienza a ejecutarse la instrucción y el momento en que termina, hay una brecha asincrónica. La expresión map se ejecuta antes de que se agregue cualquier cosa a la lista, por lo que cada uno de los operadores += comienza desde una cadena vacía y termina, cuando termina su recuperación de almacenamiento, estableciendo lista en el resultado de agregar su línea a la cadena vacía.

    +

    Pero entre el momento en que comienza a ejecutarse la instrucción y el momento en que termina, hay una brecha asíncrona. La expresión map se ejecuta antes de que se agregue cualquier cosa a la lista, por lo que cada uno de los operadores += comienza desde una cadena vacía y acaba, cuando recupera la información del almacenamiento, estableciendo lista en el resultado de agregar su línea a la cadena vacía.

    -

    Esto podría haberse evitado fácilmente devolviendo las líneas de las promesas mapeadas y llamando a join en el resultado de Promise.all, en lugar de construir la lista cambiando un enlace. Como suele ser, calcular nuevos valores es menos propenso a errores que cambiar valores existentes.

    +

    Esto podría haberse evitado fácilmente devolviendo las líneas de las promesas mapeadas y llamando a join en el resultado de Promise.all, en lugar de construir la lista cambiando una variable. Como de costumbre, calcular nuevos valores es menos propenso a errores que cambiar valores existentes.

    -
    async function fileSizes(files) {
    -  let líneas = files.map(async fileName => {
    -    return fileName + ": " +
    -      (await textFile(fileName)).length;
    +
    async function tamañosArchivos(archivos) {
    +  let líneas = archivos.map(async nombreArchivo => {
    +    return nombreArchivo + ": " +
    +      (await archivoTexto(nombreArchivo)).length;
       });
       return (await Promise.all(líneas)).join("\n");
     }
    -

    Errores como este son fáciles de cometer, especialmente al usar await, y debes ser consciente de dónde ocurren las brechas en tu código. Una ventaja de la asincronía explícita de JavaScript (ya sea a través de devoluciones de llamada, promesas o await) es que identificar estas brechas es relativamente fácil.

    +

    Errores como este son fáciles de cometer, especialmente al usar await, y debes ser consciente de dónde ocurren las brechas en tu código. Una ventaja de la asincronía explícita de JavaScript (ya sea a través de callbacks, promesas o await) es que identificar estas brechas es relativamente fácil.

    Resumen

    -

    La programación asincrónica hace posible expresar la espera de acciones de larga duración sin congelar todo el programa. Los entornos de JavaScript típicamente implementan este estilo de programación utilizando devoluciones de llamada, funciones que se llaman cuando las acciones se completan. Un bucle de eventos programa estas devoluciones de llamada para que se llamen cuando sea apropiado, una tras otra, de modo que su ejecución no se superponga.La programación de forma asíncrona se facilita gracias a las promesas, que son objetos que representan acciones que podrían completarse en el futuro, y las funciones async, que te permiten escribir un programa asíncrono como si fuera sincrónico.

    +

    La programación asíncrona hace posible expresar la espera de acciones de larga duración sin congelar todo el programa. Los entornos de JavaScript típicamente implementan este estilo de programación utilizando callbacks, funciones que se llaman cuando las acciones se completan. Un bucle de eventos programa estas funciones de callback para que se llamen cuando sea apropiado, una tras otra, de modo que su ejecución no se superponga.

    + +

    La programación asíncrona se facilita gracias a las promesas, que son objetos que representan acciones que podrían completarse en el futuro, y las funciones async, que te permiten escribir un programa asíncrono como si fuera sincrónico.

    Ejercicios

    Momentos de tranquilidad

    -

    Hay una cámara de seguridad cerca del laboratorio de Carla que se activa con un sensor de movimiento. Está conectada a la red y comienza a enviar un flujo de video cuando está activa. Como prefiere no ser descubierta, Carla ha configurado un sistema que detecta este tipo de tráfico de red inalámbrico y enciende una luz en su guarida cada vez que hay actividad afuera, para que ella sepa cuándo mantenerse en silencio.

    +

    Hay una cámara de seguridad cerca del laboratorio de Carla que se activa con un sensor de movimiento. Está conectada a la red y comienza a enviar un flujo de video cuando está activa. Como prefiere no ser descubierta, Carla ha configurado un sistema que detecta este tipo de tráfico de red inalámbrico y enciende una luz en su guarida cada vez que hay actividad afuera, de modo que sepa cuándo mantenerse en silencio.

    -

    También ha estado registrando los momentos en que la cámara se activa desde hace un tiempo, y quiere utilizar esta información para visualizar qué momentos, en una semana promedio, tienden a ser tranquilos y cuáles tienden a ser ocupados. El registro se almacena en archivos que contienen un número de marca de tiempo por línea (como devuelto por Date.now()).

    +

    También ha estado registrando los momentos en que la cámara se activa desde hace un tiempo, y quiere utilizar esta información para visualizar qué momentos, en una semana promedio, tienden a ser tranquilos y cuáles tienden a no serlo. El registro se almacena en archivos que contienen un número de marca de tiempo por línea (como devuelto por Date.now()).

    1695709940692
     1695701068331
    @@ -513,7 +527,7 @@ 

    Utiliza la función textFile definida anteriormente, que al recibir un nombre de archivo devuelve una promesa que se resuelve en el contenido del archivo. Recuerda que new Date(marcaDeTiempo) crea un objeto Date para ese momento, que tiene métodos getDay y getHours que devuelven el día de la semana y la hora del día.

    -

    Ambos tipos de archivos, la lista de archivos de registro y los propios archivos de registro, tienen cada dato en su propia línea, separados por caracteres de nueva línea ("\n").

    +

    Ambos tipos de archivos —la lista de archivos de registro y los propios archivos de registro— tienen cada dato en una línea, separados por caracteres de nueva línea ("\n").

    async function activityTable(day) {
       let logFileList = await textFile("camera_logs.txt");
    @@ -525,13 +539,13 @@ 

    Mostrar pistas...
    -

    Necesitarás convertir el contenido de estos archivos en un array. La forma más fácil de hacerlo es utilizando el método split en la cadena producida por textFile. Ten en cuenta que para los archivos de registro, eso seguirá dándote un array de cadenas, que debes convertir a números antes de pasarlos a new Date.

    +

    Necesitarás convertir el contenido de estos archivos en un array. La forma más fácil de hacerlo es utilizando el método split en la cadena producida por textFile. Ten en cuenta que para los archivos de registro, eso te dará un array de cadenas, que debes convertir a números antes de pasarlos a new Date.

    -

    Resumir todos los puntos temporales en una tabla de horas se puede hacer creando una tabla (array) que contenga un número para cada hora del día. Luego puedes recorrer todos los marca de tiempos (sobre los archivos de registro y los números en cada archivo de registro) y, para cada uno, si sucedió en el día correcto, toma la hora en que ocurrió y suma uno al número correspondiente en la tabla.

    +

    Resumir todos los puntos temporales en una tabla de horas se puede hacer creando una tabla (array) que contenga un número para cada hora del día. Luego puedes recorrer todas las marcas de tiempo (de los archivos de registro y los números en cada archivo de registro) y, para cada uno, si sucedió en el día correcto, tomar la hora en que ocurrió y sumar uno al número correspondiente en la tabla.

    -

    Asegúrate de usar await en el resultado de las funciones asíncronas antes de hacer cualquier cosa con él, o terminarás con una Promise donde esperabas un string.

    +

    Asegúrate de usar await en el resultado de las funciones asíncronas antes de hacer cualquier cosa con él, o terminarás con una Promise donde esperabas tener un string.

    -

    hinting}}

    +

    Promesas Reales

    @@ -544,40 +558,40 @@

    6) .then(tabla => console.log(gráficoActividad(tabla)));

    -

    En este estilo, usar Promise.all será más conveniente que intentar modelar un bucle sobre los archivos de registro. En la función async, simplemente usar await en un bucle es más simple. Si leer un archivo toma un tiempo, ¿cuál de estos dos enfoques tomará menos tiempo para ejecutarse?

    +

    En este estilo, usar Promise.all será más conveniente que intentar modelar un bucle sobre los archivos de registro. En la función async, simplemente usar await en un bucle es más simple. Si leer un archivo lleva un tiempo, ¿cuál de estos dos enfoques necesitará menos tiempo para ejecutarse?

    -

    Si uno de los archivos listados en la lista de archivos tiene un error tipográfico, y falla al leerlo, ¿cómo termina ese fallo en el objeto Promise que retorna tu función?

    +

    Si uno de los archivos listados en la lista de archivos tiene un error tipográfico, y su lectura falla, ¿cómo termina ese fallo en el objeto Promise que retorna tu función?

    Mostrar pistas...
    -

    El enfoque más directo para escribir esta función es usar una cadena de llamadas then. La primera promesa se produce al leer la lista de archivos de registro. El primer callback puede dividir esta lista y mapear textFile sobre ella para obtener una matriz de promesas para pasar a Promise.all. Puede devolver el objeto devuelto por Promise.all, para que lo que sea que eso devuelva se convierta en el resultado del valor de retorno de este primer then.

    +

    El enfoque más directo para escribir esta función es usar una cadena de llamadas then. La primera promesa se produce al leer la lista de archivos de registro. El primer callback puede dividir esta lista y mapear textFile sobre ella para obtener un array de promesas para pasar a Promise.all. Puede devolver el objeto devuelto por Promise.all, para que lo que sea que eso devuelva se convierta en el resultado del valor de retorno de este primer then.

    -

    Ahora tenemos una promesa que devuelve un array de archivos de registro. Podemos llamar a then nuevamente en eso, y poner la lógica de conteo de marcas de tiempo allí. Algo así:

    +

    Ahora tenemos una promesa que devuelve un array de archivos de registro. Podemos llamar a then nuevamente en eso, y poner la lógica de recuento de marcas de tiempo allí. Algo así:

    -
    function activityTable(día) {
    -  return textoArchivo("registros_camara.txt").then(archivos => {
    -    return Promise.all(archivos.split("\n").map(textoArchivo));
    +
    function activityTable(día) {
    +  return archivoTexto("registros_camara.txt").then(archivos => {
    +    return Promise.all(archivos.split("\n").map(archivoTexto));
       }).then(logs => {
         // analizar...
       });
     }
    -

    O podrías, para una programación aún mejor, poner el análisis de cada archivo dentro de Promise.all, para que ese trabajo pueda comenzar para el primer archivo que regresa del disco, incluso antes de que los otros archivos regresen.

    +

    O podrías, para una programación del trabajo aún mejor, poner el análisis de cada archivo dentro de Promise.all, para que ese trabajo pueda comenzar con el primer archivo que se reciba del disco, incluso antes de que lleguen los otros archivos.

    -
    function activityTable(día) {
    +
    function activityTable(día) {
       let tabla = []; // inicializar...
    -  return textoArchivo("registros_camara.txt").then(archivos => {
    +  return archivoTexto("registros_camara.txt").then(archivos => {
         return Promise.all(archivos.split("\n").map(nombre => {
    -      return textoArchivo(nombre).then(log => {
    +      return archivoTexto(nombre).then(log => {
             // analizar...
           });
         }));
       }).then(() => tabla);
     }
    -

    Lo que muestra que la forma en que estructuras tus promesas puede tener un efecto real en la forma en que se programa el trabajo. Un simple bucle con await hará que el proceso sea completamente lineal: espera a que se cargue cada archivo antes de continuar. Promise.all hace posible que varias tareas sean trabajadas conceptualmente al mismo tiempo, permitiéndoles progresar mientras los archivos aún se están cargando. Esto puede ser más rápido, pero también hace que el orden en que sucederán las cosas sea menos predecible. En este caso, donde solo vamos a estar incrementando números en una tabla, eso no es difícil de hacer de manera segura. Para otros tipos de problemas, puede ser mucho más difícil.

    +

    Esto demuestra que la forma en que estructuras tus promesas puede tener un efecto real en la forma en que se programa el trabajo. Un simple bucle con await hará que el proceso sea completamente lineal: espera a que se cargue cada archivo antes de continuar. Promise.all hace posible que varias tareas sean trabajadas conceptualmente al mismo tiempo, permitiéndoles progresar mientras los archivos aún se están cargando. Esto puede ser más rápido, pero también hace que el orden en que sucederán las cosas sea menos predecible. En este caso, donde solo vamos a estar incrementando números en una tabla, eso no es difícil de hacer de manera segura. Para otros tipos de problemas, puede ser mucho más difícil.

    -

    Cuando un archivo en la lista no existe, la promesa devuelta por textFile será rechazada. Debido a que Promise.all se rechaza si alguna de las promesas que se le pasan falla, el valor de retorno de la devolución de llamada dada al primer then también será una promesa rechazada. Esto hace que la promesa devuelta por then falle, por lo que la devolución de llamada dada al segundo then ni siquiera se llama, y se devuelve una promesa rechazada desde la función.

    +

    Cuando un archivo en la lista no existe, la promesa devuelta por archivoTexto será rechazada. Debido a que Promise.all se rechaza si alguna de las promesas que se le pasan falla, el valor de retorno de la callback dada al primer then también será una promesa rechazada. Esto hace que la promesa devuelta por then falle, por lo que la callback dada al segundo then ni siquiera se llama, y se devuelve una promesa rechazada desde la función.

    @@ -585,7 +599,7 @@

    Como vimos, dado un array de promesas, Promise.all devuelve una promesa que espera a que todas las promesas en el array finalicen. Luego tiene éxito, devolviendo un array de valores de resultado. Si una promesa en el array falla, la promesa devuelta por all también falla, con la razón de fallo de la promesa que falló.

    -

    Implementa algo similar tú mismo como una función regular llamada Promise_all.

    +

    Implementa algo similar tú mismo como una función normal llamada Promise_all.

    Recuerda que después de que una promesa tiene éxito o falla, no puede volver a tener éxito o fallar, y las llamadas posteriores a las funciones que la resuelven se ignoran. Esto puede simplificar la forma en que manejas el fallo de tu promesa.

    @@ -619,15 +633,13 @@

    Mostrar pistas...
    -

    La función pasada al constructor Promise tendrá que llamar a then en cada una de las promesas en el array dado. Cuando una de ellas tiene éxito, dos cosas deben suceder. El valor resultante debe ser almacenado en la posición correcta de un array de resultados, y debemos verificar si esta era la última promesa pendiente y finalizar nuestra propia promesa si lo era.

    +

    La función pasada al constructor Promise tendrá que llamar a then en cada una de las promesas en el array dado. Cuando una de ellas tiene éxito, dos cosas deben suceder: el valor resultante debe ser almacenado en la posición correcta de un array de resultados, y debemos verificar si esta era la última promesa pendiente y finalizar nuestra propia promesa si lo era.

    Esto último se puede hacer con un contador que se inicializa con la longitud del array de entrada y del cual restamos 1 cada vez que una promesa tiene éxito. Cuando llegue a 0, hemos terminado. Asegúrate de tener en cuenta la situación en la que el array de entrada está vacío (y por lo tanto ninguna promesa se resolverá nunca).

    Manejar el fallo requiere un poco de pensamiento pero resulta ser extremadamente simple. Simplemente pasa la función reject de la promesa contenedora a cada una de las promesas en el array como un controlador catch o como un segundo argumento para then para que un fallo en una de ellas desencadene el rechazo de toda la promesa contenedora.

    -

    pista

    - -
    +

    }}

    diff --git a/html/20_node.html b/html/20_node.html index 7986d4b8..a1ffdbca 100644 --- a/html/20_node.html +++ b/html/20_node.html @@ -32,9 +32,9 @@

    Cuando se construyen sistemas que se comunican a través de la red, la forma en que gestionas la entrada y el output—es decir, la lectura y escritura de datos desde y hacia la red y el disco duro—puede marcar una gran diferencia en cuán rápido responde un sistema al usuario o a las solicitudes de red.

    -

    En tales programas, la programación asincrónica a menudo es útil. Permite que el programa envíe y reciba datos desde y hacia múltiples dispositivos al mismo tiempo sin una complicada gestión de hilos y sincronización.

    +

    En tales programas, la programación asíncrona a menudo es útil. Permite que el programa envíe y reciba datos desde y hacia múltiples dispositivos al mismo tiempo sin una complicada gestión de hilos y sincronización.

    -

    Node fue concebido inicialmente con el propósito de hacer que la programación asincrónica sea fácil y conveniente. JavaScript se presta bien a un sistema como Node. Es uno de los pocos lenguajes de programación que no tiene una forma incorporada de manejar la entrada y salida. Por lo tanto, JavaScript podría adaptarse al enfoque algo excéntrico de Node para la programación de red y sistemas de archivos sin terminar con dos interfaces inconsistentes. En 2009, cuando se diseñaba Node, la gente ya estaba realizando programación basada en callbacks en el navegador, por lo que la comunidad alrededor del lenguaje estaba acostumbrada a un estilo de programación asincrónica.

    +

    Node fue concebido inicialmente con el propósito de hacer que la programación asíncrona sea fácil y conveniente. JavaScript se presta bien a un sistema como Node. Es uno de los pocos lenguajes de programación que no tiene una forma incorporada de manejar la entrada y salida. Por lo tanto, JavaScript podría adaptarse al enfoque algo excéntrico de Node para la programación de red y sistemas de archivos sin terminar con dos interfaces inconsistentes. En 2009, cuando se diseñaba Node, la gente ya estaba realizando programación basada en callbacks en el navegador, por lo que la comunidad alrededor del lenguaje estaba acostumbrada a un estilo de programación asíncrona.

    El comando node

    From 65f13ce78231c9ca31ad43ef122b7e57f43b7178 Mon Sep 17 00:00:00 2001 From: ckdvk Date: Thu, 20 Feb 2025 19:23:53 +0800 Subject: [PATCH 23/36] =?UTF-8?q?revisado=20cap=C3=ADtulo=2012.=20Alguna?= =?UTF-8?q?=20correcci=C3=B3n=20en=205=20y=2011?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 05_higher_order.md | 2 +- 11_async.md | 14 +++--- 12_language.md | 89 ++++++++++++++++++------------------ html/05_higher_order.html | 2 +- html/11_async.html | 18 ++++---- html/12_language.html | 94 ++++++++++++++++++++------------------- 6 files changed, 112 insertions(+), 107 deletions(-) diff --git a/05_higher_order.md b/05_higher_order.md index bf2f2f4a..ac0c6a84 100644 --- a/05_higher_order.md +++ b/05_higher_order.md @@ -281,7 +281,7 @@ Otra cosa común que hacer con arrays es calcular un único valor a partir de el La operación de orden superior que representa esta idea se llama _reduce_ (a veces también llamada _fold_). Construye un valor tomando repetidamente un único elemento del array y combinándolo con el valor actual. Al sumar números empezarías con el número cero y añadirías cada elemento a la suma. -Los parámetros de `reduce` son, además del array, una función de combinación y un valor inicial. Esta función es un poco menos directa que `filter` y `map`, así que obsérva detenidamente: +Los parámetros de `reduce` son, además del array, una función de combinación y un valor inicial. Esta función es un poco menos directa que `filter` y `map`, así que observa detenidamente: ``` function reducir(array, combinación, principio) { diff --git a/11_async.md b/11_async.md index c89482c0..7799689d 100644 --- a/11_async.md +++ b/11_async.md @@ -671,11 +671,11 @@ La programación asíncrona se facilita gracias a las promesas, que son objetos {{index "momentos de tranquilidad (ejercicio)", "cámara de seguridad", "Carla la urraca", "función async"}} -Hay una cámara de seguridad cerca del laboratorio de Carla que se activa con un sensor de movimiento. Está conectada a la red y comienza a enviar un flujo de video cuando está activa. Como prefiere no ser descubierta, Carla ha configurado un sistema que detecta este tipo de tráfico de red inalámbrico y enciende una luz en su guarida cada vez que hay actividad afuera, de modo que sepa cuándo mantenerse en silencio. +Cerca del laboratorio de Carla hay una cámara de seguridad que se activa con un sensor de movimiento. Está conectada a la red y comienza a enviar un flujo de vídeo cuando está activa. Como prefiere no ser descubierta, Carla ha configurado un sistema que detecta este tipo de tráfico de red inalámbrico y enciende una luz en su guarida cada vez que hay actividad afuera, de modo que sepa cuándo estar tranquila. {{index "clase Date", "función Date.now", marca de tiempo}} -También ha estado registrando los momentos en que la cámara se activa desde hace un tiempo, y quiere utilizar esta información para visualizar qué momentos, en una semana promedio, tienden a ser tranquilos y cuáles tienden a no serlo. El registro se almacena en archivos que contienen un número de marca de tiempo por línea (como devuelto por `Date.now()`). +También ha estado registrando los momentos en que la cámara se activa desde hace un tiempo, y quiere utilizar esta información para visualizar qué momentos, en una semana promedio, tienden a ser tranquilos y cuáles tienden a no serlo. El registro se almacena en archivos que contienen un número de marca de tiempo por línea (como los que proporciona `Date.now()`). ```{lang: null} 1695709940692 @@ -689,14 +689,14 @@ La función `activityGraph`, proporcionada por el sandbox, resume dicha tabla en {{index "función textFile"}} -Utiliza la función `textFile` definida anteriormente, que al recibir un nombre de archivo devuelve una promesa que se resuelve en el contenido del archivo. Recuerda que `new Date(marcaDeTiempo)` crea un objeto `Date` para ese momento, que tiene métodos `getDay` y `getHours` que devuelven el día de la semana y la hora del día. +Utiliza la función `textFile` ( o `archivoTexto`) definida anteriormente, que al recibir un nombre de archivo devuelve una promesa que se resuelve en el contenido del archivo. Recuerda que `new Date(marcaDeTiempo)` crea un objeto `Date` para ese momento, que tiene métodos `getDay` y `getHours` que devuelven el día de la semana y la hora del día. Ambos tipos de archivos —la lista de archivos de registro y los propios archivos de registro— tienen cada dato en una línea, separados por caracteres de nueva línea (`"\n"`). {{if interactive ```{test: no} -async function activityTable(day) { +async function activityTable(día) { let logFileList = await textFile("camera_logs.txt"); // Tu código aquí } @@ -711,7 +711,7 @@ if}} {{index "momentos de tranquilidad (ejercicio)", "método split", "función textFile", "clase Date"}} -Necesitarás convertir el contenido de estos archivos en un array. La forma más fácil de hacerlo es utilizando el método `split` en la cadena producida por `textFile`. Ten en cuenta que para los archivos de registro, eso te dará un array de cadenas, que debes convertir a números antes de pasarlos a `new Date`. +Necesitarás convertir el contenido de estos archivos en un array. La forma más fácil de hacerlo es utilizando el método `split` en la cadena producida por `textFile` ( o `archivoTexto`). Ten en cuenta que para los archivos de registro, eso te dará un array de cadenas, que debes convertir a números antes de pasarlos a `new Date`. Resumir todos los puntos temporales en una tabla de horas se puede hacer creando una tabla (array) que contenga un número para cada hora del día. Luego puedes recorrer todas las marcas de tiempo (de los archivos de registro y los números en cada archivo de registro) y, para cada uno, si sucedió en el día correcto, tomar la hora en que ocurrió y sumar uno al número correspondiente en la tabla. @@ -736,7 +736,7 @@ function activityTable(día) { } activityTable(6) - .then(tabla => console.log(gráficoActividad(tabla))); + .then(tabla => console.log(activityGraph(tabla))); ``` if}} @@ -761,7 +761,7 @@ Ahora tenemos una promesa que devuelve un array de archivos de registro. Podemos ```{test: no} function activityTable(día) { - return archivoTexto("registros_camara.txt").then(archivos => { + return archivoTexto("camera_logs.txt").then(archivos => { return Promise.all(archivos.split("\n").map(archivoTexto)); }).then(logs => { // analizar... diff --git a/12_language.md b/12_language.md index 7b729f67..f9e2e6bd 100644 --- a/12_language.md +++ b/12_language.md @@ -2,7 +2,7 @@ # Proyecto: Un Lenguaje de Programación -{{quote {author: "Hal Abelson y Gerald Sussman", title: "Estructura e Interpretación de Programas de Computadora", chapter: true} +{{quote {author: "Hal Abelson y Gerald Sussman", title: "Estructura e Interpretación de Programas Informáticos", chapter: true} El evaluador, que determina el significado de expresiones en un lenguaje de programación, es solo otro programa. @@ -14,11 +14,11 @@ quote}} Crear tu propio ((lenguaje de programación)) es sorprendentemente fácil (si no apuntas muy alto) y muy esclarecedor. -Lo principal que quiero mostrar en este capítulo es que no hay ((magia)) involucrada en la construcción de un lenguaje de programación. A menudo he sentido que algunas invenciones humanas eran tan inmensamente inteligentes y complicadas que nunca las entendería. Pero con un poco de lectura y experimentación, a menudo resultan ser bastante mundanas. +Lo principal que quiero mostrar en este capítulo es que la construcción de un lenguaje de programación no es resultado de ningún tipo de ((magia)). A menudo he sentido que algunas invenciones humanas eran tan inmensamente inteligentes y complicadas que nunca las entendería. Pero con un poco de lectura y experimentación, a menudo resultan ser bastante mundanas. {{index "Lenguaje Egg", ["abstracción", "en Egg"]}} -Construiremos un lenguaje de programación llamado Egg. Será un lenguaje simple y diminuto, pero lo suficientemente poderoso como para expresar cualquier cálculo que puedas imaginar. Permitirá una simple ((abstracción)) basada en ((funciones)). +Construiremos un lenguaje de programación llamado Egg. Será un lenguaje simple y diminuto, pero lo suficientemente poderoso como para expresar cualquier cálculo que puedas imaginar. Permitirá una ((abstracción)) simple basada en ((funciones)). {{id parsing}} @@ -26,7 +26,7 @@ Construiremos un lenguaje de programación llamado Egg. Será un lenguaje simple {{index parsing, "validación", [sintaxis, "de Egg"]}} -La parte más inmediatamente visible de un lenguaje de programación es su _sintaxis_, o notación. Un _analizador sintáctico_ es un programa que lee un fragmento de texto y produce una estructura de datos que refleja la estructura del programa contenido en ese texto. Si el texto no forma un programa válido, el analizador sintáctico debería señalar el error. +La parte más inmediatamente visible de un lenguaje de programación es su _sintaxis_, o notación. Un _analizador sintáctico_ (o _parser_) es un programa que lee un fragmento de texto y produce una estructura de datos que refleja la estructura del programa contenido en ese texto. Si el texto no forma un programa válido, el analizador sintáctico debería señalar el error. {{index "forma especial", ["función", "aplicación"]}} @@ -34,7 +34,7 @@ Nuestro lenguaje tendrá una sintaxis simple y uniforme. Todo en Egg es una ((ex {{index "carácter de comillas dobles", parsing, [escape, "en cadenas"], [espacio en blanco, sintaxis]}} -Para mantener el analizador sintáctico simple, las cadenas en Egg no admiten nada parecido a los escapes con barra invertida. Una cadena es simplemente una secuencia de caracteres que no son comillas dobles, envueltos entre comillas dobles. Un número es una secuencia de dígitos. Los nombres de las asignaciones pueden consistir en cualquier carácter que no sea espacio en blanco y que no tenga un significado especial en la sintaxis. +Para que el analizador sintáctico sea más simple, las cadenas en Egg no admiten nada como los escapes con barra invertida. Una cadena es simplemente una secuencia de caracteres que no son comillas dobles, envueltos entre comillas dobles. Un número es una secuencia de dígitos. Los nombres de las asignaciones pueden consistir en cualquier carácter que no sea espacio en blanco y que no tenga un significado especial en la sintaxis. {{index "carácter de coma", ["paréntesis", argumentos]}} @@ -49,7 +49,7 @@ do(define(x, 10), {{index bloque, [sintaxis, "de Egg"]}} -La ((uniformidad)) del ((lenguaje Egg)) significa que las cosas que son ((operador))es en JavaScript (como `>`) son asignaciones normales en este lenguaje, aplicadas de la misma manera que otras ((funciones)). Y dado que la sintaxis no tiene concepto de bloque, necesitamos un constructo `do` para representar la realización de múltiples tareas en secuencia. +Como queremos tener la ya mencionada ((uniformidad)) en el ((lenguaje Egg)), resulta que, cosas que son ((operador))es en JavaScript (como `>`), serán asignaciones normales en este lenguaje, aplicadas de la misma manera que otras ((funciones)). Y dado que la sintaxis no tiene concepto de bloque, necesitamos un constructo `do` para representar la realización de múltiples tareas en secuencia. {{index "propiedad tipo", "análisis sintáctico", ["estructura de datos", "árbol"]}} @@ -74,13 +74,13 @@ La parte `>(x, 5)` del programa anterior se representaría de la siguiente maner {{indexsee "árbol de sintaxis abstracta", "árbol sintáctico", ["estructura de datos", "árbol"]}} -Esta estructura de datos se llama un _((árbol de sintaxis))_. Si te imaginas los objetos como puntos y los enlaces entre ellos como líneas entre esos puntos, tiene una forma similar a un ((árbol)). El hecho de que las expresiones contienen otras expresiones, que a su vez pueden contener más expresiones, es similar a la forma en que las ramas de un árbol se dividen y vuelven a dividir. +Esta estructura de datos se llama un _((árbol sintáctico))_. Si te imaginas los objetos como puntos y los enlaces entre ellos como líneas entre esos puntos, tiene forma de ((árbol)). El hecho de que las expresiones contienen otras expresiones, que a su vez pueden contener más expresiones, es similar a la forma en que las ramas de un árbol se dividen y vuelven a dividir. {{figure {url: "img/syntax_tree.svg", alt: "Un diagrama que muestra la estructura del árbol de sintaxis del programa de ejemplo. La raíz está etiquetada como 'do' y tiene dos hijos, uno etiquetado como 'define' y otro como 'if'. A su vez, estos tienen más hijos que describen su contenido.", width: "5cm"}}} {{index "análisis" "sintáctico"}} -Contrasta esto con el analizador que escribimos para el formato de archivo de configuración en el [Capítulo ?](regexp#ini), que tenía una estructura simple: dividía la entrada en líneas y manejaba esas líneas una a la vez. Solo había algunas formas simples que una línea podía tener. +Contrasta esto con el analizador que escribimos para el formato de archivo de configuración en el [Capítulo ?](regexp#ini), que tenía una estructura simple: dividía la entrada en líneas y manejaba esas líneas una por una. Solo había un puñado de formas simples que una línea podía tener. {{index "recursión", [anidamiento, "de expresiones"]}} @@ -92,7 +92,9 @@ Afortunadamente, este problema puede resolverse muy bien escribiendo una funció {{index "función parseExpression", "árbol de sintaxis"}} -Definimos una función `parseExpression`, que recibe una cadena como entrada y devuelve un objeto que contiene la estructura de datos de la expresión al inicio de la cadena, junto con la parte de la cadena que queda después de analizar esta expresión. Al analizar subexpresiones (el argumento de una aplicación, por ejemplo), esta función puede ser llamada nuevamente, obteniendo la expresión de argumento así como el texto que queda. Este texto a su vez puede contener más argumentos o puede ser el paréntesis de cierre que finaliza la lista de argumentos.Esta es la primera parte del analizador sintáctico: +Definimos una función `parseExpression`, que recibe una cadena como entrada y devuelve un objeto que contiene la estructura de datos de la expresión al inicio de la cadena, junto con la parte de la cadena que queda después de analizar esta expresión. Al analizar subexpresiones (el argumento de una aplicación, por ejemplo), esta función puede ser llamada nuevamente, obteniendo la expresión de argumento así como el texto que queda. Este texto a su vez puede contener más argumentos o puede ser el paréntesis de cierre que finaliza la lista de argumentos. + +Esta es la primera parte del analizador sintáctico: ```{includeCode: true} function parseExpression(program) { @@ -120,7 +122,7 @@ function skipSpace(string) { {{index "skipSpace function", [whitespace, syntax]}} -Debido a que Egg, al igual que JavaScript, permite cualquier cantidad de espacios en blanco entre sus elementos, debemos cortar repetidamente el espacio en blanco del inicio de la cadena del programa. Eso es para lo que sirve la función `skipSpace`. +Como Egg, al igual que JavaScript, permite cualquier cantidad de espacios en blanco entre sus elementos, debemos cortar repetidamente el espacio en blanco del inicio de la cadena del programa. Para eso es para lo que sirve la función `skipSpace`. {{index "literal expression", "SyntaxError type"}} @@ -128,7 +130,7 @@ Después de omitir cualquier espacio inicial, `parseExpression` utiliza tres ((e {{index "parseApply function"}} -Luego cortamos la parte que coincidió de la cadena del programa y la pasamos, junto con el objeto de la expresión, a `parseApply`, que verifica si la expresión es una aplicación. Si lo es, analiza una lista de argumentos entre paréntesis. +Luego cortamos la parte que coincidió de la cadena del programa y pasamos el resto, junto con el objeto de la expresión, a `parseApply`, que verifica si la expresión es una aplicación. Si lo es, analiza una lista de argumentos entre paréntesis. ```{includeCode: true} function parseApply(expr, program) { @@ -155,17 +157,17 @@ function parseApply(expr, program) { {{index parsing}} -Si el próximo carácter en el programa no es un paréntesis de apertura, esto no es una aplicación y `parseApply` devuelve la expresión que se le dio. +Si el próximo carácter en el programa no es un paréntesis de apertura, entonces no se trata de una aplicación y `parseApply` devuelve la expresión que se le dio. {{index recursion}} -De lo contrario, se salta el paréntesis de apertura y crea el objeto ((árbol sintáctico)) para esta expresión de aplicación. Luego llama recursivamente a `parseExpression` para analizar cada argumento hasta encontrar un paréntesis de cierre. La recursión es indirecta, a través de `parseApply` y `parseExpression` llamándose mutuamente. +De lo contrario, se salta el paréntesis de apertura y crea el objeto ((árbol sintáctico)) para esta expresión de aplicación. Luego llama recursivamente a `parseExpression` para analizar cada argumento hasta encontrar un paréntesis de cierre. La recursión es indirecta, realizada a través de `parseApply` y `parseExpression` llamándose mutuamente. Dado que una expresión de aplicación puede a su vez ser aplicada (como en `multiplicador(2)(1)`), `parseApply` debe, después de analizar una aplicación, llamarse a sí misma nuevamente para verificar si sigue otro par de paréntesis. {{index "árbol de sintaxis", "lenguaje Egg", "función de análisis"}} -Esto es todo lo que necesitamos para analizar Egg. Lo envolvemos en una conveniente `parse` función que verifica que ha llegado al final de la cadena de entrada después de analizar la expresión (un programa Egg es una sola expresión), y que nos da la estructura de datos del programa. +Esto es todo lo que necesitamos para analizar el lenguaje Egg. Lo envolvemos en una conveniente función `parse` que verifica que ha llegado al final de la cadena de entrada después de analizar la expresión (un programa Egg es una sola expresión), y que nos da la estructura de datos del programa. ```{includeCode: strip_log, test: join} function parse(program) { @@ -185,13 +187,13 @@ console.log(parse("+(a, 10)")); {{index "mensaje de error"}} -¡Funciona! No nos da información muy útil cuando falla y no almacena la línea y la columna en las que comienza cada expresión, lo cual podría ser útil al informar errores más tarde, pero es suficiente para nuestros propósitos. +¡Funciona! No nos da información muy útil cuando falla y no almacena la línea y columna en las que comienza cada expresión, lo cual podría ser útil para informar de errores más tarde, pero es suficientemente bueno para lo que queremos hacer. ## El evaluador {{index "función de evaluación", "evaluación", "interpretación", "árbol de sintaxis", "lenguaje Egg"}} -¿Qué podemos hacer con el árbol de sintaxis de un programa? ¡Ejecutarlo, por supuesto! Y eso es lo que hace el evaluador. Le das un árbol de sintaxis y un objeto de ámbito que asocia nombres con valores, y evaluará la expresión que representa el árbol y devolverá el valor que esto produce. +¿Qué podemos hacer con el árbol de sintaxis de un programa? ¡Ejecutarlo, por supuesto! Y eso es lo que hace el evaluador. Le das un árbol de sintaxis y un objeto de ámbito que asocia nombres con valores, y evaluará la expresión que representa el árbol y devolverá el valor que todo esto produce. ```{includeCode: true} const specialForms = Object.create(null); @@ -204,7 +206,7 @@ function evaluate(expr, scope) { return scope[expr.name]; } else { throw new ReferenceError( - `Vinculación indefinida: ${expr.name}`); + `Asociación indefinida: ${expr.name}`); } } else if (expr.type == "apply") { let {operator, args} = expr; @@ -225,7 +227,7 @@ function evaluate(expr, scope) { {{index "expresión literal", "ámbito"}} -El evaluador tiene código para cada uno de los tipos de expresión. Una expresión de valor literal produce su valor. (Por ejemplo, la expresión `100` simplemente se evalúa como el número 100.) Para un enlace, debemos verificar si está realmente definido en el ámbito y, si lo está, obtener el valor del enlace. +El evaluador tiene código para cada uno de los tipos de expresión. Una expresión de valor literal produce su valor. (Por ejemplo, la expresión `100` simplemente se evalúa como el número 100.) Para una asociación (o variable), debemos verificar si está realmente definida en el ámbito y, si lo está, obtener el valor de esta. {{index ["función", "aplicación"]}} @@ -235,7 +237,7 @@ Usamos valores de función JavaScript simples para representar los valores de fu {{index legibilidad, "función de evaluación", "recursión", "análisis sintáctico"}} -La estructura recursiva de `evaluate` se asemeja a la estructura similar del analizador sintáctico, y ambos reflejan la estructura del lenguaje en sí. También sería posible combinar el analizador sintáctico y el evaluador en una sola función, y evaluar durante el análisis sintáctico. Pero dividirlos de esta manera hace que el programa sea más claro y flexible. +La estructura recursiva de `evaluate` se asemeja a la estructura del analizador sintáctico, y ambos reflejan la estructura del lenguaje en sí. También sería posible combinar el analizador sintáctico y el evaluador en una sola función, y evaluar durante el análisis sintáctico. Al separarlos de esta manera, el programa es más claro y flexible. {{index "Lenguaje Egg", "interpretación"}} @@ -261,7 +263,7 @@ specialForms.if = (args, scope) => { {{index "ejecución condicional", "operador ternario", "operador ?", "operador condicional"}} -La construcción `if` de Egg espera exactamente tres argumentos. Evaluará el primero, y si el resultado no es el valor `false`, evaluará el segundo. De lo contrario, se evaluará el tercero. Esta forma `if` se asemeja más al operador ternario `?:` de JavaScript que al `if` de JavaScript. Es una expresión, no una declaración, y produce un valor, concretamente, el resultado del segundo o tercer argumento. +La construcción `if` de Egg espera exactamente tres argumentos. Evaluará el primero y, si el resultado no es el valor `false`, evaluará el segundo. De lo contrario, se evaluará el tercero. Esta forma `if` se asemeja más al operador ternario `?:` de JavaScript que al `if` de JavaScript. Es una expresión, no una declaración, y produce un valor, concretamente, el resultado del segundo o tercer argumento. {{index Booleano}} @@ -269,7 +271,7 @@ Egg también difiere de JavaScript en cómo maneja el valor de condición para ` {{index "evaluación de cortocircuito"}} -La razón por la que necesitamos representar `if` como una forma especial, en lugar de una función regular, es que todos los argumentos de las funciones se evalúan antes de llamar a la función, mientras que `if` debe evaluar solo _uno_ de sus segundos o terceros argumentos, dependiendo del valor del primero. +La razón por la que necesitamos representar `if` como una forma especial, en lugar de una función regular, es que todos los argumentos de las funciones se evalúan antes de llamar a la función, mientras que `if` debe evaluar solo _uno_ de entre su segundo y tercer argumentos, dependiendo del valor del primero. La forma `while` es similar. @@ -283,7 +285,7 @@ specialForms.while = (args, scope) => { } // Dado que undefined no existe en Egg, devolvemos false, - // por falta de un resultado significativo. + // para la falta de un resultado con sentido. return false; }; ``` @@ -302,7 +304,7 @@ specialForms.do = (args, scope) => { {{index ["operador =", "en Egg"], ["vinculación", "en Egg"]}} -Para poder crear vinculaciones y darles nuevos valores, también creamos una forma llamada `define`. Espera una palabra como su primer argumento y una expresión que produzca el valor a asignar a esa palabra como su segundo argumento. Dado que `define`, al igual que todo, es una expresión, debe devolver un valor. Haremos que devuelva el valor que se asignó (como el operador `=` de JavaScript). +Para poder crear asociaciones y darles nuevos valores, también creamos una forma llamada `define`. Espera una palabra como su primer argumento y una expresión que produzca el valor a asignar a esa palabra como su segundo argumento. Dado que `define`, al igual que todo, es una expresión, debe devolver un valor. Haremos que devuelva el valor que se asignó (como el operador `=` de JavaScript). ```{includeCode: true} specialForms.define = (args, scope) => { @@ -319,7 +321,7 @@ specialForms.define = (args, scope) => { {{index "Lenguaje Egg", "función evaluate", [binding, "en Egg"]}} -El ((scope)) aceptado por `evaluate` es un objeto con propiedades cuyos nombres corresponden a los nombres de los bindings y cuyos valores corresponden a los valores a los que esos bindings están ligados. Definamos un objeto para representar el ((scope global)). +El ((scope)) aceptado por `evaluate` es un objeto con propiedades cuyos nombres corresponden a los nombres de las asociaciones y cuyos valores corresponden a los valores a los que esas asociaciones están ligadas. Definamos un objeto para representar el ((scope global)). Para poder usar la construcción `if` que acabamos de definir, necesitamos tener acceso a valores ((Booleanos)). Dado que solo hay dos valores Booleanos, no necesitamos una sintaxis especial para ellos. Simplemente asignamos dos nombres a los valores `true` y `false` y los usamos. @@ -369,7 +371,7 @@ function run(program) { {{index "Función Object.create", prototipo}} -Utilizaremos las cadenas de prototipos de objetos para representar ámbitos anidados para que el programa pueda agregar bindings a su ámbito local sin modificar el ámbito de nivel superior. +Utilizaremos cadenas de prototipos de objetos para representar ámbitos anidados para que el programa pueda agregar asociaciones a su ámbito local sin modificar el ámbito de nivel superior. ``` run(` @@ -385,7 +387,7 @@ do(define(total, 0), {{index "ejemplo de suma", "Lenguaje Egg"}} -Este es el programa que hemos visto varias veces antes, que calcula la suma de los números del 1 al 10, expresado en Egg. Es claramente más feo que el equivalente programa en JavaScript, pero no está mal para un lenguaje implementado en menos de 150 ((líneas de código)). +Este es el programa que hemos visto varias veces antes, que calcula la suma de los números del 1 al 10, expresado en Egg. Es claramente más feo que el programa equivalente en JavaScript, pero no está mal para un lenguaje implementado en menos de 150 ((líneas de código)). {{id egg_fun}} @@ -393,9 +395,7 @@ Este es el programa que hemos visto varias veces antes, que calcula la suma de l {{index "función", "Lenguaje Egg"}} -Un lenguaje de programación sin funciones es un pobre lenguaje de programación. - -Afortunadamente, no es difícil agregar una construcción `fun`, que trata su último argumento como el cuerpo de la función y utiliza todos los argumentos anteriores como los nombres de los parámetros de la función. +Un lenguaje de programación sin funciones es sin lugar a dudas un mal lenguaje de programación. Por suerte, no es difícil agregar una construcción `fun`, que trata su último argumento como el cuerpo de la función y utiliza todos los argumentos anteriores como los nombres de los parámetros de la función. ```{includeCode: true} specialForms.fun = (args, scope) => { @@ -405,7 +405,7 @@ specialForms.fun = (args, scope) => { let body = args[args.length - 1]; let params = args.slice(0, args.length - 1).map(expr => { if (expr.type != "word") { - throw new SyntaxError("Los nombres de los parámetros deben ser palabras"); + throw new SyntaxError("Los nombres de los parámetros deben ser de tipo word"); } return expr.name; }); @@ -452,13 +452,14 @@ Lo que hemos construido es un intérprete. Durante la evaluación, actúa direct {{index eficiencia, rendimiento, [enlace, "definición"], [memoria, velocidad]}} -_La compilación_ es el proceso de agregar otro paso entre el análisis sintáctico y la ejecución de un programa, que transforma el programa en algo que puede ser evaluado de manera más eficiente al hacer la mayor cantidad de trabajo posible por adelantado. Por ejemplo, en lenguajes bien diseñados, es obvio, para cada uso de un enlace, a qué enlace se hace referencia, sin ejecutar realmente el programa. Esto se puede utilizar para evitar buscar el enlace por nombre cada vez que se accede, en su lugar, recuperándolo directamente desde una ubicación de memoria predeterminada. +_La compilación_ es el proceso de agregar otro paso entre el análisis sintáctico y la ejecución de un programa, que transforma el programa en algo que puede ser evaluado de manera más eficiente al hacer la mayor cantidad de trabajo posible por adelantado. Por ejemplo, en lenguajes bien diseñados, para cada uso de una asociación, es obvio a qué asociación se hace referencia, sin tener que buscarla por nombre cada vez que se accede. Esto se puede hacer para evitar buscar la asociación por nombre cada vez que se accede a la misma, recuperando el valor de la asociación directamente desde un lugar predeterminado de la memoria. + Tradicionalmente, ((compilar)) implica convertir el programa a ((código máquina)), el formato en bruto que un procesador de computadora puede ejecutar. Pero cualquier proceso que convierta un programa a una representación diferente se puede considerar como compilación. {{index simplicidad, "Constructor de funciones", "transpilación"}} -Sería posible escribir una estrategia de ((evaluación)) alternativa para Egg, una que primero convierte el programa a un programa JavaScript, usa `Function` para invocar el compilador de JavaScript en él, y luego ejecuta el resultado. Cuando se hace correctamente, esto haría que Egg se ejecutara muy rápido y aún así fuera bastante simple de implementar. +Sería posible escribir una estrategia de ((evaluación)) alternativa para Egg, una que primero convierte el programa a un programa JavaScript, usa `Function` para invocar el compilador de JavaScript en él, y luego ejecuta el resultado. Hecho de manera adecuada, esto haría que Egg se ejecutara muy rápido y aún así fuera bastante simple de implementar. Si te interesa este tema y estás dispuesto a dedicar tiempo a ello, te animo a intentar implementar ese compilador como ejercicio. @@ -466,13 +467,15 @@ Si te interesa este tema y estás dispuesto a dedicar tiempo a ello, te animo a {{index "lenguaje Egg"}} -Cuando definimos `if` y `while`, probablemente notaste que eran envoltorios más o menos triviales alrededor del propio `if` y `while` de JavaScript. De manera similar, los valores en Egg son simplemente valores regulares de JavaScript. Cerrar la brecha hacia un sistema más primitivo, como el código máquina que entiende el procesador, requiere más esfuerzo, pero la forma en que funciona se asemeja a lo que estamos haciendo aquí.Aunque el lenguaje de juguete de este capítulo no hace nada que no se pudiera hacer mejor en JavaScript, _sí_ hay situaciones donde escribir pequeños lenguajes ayuda a realizar trabajos reales. +Cuando hemos definido `if` y `while`, probablemente has notado que eran envoltorios más o menos triviales alrededor de los propios `if` y `while` de JavaScript. De manera similar, los valores en Egg son simplemente valores normales de JavaScript. Dar el paso a un sistema más primitivo, como el código máquina que entiende el procesador, requiere mucho más esfuerzo, pero la forma en que funciona se asemeja a lo que estamos haciendo aquí. + +Aunque el lenguaje de juguete de este capítulo no hace nada que no se pudiera hacer mejor en JavaScript, _sí_ hay situaciones donde escribir pequeños lenguajes ayuda a sacar adelante trabajo de verdad. Tal lenguaje no tiene por qué parecerse a un lenguaje de programación típico. Si JavaScript no viniera equipado con expresiones regulares, por ejemplo, podrías escribir tu propio analizador sintáctico y evaluador para expresiones regulares. {{index "generador de analizadores sintácticos"}} -O imagina que estás construyendo un programa que permite crear rápidamente analizadores sintácticos al proporcionar una descripción lógica del lenguaje que necesitan analizar. Podrías definir una notación específica para eso y un compilador que la convierta en un programa analizador. +O imagina que estás construyendo un programa que permite crear rápidamente analizadores sintácticos (o _parsers_) al proporcionar una descripción lógica del lenguaje que necesitan analizar. Podrías definir una notación específica para eso y un compilador que la convierta en un programa analizador. ```{lang: null} expr = número | cadena | nombre | aplicación @@ -532,7 +535,7 @@ La forma más sencilla de hacer esto es representar los arrays de Egg con arrays {{index "método slice"}} -Los valores añadidos al ámbito superior deben ser funciones. Al usar un argumento restante (con la notación de triple punto), la definición de `array` puede ser _muy_ simple. +Los valores añadidos al ámbito superior deben ser funciones. Al usar un argumento rest (restante, es decir, con la notación de triple punto), la definición de `array` puede ser _muy_ simple. hint}} @@ -542,7 +545,7 @@ hint}} La forma en que hemos definido `fun` permite que las funciones en Egg hagan referencia al ámbito circundante, lo que permite que el cuerpo de la función use valores locales que eran visibles en el momento en que se definió la función, al igual que lo hacen las funciones de JavaScript. -El siguiente programa ilustra esto: la función `f` devuelve una función que suma su argumento al argumento de `f`, lo que significa que necesita acceder al ((ámbito)) local dentro de `f` para poder usar la vinculación `a`. +El siguiente programa ilustra esto: la función `f` devuelve una función que suma su argumento al argumento de `f`, lo que significa que necesita acceder al ((ámbito)) local dentro de `f` para poder usar la asociación `a`. ``` run(` @@ -552,17 +555,17 @@ do(define(f, fun(a, fun(b, +(a, b)))), // → 9 ``` -Vuelve a la definición del formulario `fun` y explica qué mecanismo hace que esto funcione. +Vuelve a la definición de la forma `fun` y explica qué mecanismo hace que esto funcione. {{hint {{index cierre, "cierre en Egg (ejercicio)"}} -Una vez más, estamos montando un mecanismo en JavaScript para obtener la característica equivalente en Egg. Los formularios especiales reciben el ámbito local en el que se evalúan para que puedan evaluar sus subformas en ese ámbito. La función devuelta por `fun` tiene acceso al argumento `scope` dado a su función contenedora y lo utiliza para crear el ámbito ((local)) de la función cuando se llama. +Una vez más, estamos montando un mecanismo en JavaScript para obtener la característica equivalente en Egg. Las formas especiales reciben el ámbito local en el que se evalúan para que puedan evaluar sus subformas en ese ámbito. La función devuelta por `fun` tiene acceso al argumento `scope` dado a su función contenedora y lo utiliza para crear el ámbito ((local)) de la función cuando se llama. {{index "compilación"}} -Esto significa que el ((prototipo)) del ámbito local será el ámbito en el cual la función fue creada, lo que hace posible acceder a los enlaces en ese ámbito desde la función. Esto es todo lo que se necesita para implementar el cierre (aunque para compilarlo de una manera realmente eficiente, sería necesario hacer un poco más de trabajo). +Esto significa que el ((prototipo)) del ámbito local será el ámbito en el cual la función fue creada, lo que hace posible acceder a los enlaces en ese ámbito desde la función. Esto es todo lo que se necesita para implementar la clausura (aunque para compilarlo de una manera realmente eficiente, sería necesario hacer un poco más de trabajo). hint}} @@ -570,7 +573,7 @@ hint}} {{index "carácter de almohadilla", "lenguaje Egg", "comentarios en Egg (ejercicio)"}} -Sería bueno si pudiéramos escribir ((comentario))s en Egg. Por ejemplo, siempre que encontremos un signo de almohadilla (`#`), podríamos tratar el resto de la línea como un comentario y ignorarlo, similar a `//` en JavaScript. +Sería bueno si pudiéramos escribir ((comentario))s en Egg. Por ejemplo, siempre que encontremos un signo de almohadilla (`#`), podríamos tratar el resto de la línea como un comentario e ignorarlo, como con `//` en JavaScript. {{index "función skipSpace"}} @@ -602,7 +605,7 @@ if}} Asegúrate de que tu solución maneje múltiples comentarios seguidos, con posiblemente espacios en blanco entre ellos o después de ellos. -Una ((expresión regular)) es probablemente la forma más sencilla de resolver esto. Escribe algo que coincida con "espacio en blanco o un comentario, cero o más veces". Utiliza el método `exec` o `match` y observa la longitud del primer elemento en la matriz devuelta (la coincidencia completa) para averiguar cuántos caracteres cortar. +Para resolver esto, la forma más sencilla es probablemente usar alguna ((expresión regular)). Escribe algo que coincida con "espacio en blanco o un comentario, cero o más veces". Utiliza el método `exec` o `match` y observa la longitud del primer elemento en la matriz devuelta (la coincidencia completa) para averiguar cuántos caracteres cortar. hint}} @@ -610,7 +613,7 @@ hint}} {{index [enlace, "definición"], "asignación", "corrección de ámbito (ejercicio)"}} -Actualmente, la única forma de asignar un enlace un valor es `define`. Esta construcción actúa como una forma tanto de definir nuevos enlaces como de dar un nuevo valor a los existentes. +Actualmente, la única forma de asignar un valor a una asociación es usar `define`. Esta construcción actúa como una forma tanto de definir nuevos enlaces como de dar un nuevo valor a los existentes. {{index "enlace local"}} @@ -618,7 +621,7 @@ Esta ((ambigüedad)) causa un problema. Cuando intentas darle un nuevo valor a u {{index "tipo Error de Referencia"}} -Agrega una forma especial `set`, similar a `define`, que da un nuevo valor a un enlace, actualizando el enlace en un ámbito exterior si aún no existe en el ámbito interior. Si el enlace no está definido en absoluto, lanza un `ReferenceError` (otro tipo de error estándar). +Agrega una forma especial `set`, similar a `define`, que da un nuevo valor a una asociación, actualizando la asociación en un ámbito exterior si aún no existe en el ámbito interior. Si la asociación no está definida, lanza un `ReferenceError` (otro tipo de error estándar). {{index "hasOwn function", prototype, "getPrototypeOf function"}} diff --git a/html/05_higher_order.html b/html/05_higher_order.html index a356ed8f..08565672 100644 --- a/html/05_higher_order.html +++ b/html/05_higher_order.html @@ -234,7 +234,7 @@

    La operación de orden superior que representa esta idea se llama reduce (a veces también llamada fold). Construye un valor tomando repetidamente un único elemento del array y combinándolo con el valor actual. Al sumar números empezarías con el número cero y añadirías cada elemento a la suma.

    -

    Los parámetros de reduce son, además del array, una función de combinación y un valor inicial. Esta función es un poco menos directa que filter y map, así que obsérva detenidamente:

    +

    Los parámetros de reduce son, además del array, una función de combinación y un valor inicial. Esta función es un poco menos directa que filter y map, así que observa detenidamente:

    function reducir(array, combinación, principio) {
       let actual = inicio;
    diff --git a/html/11_async.html b/html/11_async.html
    index 4e4cc052..87a6568b 100644
    --- a/html/11_async.html
    +++ b/html/11_async.html
    @@ -513,9 +513,9 @@ 

    Momentos de tranquilidad

    -

    Hay una cámara de seguridad cerca del laboratorio de Carla que se activa con un sensor de movimiento. Está conectada a la red y comienza a enviar un flujo de video cuando está activa. Como prefiere no ser descubierta, Carla ha configurado un sistema que detecta este tipo de tráfico de red inalámbrico y enciende una luz en su guarida cada vez que hay actividad afuera, de modo que sepa cuándo mantenerse en silencio.

    +

    Cerca del laboratorio de Carla hay una cámara de seguridad que se activa con un sensor de movimiento. Está conectada a la red y comienza a enviar un flujo de vídeo cuando está activa. Como prefiere no ser descubierta, Carla ha configurado un sistema que detecta este tipo de tráfico de red inalámbrico y enciende una luz en su guarida cada vez que hay actividad afuera, de modo que sepa cuándo estar tranquila.

    -

    También ha estado registrando los momentos en que la cámara se activa desde hace un tiempo, y quiere utilizar esta información para visualizar qué momentos, en una semana promedio, tienden a ser tranquilos y cuáles tienden a no serlo. El registro se almacena en archivos que contienen un número de marca de tiempo por línea (como devuelto por Date.now()).

    +

    También ha estado registrando los momentos en que la cámara se activa desde hace un tiempo, y quiere utilizar esta información para visualizar qué momentos, en una semana promedio, tienden a ser tranquilos y cuáles tienden a no serlo. El registro se almacena en archivos que contienen un número de marca de tiempo por línea (como los que proporciona Date.now()).

    1695709940692
     1695701068331
    @@ -525,11 +525,11 @@ 

    La función activityGraph, proporcionada por el sandbox, resume dicha tabla en una cadena.

    -

    Utiliza la función textFile definida anteriormente, que al recibir un nombre de archivo devuelve una promesa que se resuelve en el contenido del archivo. Recuerda que new Date(marcaDeTiempo) crea un objeto Date para ese momento, que tiene métodos getDay y getHours que devuelven el día de la semana y la hora del día.

    +

    Utiliza la función textFile ( o archivoTexto) definida anteriormente, que al recibir un nombre de archivo devuelve una promesa que se resuelve en el contenido del archivo. Recuerda que new Date(marcaDeTiempo) crea un objeto Date para ese momento, que tiene métodos getDay y getHours que devuelven el día de la semana y la hora del día.

    Ambos tipos de archivos —la lista de archivos de registro y los propios archivos de registro— tienen cada dato en una línea, separados por caracteres de nueva línea ("\n").

    -
    async function activityTable(day) {
    +
    async function activityTable(día) {
       let logFileList = await textFile("camera_logs.txt");
       // Tu código aquí
     }
    @@ -539,7 +539,7 @@ 

    Mostrar pistas...
    -

    Necesitarás convertir el contenido de estos archivos en un array. La forma más fácil de hacerlo es utilizando el método split en la cadena producida por textFile. Ten en cuenta que para los archivos de registro, eso te dará un array de cadenas, que debes convertir a números antes de pasarlos a new Date.

    +

    Necesitarás convertir el contenido de estos archivos en un array. La forma más fácil de hacerlo es utilizando el método split en la cadena producida por textFile ( o archivoTexto). Ten en cuenta que para los archivos de registro, eso te dará un array de cadenas, que debes convertir a números antes de pasarlos a new Date.

    Resumir todos los puntos temporales en una tabla de horas se puede hacer creando una tabla (array) que contenga un número para cada hora del día. Luego puedes recorrer todas las marcas de tiempo (de los archivos de registro y los números en cada archivo de registro) y, para cada uno, si sucedió en el día correcto, tomar la hora en que ocurrió y sumar uno al número correspondiente en la tabla.

    @@ -551,12 +551,12 @@

    Reescribe la función del ejercicio anterior sin async/await, utilizando métodos simples de Promise.

    -
    function activityTable(día) {
    +
    function activityTable(día) {
       // Tu código aquí
     }
     
     activityTable(6)
    -  .then(tabla => console.log(gráficoActividad(tabla)));
    + .then(tabla => console.log(activityGraph(tabla)));

    En este estilo, usar Promise.all será más conveniente que intentar modelar un bucle sobre los archivos de registro. En la función async, simplemente usar await en un bucle es más simple. Si leer un archivo lleva un tiempo, ¿cuál de estos dos enfoques necesitará menos tiempo para ejecutarse?

    @@ -568,8 +568,8 @@

    Ahora tenemos una promesa que devuelve un array de archivos de registro. Podemos llamar a then nuevamente en eso, y poner la lógica de recuento de marcas de tiempo allí. Algo así:

    -
    function activityTable(día) {
    -  return archivoTexto("registros_camara.txt").then(archivos => {
    +
    function activityTable(día) {
    +  return archivoTexto("camera_logs.txt").then(archivos => {
         return Promise.all(archivos.split("\n").map(archivoTexto));
       }).then(logs => {
         // analizar...
    diff --git a/html/12_language.html b/html/12_language.html
    index ca07d244..ccc642ff 100644
    --- a/html/12_language.html
    +++ b/html/12_language.html
    @@ -16,23 +16,23 @@ 

    Proyecto: Un Lenguaje de Programación

    El evaluador, que determina el significado de expresiones en un lenguaje de programación, es solo otro programa.

    -
    Hal Abelson y Gerald Sussman, Estructura e Interpretación de Programas de Computadora
    +
    Hal Abelson y Gerald Sussman, Estructura e Interpretación de Programas Informáticos
    Ilustración que muestra un huevo con agujeros, mostrando huevos más pequeños dentro, que a su vez tienen huevos aún más pequeños dentro de ellos, y así sucesivamente

    Crear tu propio lenguaje de programación es sorprendentemente fácil (si no apuntas muy alto) y muy esclarecedor.

    -

    Lo principal que quiero mostrar en este capítulo es que no hay magia involucrada en la construcción de un lenguaje de programación. A menudo he sentido que algunas invenciones humanas eran tan inmensamente inteligentes y complicadas que nunca las entendería. Pero con un poco de lectura y experimentación, a menudo resultan ser bastante mundanas.

    +

    Lo principal que quiero mostrar en este capítulo es que la construcción de un lenguaje de programación no es resultado de ningún tipo de magia. A menudo he sentido que algunas invenciones humanas eran tan inmensamente inteligentes y complicadas que nunca las entendería. Pero con un poco de lectura y experimentación, a menudo resultan ser bastante mundanas.

    -

    Construiremos un lenguaje de programación llamado Egg. Será un lenguaje simple y diminuto, pero lo suficientemente poderoso como para expresar cualquier cálculo que puedas imaginar. Permitirá una simple abstracción basada en funciones.

    +

    Construiremos un lenguaje de programación llamado Egg. Será un lenguaje simple y diminuto, pero lo suficientemente poderoso como para expresar cualquier cálculo que puedas imaginar. Permitirá una abstracción simple basada en funciones.

    Análisis Sintáctico

    -

    La parte más inmediatamente visible de un lenguaje de programación es su sintaxis, o notación. Un analizador sintáctico es un programa que lee un fragmento de texto y produce una estructura de datos que refleja la estructura del programa contenido en ese texto. Si el texto no forma un programa válido, el analizador sintáctico debería señalar el error.

    +

    La parte más inmediatamente visible de un lenguaje de programación es su sintaxis, o notación. Un analizador sintáctico (o parser) es un programa que lee un fragmento de texto y produce una estructura de datos que refleja la estructura del programa contenido en ese texto. Si el texto no forma un programa válido, el analizador sintáctico debería señalar el error.

    Nuestro lenguaje tendrá una sintaxis simple y uniforme. Todo en Egg es una expresión. Una expresión puede ser el nombre de una asignación, un número, una cadena o una aplicación. Las aplicaciones se utilizan para llamadas de funciones pero también para estructuras como if o while.

    -

    Para mantener el analizador sintáctico simple, las cadenas en Egg no admiten nada parecido a los escapes con barra invertida. Una cadena es simplemente una secuencia de caracteres que no son comillas dobles, envueltos entre comillas dobles. Un número es una secuencia de dígitos. Los nombres de las asignaciones pueden consistir en cualquier carácter que no sea espacio en blanco y que no tenga un significado especial en la sintaxis.

    +

    Para que el analizador sintáctico sea más simple, las cadenas en Egg no admiten nada como los escapes con barra invertida. Una cadena es simplemente una secuencia de caracteres que no son comillas dobles, envueltos entre comillas dobles. Un número es una secuencia de dígitos. Los nombres de las asignaciones pueden consistir en cualquier carácter que no sea espacio en blanco y que no tenga un significado especial en la sintaxis.

    Las aplicaciones se escriben de la misma manera que en JavaScript, colocando paréntesis después de una expresión y teniendo cualquier número de argumentos entre esos paréntesis, separados por comas.

    @@ -41,7 +41,7 @@

    -

    La uniformidad del lenguaje Egg significa que las cosas que son operadores en JavaScript (como >) son asignaciones normales en este lenguaje, aplicadas de la misma manera que otras funciones. Y dado que la sintaxis no tiene concepto de bloque, necesitamos un constructo do para representar la realización de múltiples tareas en secuencia.

    +

    Como queremos tener la ya mencionada uniformidad en el lenguaje Egg, resulta que, cosas que son operadores en JavaScript (como >), serán asignaciones normales en este lenguaje, aplicadas de la misma manera que otras funciones. Y dado que la sintaxis no tiene concepto de bloque, necesitamos un constructo do para representar la realización de múltiples tareas en secuencia.

    La estructura de datos que el analizador sintáctico utilizará para describir un programa consiste en objetos expresión, cada uno de los cuales tiene una propiedad type que indica el tipo de expresión que es y otras propiedades para describir su contenido.

    @@ -58,15 +58,17 @@

    -

    Esta estructura de datos se llama un árbol de sintaxis. Si te imaginas los objetos como puntos y los enlaces entre ellos como líneas entre esos puntos, tiene una forma similar a un árbol. El hecho de que las expresiones contienen otras expresiones, que a su vez pueden contener más expresiones, es similar a la forma en que las ramas de un árbol se dividen y vuelven a dividir.

    Un diagrama que muestra la estructura del árbol de sintaxis del programa de ejemplo. La raíz está etiquetada como 'do' y tiene dos hijos, uno etiquetado como 'define' y otro como 'if'. A su vez, estos tienen más hijos que describen su contenido.
    +

    Esta estructura de datos se llama un árbol sintáctico. Si te imaginas los objetos como puntos y los enlaces entre ellos como líneas entre esos puntos, tiene forma de árbol. El hecho de que las expresiones contienen otras expresiones, que a su vez pueden contener más expresiones, es similar a la forma en que las ramas de un árbol se dividen y vuelven a dividir.

    Un diagrama que muestra la estructura del árbol de sintaxis del programa de ejemplo. La raíz está etiquetada como 'do' y tiene dos hijos, uno etiquetado como 'define' y otro como 'if'. A su vez, estos tienen más hijos que describen su contenido.
    -

    Contrasta esto con el analizador que escribimos para el formato de archivo de configuración en el Capítulo 9, que tenía una estructura simple: dividía la entrada en líneas y manejaba esas líneas una a la vez. Solo había algunas formas simples que una línea podía tener.

    +

    Contrasta esto con el analizador que escribimos para el formato de archivo de configuración en el Capítulo 9, que tenía una estructura simple: dividía la entrada en líneas y manejaba esas líneas una por una. Solo había un puñado de formas simples que una línea podía tener.

    Aquí debemos encontrar un enfoque diferente. Las expresiones no están separadas en líneas, y tienen una estructura recursiva. Las expresiones de aplicación contienen otras expresiones.

    Afortunadamente, este problema puede resolverse muy bien escribiendo una función de análisis sintáctico que sea recursiva de una manera que refleje la naturaleza recursiva del lenguaje.

    -

    Definimos una función parseExpression, que recibe una cadena como entrada y devuelve un objeto que contiene la estructura de datos de la expresión al inicio de la cadena, junto con la parte de la cadena que queda después de analizar esta expresión. Al analizar subexpresiones (el argumento de una aplicación, por ejemplo), esta función puede ser llamada nuevamente, obteniendo la expresión de argumento así como el texto que queda. Este texto a su vez puede contener más argumentos o puede ser el paréntesis de cierre que finaliza la lista de argumentos.Esta es la primera parte del analizador sintáctico:

    +

    Definimos una función parseExpression, que recibe una cadena como entrada y devuelve un objeto que contiene la estructura de datos de la expresión al inicio de la cadena, junto con la parte de la cadena que queda después de analizar esta expresión. Al analizar subexpresiones (el argumento de una aplicación, por ejemplo), esta función puede ser llamada nuevamente, obteniendo la expresión de argumento así como el texto que queda. Este texto a su vez puede contener más argumentos o puede ser el paréntesis de cierre que finaliza la lista de argumentos.

    + +

    Esta es la primera parte del analizador sintáctico:

    function parseExpression(program) {
       program = skipSpace(program);
    @@ -90,11 +92,11 @@ 

    return string.slice(first); }

    -

    Debido a que Egg, al igual que JavaScript, permite cualquier cantidad de espacios en blanco entre sus elementos, debemos cortar repetidamente el espacio en blanco del inicio de la cadena del programa. Eso es para lo que sirve la función skipSpace.

    +

    Como Egg, al igual que JavaScript, permite cualquier cantidad de espacios en blanco entre sus elementos, debemos cortar repetidamente el espacio en blanco del inicio de la cadena del programa. Para eso es para lo que sirve la función skipSpace.

    Después de omitir cualquier espacio inicial, parseExpression utiliza tres expresiones regulares para detectar los tres elementos atómicos que admite Egg: cadenas, números y palabras. El analizador construye un tipo diferente de estructura de datos dependiendo de cuál de ellos coincida. Si la entrada no coincide con ninguna de estas tres formas, no es una expresión válida y el analizador genera un error. Utilizamos el constructor SyntaxError aquí. Esta es una clase de excepción definida por el estándar, al igual que Error, pero más específica.

    -

    Luego cortamos la parte que coincidió de la cadena del programa y la pasamos, junto con el objeto de la expresión, a parseApply, que verifica si la expresión es una aplicación. Si lo es, analiza una lista de argumentos entre paréntesis.

    +

    Luego cortamos la parte que coincidió de la cadena del programa y pasamos el resto, junto con el objeto de la expresión, a parseApply, que verifica si la expresión es una aplicación. Si lo es, analiza una lista de argumentos entre paréntesis.

    function parseApply(expr, program) {
       program = skipSpace(program);
    @@ -117,13 +119,13 @@ 

    return parseApply(expr, program.slice(1)); }

    -

    Si el próximo carácter en el programa no es un paréntesis de apertura, esto no es una aplicación y parseApply devuelve la expresión que se le dio.

    +

    Si el próximo carácter en el programa no es un paréntesis de apertura, entonces no se trata de una aplicación y parseApply devuelve la expresión que se le dio.

    -

    De lo contrario, se salta el paréntesis de apertura y crea el objeto árbol sintáctico para esta expresión de aplicación. Luego llama recursivamente a parseExpression para analizar cada argumento hasta encontrar un paréntesis de cierre. La recursión es indirecta, a través de parseApply y parseExpression llamándose mutuamente.

    +

    De lo contrario, se salta el paréntesis de apertura y crea el objeto árbol sintáctico para esta expresión de aplicación. Luego llama recursivamente a parseExpression para analizar cada argumento hasta encontrar un paréntesis de cierre. La recursión es indirecta, realizada a través de parseApply y parseExpression llamándose mutuamente.

    Dado que una expresión de aplicación puede a su vez ser aplicada (como en multiplicador(2)(1)), parseApply debe, después de analizar una aplicación, llamarse a sí misma nuevamente para verificar si sigue otro par de paréntesis.

    -

    Esto es todo lo que necesitamos para analizar Egg. Lo envolvemos en una conveniente parse función que verifica que ha llegado al final de la cadena de entrada después de analizar la expresión (un programa Egg es una sola expresión), y que nos da la estructura de datos del programa.

    +

    Esto es todo lo que necesitamos para analizar el lenguaje Egg. Lo envolvemos en una conveniente función parse que verifica que ha llegado al final de la cadena de entrada después de analizar la expresión (un programa Egg es una sola expresión), y que nos da la estructura de datos del programa.

    function parse(program) {
       let {expr, rest} = parseExpression(program);
    @@ -139,13 +141,13 @@ 

    // args: [{type: "word", name: "a"}, // {type: "value", value: 10}]}

    -

    ¡Funciona! No nos da información muy útil cuando falla y no almacena la línea y la columna en las que comienza cada expresión, lo cual podría ser útil al informar errores más tarde, pero es suficiente para nuestros propósitos.

    +

    ¡Funciona! No nos da información muy útil cuando falla y no almacena la línea y columna en las que comienza cada expresión, lo cual podría ser útil para informar de errores más tarde, pero es suficientemente bueno para lo que queremos hacer.

    El evaluador

    -

    ¿Qué podemos hacer con el árbol de sintaxis de un programa? ¡Ejecutarlo, por supuesto! Y eso es lo que hace el evaluador. Le das un árbol de sintaxis y un objeto de ámbito que asocia nombres con valores, y evaluará la expresión que representa el árbol y devolverá el valor que esto produce.

    +

    ¿Qué podemos hacer con el árbol de sintaxis de un programa? ¡Ejecutarlo, por supuesto! Y eso es lo que hace el evaluador. Le das un árbol de sintaxis y un objeto de ámbito que asocia nombres con valores, y evaluará la expresión que representa el árbol y devolverá el valor que todo esto produce.

    -
    const specialForms = Object.create(null);
    +
    const specialForms = Object.create(null);
     
     function evaluate(expr, scope) {
       if (expr.type == "value") {
    @@ -155,7 +157,7 @@ 

    return scope[expr.name]; } else { throw new ReferenceError( - `Vinculación indefinida: ${expr.name}`); + `Asociación indefinida: ${expr.name}`); } } else if (expr.type == "apply") { let {operator, args} = expr; @@ -173,13 +175,13 @@

    -

    El evaluador tiene código para cada uno de los tipos de expresión. Una expresión de valor literal produce su valor. (Por ejemplo, la expresión 100 simplemente se evalúa como el número 100.) Para un enlace, debemos verificar si está realmente definido en el ámbito y, si lo está, obtener el valor del enlace.

    +

    El evaluador tiene código para cada uno de los tipos de expresión. Una expresión de valor literal produce su valor. (Por ejemplo, la expresión 100 simplemente se evalúa como el número 100.) Para una asociación (o variable), debemos verificar si está realmente definida en el ámbito y, si lo está, obtener el valor de esta.

    Las aplicaciones son más complicadas. Si son una forma especial, como if, no evaluamos nada y pasamos las expresiones de argumento, junto con el ámbito, a la función que maneja esta forma. Si es una llamada normal, evaluamos el operador, verificamos que sea una función, y la llamamos con los argumentos evaluados.

    Usamos valores de función JavaScript simples para representar los valores de función de Egg. Volveremos a esto más tarde, cuando se defina la forma especial llamada fun.

    -

    La estructura recursiva de evaluate se asemeja a la estructura similar del analizador sintáctico, y ambos reflejan la estructura del lenguaje en sí. También sería posible combinar el analizador sintáctico y el evaluador en una sola función, y evaluar durante el análisis sintáctico. Pero dividirlos de esta manera hace que el programa sea más claro y flexible.

    +

    La estructura recursiva de evaluate se asemeja a la estructura del analizador sintáctico, y ambos reflejan la estructura del lenguaje en sí. También sería posible combinar el analizador sintáctico y el evaluador en una sola función, y evaluar durante el análisis sintáctico. Al separarlos de esta manera, el programa es más claro y flexible.

    Esto es realmente todo lo que se necesita para interpretar Egg. Es así de simple. Pero sin definir algunas formas especiales y agregar algunos valores útiles al entorno, todavía no puedes hacer mucho con este lenguaje.

    @@ -197,15 +199,15 @@

    -

    La construcción if de Egg espera exactamente tres argumentos. Evaluará el primero, y si el resultado no es el valor false, evaluará el segundo. De lo contrario, se evaluará el tercero. Esta forma if se asemeja más al operador ternario ?: de JavaScript que al if de JavaScript. Es una expresión, no una declaración, y produce un valor, concretamente, el resultado del segundo o tercer argumento.

    +

    La construcción if de Egg espera exactamente tres argumentos. Evaluará el primero y, si el resultado no es el valor false, evaluará el segundo. De lo contrario, se evaluará el tercero. Esta forma if se asemeja más al operador ternario ?: de JavaScript que al if de JavaScript. Es una expresión, no una declaración, y produce un valor, concretamente, el resultado del segundo o tercer argumento.

    Egg también difiere de JavaScript en cómo maneja el valor de condición para if. No tratará cosas como cero o la cadena vacía como falso, solo el valor preciso false.

    -

    La razón por la que necesitamos representar if como una forma especial, en lugar de una función regular, es que todos los argumentos de las funciones se evalúan antes de llamar a la función, mientras que if debe evaluar solo uno de sus segundos o terceros argumentos, dependiendo del valor del primero.

    +

    La razón por la que necesitamos representar if como una forma especial, en lugar de una función regular, es que todos los argumentos de las funciones se evalúan antes de llamar a la función, mientras que if debe evaluar solo uno de entre su segundo y tercer argumentos, dependiendo del valor del primero.

    La forma while es similar.

    -
    specialForms.while = (args, scope) => {
    +
    specialForms.while = (args, scope) => {
       if (args.length != 2) {
         throw new SyntaxError("Número incorrecto de argumentos para while");
       }
    @@ -214,7 +216,7 @@ 

    // Dado que undefined no existe en Egg, devolvemos false, - // por falta de un resultado significativo. + // para la falta de un resultado con sentido. return false; };

    @@ -228,7 +230,7 @@

    return valor; };

    -

    Para poder crear vinculaciones y darles nuevos valores, también creamos una forma llamada define. Espera una palabra como su primer argumento y una expresión que produzca el valor a asignar a esa palabra como su segundo argumento. Dado que define, al igual que todo, es una expresión, debe devolver un valor. Haremos que devuelva el valor que se asignó (como el operador = de JavaScript).

    +

    Para poder crear asociaciones y darles nuevos valores, también creamos una forma llamada define. Espera una palabra como su primer argumento y una expresión que produzca el valor a asignar a esa palabra como su segundo argumento. Dado que define, al igual que todo, es una expresión, debe devolver un valor. Haremos que devuelva el valor que se asignó (como el operador = de JavaScript).

    specialForms.define = (args, scope) => {
       if (args.length != 2 || args[0].type != "word") {
    @@ -241,7 +243,7 @@ 

    El entorno

    -

    El scope aceptado por evaluate es un objeto con propiedades cuyos nombres corresponden a los nombres de los bindings y cuyos valores corresponden a los valores a los que esos bindings están ligados. Definamos un objeto para representar el scope global.

    +

    El scope aceptado por evaluate es un objeto con propiedades cuyos nombres corresponden a los nombres de las asociaciones y cuyos valores corresponden a los valores a los que esas asociaciones están ligadas. Definamos un objeto para representar el scope global.

    Para poder usar la construcción if que acabamos de definir, necesitamos tener acceso a valores Booleanos. Dado que solo hay dos valores Booleanos, no necesitamos una sintaxis especial para ellos. Simplemente asignamos dos nombres a los valores true y false y los usamos.

    @@ -275,7 +277,7 @@

    return evaluate(parse(program), Object.create(topScope)); }

    -

    Utilizaremos las cadenas de prototipos de objetos para representar ámbitos anidados para que el programa pueda agregar bindings a su ámbito local sin modificar el ámbito de nivel superior.

    +

    Utilizaremos cadenas de prototipos de objetos para representar ámbitos anidados para que el programa pueda agregar asociaciones a su ámbito local sin modificar el ámbito de nivel superior.

    run(`
     do(define(total, 0),
    @@ -287,22 +289,20 @@ 

    `); // → 55

    -

    Este es el programa que hemos visto varias veces antes, que calcula la suma de los números del 1 al 10, expresado en Egg. Es claramente más feo que el equivalente programa en JavaScript, pero no está mal para un lenguaje implementado en menos de 150 líneas de código.

    +

    Este es el programa que hemos visto varias veces antes, que calcula la suma de los números del 1 al 10, expresado en Egg. Es claramente más feo que el programa equivalente en JavaScript, pero no está mal para un lenguaje implementado en menos de 150 líneas de código.

    Funciones

    -

    Un lenguaje de programación sin funciones es un pobre lenguaje de programación.

    - -

    Afortunadamente, no es difícil agregar una construcción fun, que trata su último argumento como el cuerpo de la función y utiliza todos los argumentos anteriores como los nombres de los parámetros de la función.

    +

    Un lenguaje de programación sin funciones es sin lugar a dudas un mal lenguaje de programación. Por suerte, no es difícil agregar una construcción fun, que trata su último argumento como el cuerpo de la función y utiliza todos los argumentos anteriores como los nombres de los parámetros de la función.

    -
    specialForms.fun = (args, scope) => {
    +
    specialForms.fun = (args, scope) => {
       if (!args.length) {
         throw new SyntaxError("Las funciones necesitan un cuerpo");
       }
       let body = args[args.length - 1];
       let params = args.slice(0, args.length - 1).map(expr => {
         if (expr.type != "word") {
    -      throw new SyntaxError("Los nombres de los parámetros deben ser palabras");
    +      throw new SyntaxError("Los nombres de los parámetros deben ser de tipo word");
         }
         return expr.name;
       });
    @@ -340,21 +340,23 @@ 

    Lo que hemos construido es un intérprete. Durante la evaluación, actúa directamente sobre la representación del programa producido por el analizador sintáctico.

    -

    La compilación es el proceso de agregar otro paso entre el análisis sintáctico y la ejecución de un programa, que transforma el programa en algo que puede ser evaluado de manera más eficiente al hacer la mayor cantidad de trabajo posible por adelantado. Por ejemplo, en lenguajes bien diseñados, es obvio, para cada uso de un enlace, a qué enlace se hace referencia, sin ejecutar realmente el programa. Esto se puede utilizar para evitar buscar el enlace por nombre cada vez que se accede, en su lugar, recuperándolo directamente desde una ubicación de memoria predeterminada.

    +

    La compilación es el proceso de agregar otro paso entre el análisis sintáctico y la ejecución de un programa, que transforma el programa en algo que puede ser evaluado de manera más eficiente al hacer la mayor cantidad de trabajo posible por adelantado. Por ejemplo, en lenguajes bien diseñados, para cada uso de una asociación, es obvio a qué asociación se hace referencia, sin tener que buscarla por nombre cada vez que se accede. Esto se puede hacer para evitar buscar la asociación por nombre cada vez que se accede a la misma, recuperando el valor de la asociación directamente desde un lugar predeterminado de la memoria.

    Tradicionalmente, compilar implica convertir el programa a código máquina, el formato en bruto que un procesador de computadora puede ejecutar. Pero cualquier proceso que convierta un programa a una representación diferente se puede considerar como compilación.

    -

    Sería posible escribir una estrategia de evaluación alternativa para Egg, una que primero convierte el programa a un programa JavaScript, usa Function para invocar el compilador de JavaScript en él, y luego ejecuta el resultado. Cuando se hace correctamente, esto haría que Egg se ejecutara muy rápido y aún así fuera bastante simple de implementar.

    +

    Sería posible escribir una estrategia de evaluación alternativa para Egg, una que primero convierte el programa a un programa JavaScript, usa Function para invocar el compilador de JavaScript en él, y luego ejecuta el resultado. Hecho de manera adecuada, esto haría que Egg se ejecutara muy rápido y aún así fuera bastante simple de implementar.

    Si te interesa este tema y estás dispuesto a dedicar tiempo a ello, te animo a intentar implementar ese compilador como ejercicio.

    Haciendo trampa

    -

    Cuando definimos if y while, probablemente notaste que eran envoltorios más o menos triviales alrededor del propio if y while de JavaScript. De manera similar, los valores en Egg son simplemente valores regulares de JavaScript. Cerrar la brecha hacia un sistema más primitivo, como el código máquina que entiende el procesador, requiere más esfuerzo, pero la forma en que funciona se asemeja a lo que estamos haciendo aquí.Aunque el lenguaje de juguete de este capítulo no hace nada que no se pudiera hacer mejor en JavaScript, hay situaciones donde escribir pequeños lenguajes ayuda a realizar trabajos reales.

    +

    Cuando hemos definido if y while, probablemente has notado que eran envoltorios más o menos triviales alrededor de los propios if y while de JavaScript. De manera similar, los valores en Egg son simplemente valores normales de JavaScript. Dar el paso a un sistema más primitivo, como el código máquina que entiende el procesador, requiere mucho más esfuerzo, pero la forma en que funciona se asemeja a lo que estamos haciendo aquí.

    + +

    Aunque el lenguaje de juguete de este capítulo no hace nada que no se pudiera hacer mejor en JavaScript, hay situaciones donde escribir pequeños lenguajes ayuda a sacar adelante trabajo de verdad.

    Tal lenguaje no tiene por qué parecerse a un lenguaje de programación típico. Si JavaScript no viniera equipado con expresiones regulares, por ejemplo, podrías escribir tu propio analizador sintáctico y evaluador para expresiones regulares.

    -

    O imagina que estás construyendo un programa que permite crear rápidamente analizadores sintácticos al proporcionar una descripción lógica del lenguaje que necesitan analizar. Podrías definir una notación específica para eso y un compilador que la convierta en un programa analizador.

    +

    O imagina que estás construyendo un programa que permite crear rápidamente analizadores sintácticos (o parsers) al proporcionar una descripción lógica del lenguaje que necesitan analizar. Podrías definir una notación específica para eso y un compilador que la convierta en un programa analizador.

    expr = número | cadena | nombre | aplicación
     
    @@ -398,7 +400,7 @@ 

    La forma más sencilla de hacer esto es representar los arrays de Egg con arrays de JavaScript.

    -

    Los valores añadidos al ámbito superior deben ser funciones. Al usar un argumento restante (con la notación de triple punto), la definición de array puede ser muy simple.

    +

    Los valores añadidos al ámbito superior deben ser funciones. Al usar un argumento rest (restante, es decir, con la notación de triple punto), la definición de array puede ser muy simple.

    @@ -406,7 +408,7 @@

    La forma en que hemos definido fun permite que las funciones en Egg hagan referencia al ámbito circundante, lo que permite que el cuerpo de la función use valores locales que eran visibles en el momento en que se definió la función, al igual que lo hacen las funciones de JavaScript.

    -

    El siguiente programa ilustra esto: la función f devuelve una función que suma su argumento al argumento de f, lo que significa que necesita acceder al ámbito local dentro de f para poder usar la vinculación a.

    +

    El siguiente programa ilustra esto: la función f devuelve una función que suma su argumento al argumento de f, lo que significa que necesita acceder al ámbito local dentro de f para poder usar la asociación a.

    run(`
     do(define(f, fun(a, fun(b, +(a, b)))),
    @@ -414,19 +416,19 @@ 

    `); // → 9

    -

    Vuelve a la definición del formulario fun y explica qué mecanismo hace que esto funcione.

    +

    Vuelve a la definición de la forma fun y explica qué mecanismo hace que esto funcione.

    Mostrar pistas...
    -

    Una vez más, estamos montando un mecanismo en JavaScript para obtener la característica equivalente en Egg. Los formularios especiales reciben el ámbito local en el que se evalúan para que puedan evaluar sus subformas en ese ámbito. La función devuelta por fun tiene acceso al argumento scope dado a su función contenedora y lo utiliza para crear el ámbito local de la función cuando se llama.

    +

    Una vez más, estamos montando un mecanismo en JavaScript para obtener la característica equivalente en Egg. Las formas especiales reciben el ámbito local en el que se evalúan para que puedan evaluar sus subformas en ese ámbito. La función devuelta por fun tiene acceso al argumento scope dado a su función contenedora y lo utiliza para crear el ámbito local de la función cuando se llama.

    -

    Esto significa que el prototipo del ámbito local será el ámbito en el cual la función fue creada, lo que hace posible acceder a los enlaces en ese ámbito desde la función. Esto es todo lo que se necesita para implementar el cierre (aunque para compilarlo de una manera realmente eficiente, sería necesario hacer un poco más de trabajo).

    +

    Esto significa que el prototipo del ámbito local será el ámbito en el cual la función fue creada, lo que hace posible acceder a los enlaces en ese ámbito desde la función. Esto es todo lo que se necesita para implementar la clausura (aunque para compilarlo de una manera realmente eficiente, sería necesario hacer un poco más de trabajo).

    Comentarios

    -

    Sería bueno si pudiéramos escribir comentarios en Egg. Por ejemplo, siempre que encontremos un signo de almohadilla (#), podríamos tratar el resto de la línea como un comentario y ignorarlo, similar a // en JavaScript.

    +

    Sería bueno si pudiéramos escribir comentarios en Egg. Por ejemplo, siempre que encontremos un signo de almohadilla (#), podríamos tratar el resto de la línea como un comentario e ignorarlo, como con // en JavaScript.

    No tenemos que hacer grandes cambios en el analizador para admitir esto. Simplemente podemos cambiar skipSpace para omitir comentarios como si fueran espacios en blanco de manera que todos los puntos donde se llama a skipSpace ahora también omitirán comentarios. Realiza este cambio.

    @@ -449,17 +451,17 @@

    Asegúrate de que tu solución maneje múltiples comentarios seguidos, con posiblemente espacios en blanco entre ellos o después de ellos.

    -

    Una expresión regular es probablemente la forma más sencilla de resolver esto. Escribe algo que coincida con “espacio en blanco o un comentario, cero o más veces”. Utiliza el método exec o match y observa la longitud del primer elemento en la matriz devuelta (la coincidencia completa) para averiguar cuántos caracteres cortar.

    +

    Para resolver esto, la forma más sencilla es probablemente usar alguna expresión regular. Escribe algo que coincida con “espacio en blanco o un comentario, cero o más veces”. Utiliza el método exec o match y observa la longitud del primer elemento en la matriz devuelta (la coincidencia completa) para averiguar cuántos caracteres cortar.

    Corrigiendo el ámbito

    -

    Actualmente, la única forma de asignar un enlace un valor es define. Esta construcción actúa como una forma tanto de definir nuevos enlaces como de dar un nuevo valor a los existentes.

    +

    Actualmente, la única forma de asignar un valor a una asociación es usar define. Esta construcción actúa como una forma tanto de definir nuevos enlaces como de dar un nuevo valor a los existentes.

    Esta ambigüedad causa un problema. Cuando intentas darle un nuevo valor a un enlace no local, terminarás definiendo uno local con el mismo nombre en su lugar. Algunos lenguajes funcionan de esta manera por diseño, pero siempre he encontrado que es una forma incómoda de manejar el ámbito.

    -

    Agrega una forma especial set, similar a define, que da un nuevo valor a un enlace, actualizando el enlace en un ámbito exterior si aún no existe en el ámbito interior. Si el enlace no está definido en absoluto, lanza un ReferenceError (otro tipo de error estándar).

    +

    Agrega una forma especial set, similar a define, que da un nuevo valor a una asociación, actualizando la asociación en un ámbito exterior si aún no existe en el ámbito interior. Si la asociación no está definida, lanza un ReferenceError (otro tipo de error estándar).

    La técnica de representar los ámbitos como objetos simples, que hasta ahora ha sido conveniente, te causará un pequeño problema en este punto. Es posible que desees usar la función Object.getPrototypeOf, la cual devuelve el prototipo de un objeto. También recuerda que puedes utilizar Object.hasOwn para verificar si un objeto dado tiene una propiedad.

    From c27f01968414ee107efee13cb68309dd2719c1f8 Mon Sep 17 00:00:00 2001 From: ckdvk Date: Thu, 20 Feb 2025 20:48:58 +0800 Subject: [PATCH 24/36] =?UTF-8?q?revisado=20cap=C3=ADtulo=2013?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 13_browser.md | 74 ++++++++++++++++++++++---------------------- html/13_browser.html | 74 ++++++++++++++++++++++---------------------- 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/13_browser.md b/13_browser.md index 154b19e7..2ceb2118 100644 --- a/13_browser.md +++ b/13_browser.md @@ -1,8 +1,8 @@ # JavaScript y el Navegador -{{quote {author: "Tim Berners-Lee", title: "La World Wide Web: Una historia personal muy breve", chapter: true} +{{quote {author: "Tim Berners-Lee", title: "The World Wide Web: A very short personal history", chapter: true} -El sueño detrás de la Web es de un espacio de información común en el que nos comunicamos compartiendo información. Su universalidad es esencial: el hecho de que un enlace de hipertexto pueda apuntar a cualquier cosa, ya sea personal, local o global, ya sea un borrador o altamente pulido. +El sueño detrás de la Web es el de un espacio de información común en el que nos comunicamos compartiendo información. Su universalidad es esencial: el hecho de que un enlace de hipertexto pueda apuntar a cualquier cosa, ya sea personal, local o global, ya sea un borrador o algo muy trabajado. quote}} @@ -14,15 +14,15 @@ Los próximos capítulos de este libro hablarán sobre los navegadores web. Sin {{index "descentralización", compatibilidad}} -La tecnología web ha sido descentralizada desde el principio, no solo técnicamente, sino también en la forma en que evolucionó. Varios fabricantes de navegadores han añadido nueva funcionalidad de manera ad hoc y a veces sin mucho sentido, que luego, a veces, terminaba siendo adoptada por otros, y finalmente establecida como en los ((estándares)). +La tecnología web ha sido descentralizada desde el principio, no solo técnicamente, sino también en la forma en que evolucionó. Varios desarrolladores de navegadores han añadido nuevas funcionalidades de manera ad hoc y a veces sin mucho sentido, que luego, a veces, han terminado siendo adoptadas por otros, y finalmente establecidas como en los ((estándares)). -Esto es a la vez una bendición y una maldición. Por un lado, es empoderador no tener a una parte central controlando un sistema, sino mejorando con la contribución de diferentes partes que trabajan en una ((colaboración)) laxa (o a veces en abierta hostilidad). Por otro lado, la forma caótica en que se desarrolló la Web significa que el sistema resultante no es precisamente un ejemplo brillante de ((coherencia)) interna. Algunas partes son directamente confusas y están mal diseñadas. +Esto es a la vez una bendición y una maldición. Por un lado, es empoderador no tener a nadie controlando un sistema, sino mejorando con la contribución de diferentes grupos que trabajan en una ((colaboración)) laxa (o a veces en abierta hostilidad). Por otro lado, la forma caótica en que se desarrolló la Webha llevado a que el sistema resultante no sea precisamente un ejemplo brillante de ((coherencia)) interna. Algunas partes son directamente confusas y están mal diseñadas. -## Redes y el Internet +## Redes y la Internet -Las ((redes)) de computadoras existen desde la década de 1950. Si conectas cables entre dos o más computadoras y les permites enviar datos de ida y vuelta a través de estos cables, puedes hacer todo tipo de cosas maravillosas. +Las ((redes)) de computadoras existen desde la década de 1950. Si conectas cables entre dos o más computadoras y les permites enviar datos de ida y vuelta a través de estos cables, puedes hacer todo tipo de maravillas. -Y si conectar dos máquinas en el mismo edificio nos permite hacer cosas maravillosas, conectar máquinas en todo el planeta debería ser aún mejor. La tecnología para comenzar a implementar esta visión se desarrolló en la década de 1980, y la red resultante se llama el _((Internet))_. Ha cumplido su promesa. +Y si conectar dos máquinas en el mismo edificio nos permite hacer cosas maravillosas, conectar máquinas en todo el planeta debería ser aún mejor. La tecnología para comenzar a implementar esta visión se desarrolló en la década de 1980, y la red resultante se llama la (o el) _((Internet))_. Ha cumplido su promesa. Una computadora puede usar esta red para enviar bits a otra computadora. Para que surja una comunicación efectiva de este envío de bits, las computadoras en ambos extremos deben saber qué se supone que representan los bits. El significado de cualquier secuencia dada de bits depende enteramente del tipo de cosa que está tratando de expresar y del mecanismo de ((codificación)) utilizado. @@ -58,33 +58,33 @@ Otra computadora puede establecer entonces una conexión conectándose a la máq {{index ["abstracción", "de la red"]}} -Dicha conexión actúa como un conducto bidireccional a través del cual pueden fluir los bits: las máquinas en ambos extremos pueden insertar datos en él. Una vez que los bits se transmiten con éxito, pueden volver a ser leídos por la máquina del otro lado. Este es un modelo conveniente. Se podría decir que ((TCP)) proporciona una abstracción de la red. +Dicha conexión actúa como un conducto bidireccional a través del cual pueden fluir los bits: las máquinas en ambos extremos pueden insertar datos en él. Una vez que los bits se transmiten con éxito, pueden ser leídos por la máquina del otro lado. Este es un modelo muy cómodo. Se podría decir que ((TCP)) proporciona una abstracción de la red. {{id web}} ## La Web -El _((World Wide Web))_ (no se debe confundir con el ((Internet)) en su totalidad) es un conjunto de ((protocolo))s y formatos que nos permiten visitar páginas web en un navegador. La parte "Web" en el nombre se refiere al hecho de que estas páginas pueden enlazarse fácilmente entre sí, conectándose así en una gran ((malla)) por la que los usuarios pueden moverse. +La _((World Wide Web))_ (no se debe confundir con la ((Internet)) en su totalidad) es un conjunto de ((protocolo))s y formatos que nos permiten visitar páginas web en un navegador. La parte "Web" en el nombre se refiere al hecho de que estas páginas pueden enlazarse fácilmente entre sí, conectándose así todas en una gran ((malla)) por la que los usuarios pueden moverse. -Para formar parte de la Web, todo lo que necesitas hacer es conectar una máquina al ((Internet)) y hacer que escuche en el puerto 80 con el protocolo ((HTTP)) para que otras computadoras puedan solicitarle documentos. +Para formar parte de la Web, todo lo que necesitas hacer es conectar una máquina a ((Internet)) y hacer que escuche en el puerto 80 con el protocolo ((HTTP)) para que otras computadoras puedan solicitarle documentos. {{index URL}} {{indexsee "Uniform Resource Locator", URL}} -Cada ((documento)) en la Web está nombrado por un _Localizador de Recursos Uniforme_ (URL), que se ve algo así: +Cada ((documento)) en la Web está nombrado por un _Localizador de Recursos Uniforme_ (URL), que tiene un aspecto como este: ```{lang: null} - http://eloquentjavascript.net/13_browser.html - | | | | - protocol servidor ruta + https://eloquentjavascript.es/13_browser.html + | | | | + protocolo servidor ruta ``` {{index HTTPS}} -La primera parte nos dice que esta URL utiliza el protocolo HTTP (en contraposición, por ejemplo, a HTTP cifrado, que sería _https://_). Luego viene la parte que identifica desde qué servidor estamos solicitando el documento. Por último está una cadena de ruta que identifica el documento específico (o _((recurso))_) en el que estamos interesados. +La primera parte nos dice que esta URL utiliza el protocolo HTTP cifrado (en contraposición, por ejemplo, a HTTP, que sería solamente _http://_). Luego viene la parte que identifica a qué servidor estamos solicitando el documento. Por último está una cadena de ruta que identifica el documento específico (o _((recurso))_) en el que estamos interesados. -Las máquinas conectadas a Internet tienen una _((dirección IP))_, que es un número que se puede utilizar para enviar mensajes a esa máquina, y se ve algo así como `149.210.142.219` o `2001:4860:4860::8888`. Pero las listas de números más o menos aleatorios son difíciles de recordar y complicados de escribir, así que en su lugar puedes registrar un _((nombre de dominio))_ para una dirección específica o un conjunto de direcciones. Registré _eloquentjavascript.net_ para apuntar a la dirección IP de una máquina que controlo y, por lo tanto, puedo usar ese nombre de dominio para servir páginas web. +Las máquinas conectadas a Internet tienen una _((dirección IP))_, que es un número que se puede utilizar para enviar mensajes a esa máquina, y tiene un aspecto como `149.210.142.219` o `2001:4860:4860::8888`. Como unas listas de números medio aleatorios son difíciles de recordar y complicadas de escribir, en su lugar puedes registrar un _((nombre de dominio))_ para una dirección específica o un conjunto de direcciones. Registré _eloquentjavascript.es para apuntar a la dirección IP de una máquina que controlo y, por lo tanto, puedo usar ese nombre de dominio para servir páginas web. {{index browser}} @@ -98,7 +98,7 @@ Si escribes esta URL en la barra de direcciones de tu navegador, el navegador in HTML, que significa _Lenguaje de Marcado de Hipertexto_, es el formato de documento utilizado para páginas web. Un documento HTML contiene ((texto)), así como _((etiqueta))s_ que estructuran el texto, describiendo cosas como enlaces, párrafos y encabezados. -Un documento HTML corto podría lucir así: +Un documento HTML corto podría tener esta pinta: ```{lang: "html"} @@ -130,15 +130,15 @@ Las etiquetas, encerradas en ((corchetes angulares)) (`<` y `>`, los símbolos d {{index doctype, "versión"}} -El documento comienza con ``, lo que indica al navegador interpretar la página como HTML _moderno_, en contraposición a estilos obsoletos que se utilizaban en el pasado. +El documento comienza con ``, lo que le dice al navegador que interprete la página como HTML _moderno_, en contraposición a estilos obsoletos que se utilizaban en el pasado. {{index "head (etiqueta HTML)", "body (etiqueta HTML)", "title (etiqueta HTML)", "h1 (etiqueta HTML)", "p (etiqueta HTML)"}} -Los documentos HTML tienen una cabecera y un cuerpo. La cabecera contiene información _sobre_ el documento, y el cuerpo contiene el documento en sí. En este caso, la cabecera declara que el título de este documento es "Mi página de inicio" y que utiliza la codificación UTF-8, que es una forma de codificar texto Unicode como datos binarios. El cuerpo del documento contiene un encabezado (`

    `, que significa "encabezado 1" —`

    ` a `

    ` producen subencabezados) y dos ((párrafo))s (`

    `). +Los documentos HTML tienen una cabecera y un cuerpo. La cabecera contiene información _sobre_ el documento, y el cuerpo contiene el documento en sí. En este caso, la cabecera declara que el título de este documento es "Mi página de inicio" y que utiliza la codificación UTF-8, que es una forma de codificar texto Unicode como datos binarios. El cuerpo del documento contiene un encabezado (`

    `, que significa "encabezado 1" —las etiquetas `

    ` a `

    ` producen subencabezados—) y dos ((párrafo))s (`

    `). {{index "atributo href", "a (etiqueta HTML)"}} -Las etiquetas vienen en varias formas. Un ((elemento)), como el cuerpo, un párrafo o un enlace, comienza con una _((etiqueta de apertura))_ como `

    ` y finaliza con una _((etiqueta de cierre))_ como `

    `. Algunas etiquetas de apertura, como la de ((enlace)) (``), contienen información adicional en forma de pares `nombre="valor"`. Estos se llaman _((atributo))s_. En este caso, el destino del enlace se indica con `href="http://eloquentjavascript.net"`, donde `href` significa "hipervínculo de referencia". +Las etiquetas vienen en varias formas. Un ((elemento)), como el cuerpo, un párrafo o un enlace, comienza con una _((etiqueta de apertura))_ como `

    ` y finaliza con una _((etiqueta de cierre))_ como `

    `. Algunas etiquetas de apertura, como la de ((enlace)) (`
    `), contienen información adicional en forma de pares `nombre="valor"`. Estos se llaman _((atributo))s_. En este caso, el destino del enlace se indica con `href="https://eloquentjavascript.es"`, donde `href` significa "hipervínculo de referencia". {{index "atributo src", "etiqueta auto-cerrante", "img (etiqueta HTML)"}} @@ -146,11 +146,11 @@ Algunos tipos de ((etiqueta))s no contienen nada y por lo tanto no necesitan ser {{index [escape, "en HTML"]}} -Para poder incluir ((corchetes angulares)) en el texto de un documento, a pesar de que tienen un significado especial en HTML, se debe introducir otra forma especial de notación. Un simple signo menor que se escribe como `<` ("menor que"), y un signo mayor que se escribe como `>` ("mayor que"). En HTML, un carácter y comercial (`&`) seguido de un nombre o código de carácter y un punto y coma (`;`) se llama una _((entidad))_ y será reemplazado por el carácter que codifica. +Para poder incluir ((corchetes angulares)) en el texto de un documento, a pesar de que tienen un significado especial en HTML, se debe introducir otra forma especial de notación. Un simple signo de menor que se escribe `<`, y un signo mayor que se escribe `>`. En HTML, un carácter _et_ (es decir, el carácter `&`, también conocido en inglés y en general en informática como _ampersand_) seguido de un nombre o código de carácter y un punto y coma (`;`), se llama _((entidad))_, y será reemplazada por el carácter que codifica. {{index ["caracter barra invertida", "en cadenas de texto"], "caracter y comercial", "caracter de comillas dobles"}} -Esto es análogo a la manera en que se utilizan las barras invertidas en las cadenas de texto de JavaScript. Dado que este mecanismo también otorga un significado especial a los caracteres de y comercial, necesitan ser escapados como `&`. Dentro de los valores de los atributos, que están entre comillas dobles, se puede usar `"` para insertar un carácter de comillas real. +Esto es análogo a la manera en que se utilizan las barras invertidas en las cadenas de texto de JavaScript. Dado que este mecanismo también da un significado especial a los caracteres de ampersand, estos necesitan ser escapados como `&`. Dentro de los valores de los atributos, que están entre comillas dobles, se puede usar `"` para insertar un carácter de comillas real. {{index "tolerancia a errores", "análisis sintáctico"}} @@ -174,11 +174,11 @@ El siguiente documento será tratado igual que el que se mostró anteriormente: Las etiquetas ``, `` y `` han desaparecido por completo. El navegador sabe que `` y `` pertenecen a la cabecera y que `<h1>` significa que el cuerpo ha comenzado. Además, ya no cierro explícitamente los párrafos, ya que abrir un nuevo párrafo o finalizar el documento los cerrará implícitamente. Las comillas alrededor de los valores de los atributos también han desaparecido. -Este libro generalmente omitirá las etiquetas `<html>`, `<head>` y `<body>` en ejemplos para mantenerlos cortos y libres de desorden. Pero _sí_ cerraré las etiquetas e incluiré comillas alrededor de los atributos. +Este libro generalmente omitirá las etiquetas `<html>`, `<head>` y `<body>` en ejemplos para mantenerlos cortos y ordenados. Pero _sí_ cerraré las etiquetas e incluiré comillas alrededor de los atributos. {{index navegador}} -También generalmente omitiré el ((doctype)) y la declaración `charset`. Esto no debe interpretarse como una recomendación para omitirlos de documentos HTML. Los navegadores a menudo hacen cosas ridículas cuando los olvidas. Deberías considerar que el doctype y los metadatos del `charset` están implícitamente presentes en los ejemplos, incluso cuando no se muestran realmente en el texto. +También omitiré generalmente el ((doctype)) y la declaración `charset`. Esto no debe interpretarse como una recomendación para omitirlos de documentos HTML. Los navegadores a menudo hacen cosas ridículas cuando los olvidas. Deberías considerar que el doctype y los metadatos del `charset` están implícitamente presentes en los ejemplos, incluso cuando no se muestran realmente en el texto. {{id script_tag}} @@ -195,18 +195,18 @@ En el contexto de este libro, la etiqueta HTML más importante es `<script>`. Es {{index "función alert", "cronología"}} -Dicho script se ejecutará tan pronto como su etiqueta `<script>` sea encontrada mientras el navegador lee el HTML. Esta página mostrará un cuadro de diálogo al abrirla—la función `alert` se asemeja a `prompt`, en que muestra una ventana pequeña, pero solo muestra un mensaje sin solicitar entrada. +Dicho script se ejecutará tan pronto como su etiqueta `<script>` sea encontrada mientras el navegador lee el HTML. Esta página mostrará un cuadro de diálogo al abrirla —la función `alert` se asemeja a `prompt` en que muestra una ventana pequeña, pero solo muestra un mensaje sin solicitar entrada. {{index "atributo src"}} -Incluir programas extensos directamente en documentos HTML a menudo es poco práctico. La etiqueta `<script>` puede recibir un atributo `src` para obtener un archivo de script (un archivo de texto que contiene un programa JavaScript) desde una URL. +Incluir programas extensos directamente en documentos HTML a menudo resulta poco práctico. La etiqueta `<script>` puede recibir un atributo `src` para obtener un archivo de script (un archivo de texto que contiene un programa JavaScript) desde una URL. ```{lang: "html"} <h1>Probando alerta</h1> <script src="code/hello.js"></script> ``` -El archivo _code/hello.js_ incluido aquí contiene el mismo programa—`alert("¡hola!")`. Cuando una página HTML referencia otras URL como parte de sí misma—por ejemplo, un archivo de imagen o un script—los navegadores web los recuperarán inmediatamente e incluirán en la página. +El archivo _code/hello.js_ incluido aquí contiene el mismo programa —`alert("¡hola!")`— que vimos antes. Cuando una página HTML referencia otras URL como parte de sí misma —por ejemplo, un archivo de imagen o un script— los navegadores web los recuperarán inmediatamente e incluirán en la página. {{index "script (etiqueta HTML)", "etiqueta de cierre"}} @@ -226,25 +226,25 @@ Algunos atributos también pueden contener un programa JavaScript. La etiqueta ` {{index "carácter de comilla simple", [escape, "en HTML"]}} -Nota que tuve que utilizar comillas simples para el string en el atributo `onclick` porque las comillas dobles ya se usan para citar todo el atributo. También podría haber utilizado `"`. +Fíjate en que he tenido que utilizar comillas simples para el string en el atributo `onclick` porque las comillas dobles ya se usan para citar todo el atributo. También podría haber utilizado `"`. -## En el entorno controlado +## En el sandbox {{index "script malicioso", "World Wide Web", navegador, sitio web, seguridad}} -Ejecutar programas descargados de ((Internet)) es potencialmente peligroso. No sabes mucho sobre las personas detrás de la mayoría de los sitios que visitas, y no necesariamente tienen buenas intenciones. Ejecutar programas de personas que no tienen buenas intenciones es cómo se infecta tu computadora con ((virus)), te roban tus datos y hackean tus cuentas. +Ejecutar programas descargados de ((Internet)) es potencialmente peligroso. No sabes mucho sobre la gente detrás de la mayoría de los sitios que visitas, y no necesariamente tienen buenas intenciones. Ejecutar programas de gente que no tienen buenas intenciones es la manera en que se infecta tu computadora con ((virus)), te roban tus datos y hackean tus cuentas. -Sin embargo, la atracción de la Web es que puedes navegar por ella sin necesariamente confiar en todas las páginas que visitas. Por eso, los navegadores limitan severamente las cosas que un programa JavaScript puede hacer: no puede ver los archivos en tu computadora ni modificar nada que no esté relacionado con la página web en la que estaba incrustado. +Sin embargo, la gracia de la Web es que puedes navegar por ella sin necesariamente confiar en todas las páginas que visitas. Por eso, los navegadores limitan severamente las cosas que un programa JavaScript puede hacer: no puede ver los archivos en tu computadora ni modificar nada que no esté relacionado con la página web en la que estaba incrustado. {{index sandboxing}} -Aislar un entorno de programación de esta manera se llama _((sandboxing))_, la idea es que el programa está jugando inofensivamente en un arenero. Pero debes imaginar este tipo particular de arenero como teniendo una jaula de barras de acero gruesas sobre él para que los programas que juegan en él no puedan salir realmente. +Aislar un entorno de programación de esta manera se llama _((sandboxing))_, la idea es que el programa está jugando inofensivamente en un arenero. Pero debes imaginar este tipo particular de arenero como uno que tiene una jaula de barrotes de acero bien gruesas sobre él para que los programas que juegan en él de verdad no puedan salir. -La parte difícil del sandboxing es permitir que los programas tengan suficiente espacio para ser útiles y al mismo tiempo restringirlos para que no hagan nada peligroso. Muchas funcionalidades útiles, como comunicarse con otros servidores o leer el contenido del ((portapapeles)), también pueden usarse para hacer cosas problemáticas que invaden la ((privacidad)). +La parte difícil del sandboxing es permitir que los programas tengan suficiente espacio para ser útiles y, al mismo tiempo, restringirlos lo suficiente para que no hagan nada peligroso. Muchas funcionalidades útiles, como comunicarse con otros servidores o leer el contenido del ((portapapeles)), también pueden usarse para hacer cosas problemáticas que invaden la ((privacidad)). {{index fuga, exploit, seguridad}} -De vez en cuando, alguien encuentra una nueva forma de evitar las limitaciones de un ((navegador)) y hacer algo dañino, que va desde filtrar información privada menor hasta tomar el control de toda la máquina en la que se ejecuta el navegador. Los desarrolladores de navegadores responden reparando el agujero, y todo vuelve a estar bien, hasta que se descubre el próximo problema, y con suerte se publicita, en lugar de ser explotado en secreto por alguna agencia gubernamental u organización criminal. +De vez en cuando, alguien encuentra una nueva forma de evitar las limitaciones de un ((navegador)) y hacer algo dañino, que va desde filtrar información privada no demasiado relevante hasta tomar el control de toda la máquina en la que se ejecuta el navegador. Los desarrolladores de navegadores responden reparando el agujero, y todo vuelve a estar bien, hasta que se descubre el próximo problema (y con suerte se publica, en lugar de ser explotado en secreto por alguna agencia gubernamental u organización criminal). ## Compatibilidad y las guerras de navegadores @@ -252,12 +252,12 @@ De vez en cuando, alguien encuentra una nueva forma de evitar las limitaciones d En las etapas iniciales de la Web, un navegador llamado ((Mosaic)) dominaba el mercado. Después de unos años, el equilibrio se desplazó a ((Netscape)), que a su vez fue en gran medida reemplazado por ((Internet Explorer)) de Microsoft. En cualquier punto en el que un único ((navegador)) era dominante, el fabricante de ese navegador se creía con derecho a inventar nuevas funciones para la Web unilateralmente. Dado que la mayoría de usuarios usaban el navegador más popular, los ((sitio web))s simplemente comenzaban a usar esas características, sin importar los otros navegadores. -Esta fue la era oscura de la ((compatibilidad)), a menudo llamada las _((guerras de navegadores))_. Los desarrolladores web se quedaron con no una Web unificada, sino dos o tres plataformas incompatibles. Para empeorar las cosas, los navegadores en uso alrededor de 2003 estaban llenos de ((error))es, y por supuesto los errores eran diferentes para cada ((navegador)). La vida era difícil para las personas que escribían páginas web. +Esta fue la era oscura de la ((compatibilidad)), a menudo llamada la _((guerra de navegadores))_. Los desarrolladores web se quedaron con no una Web unificada, sino dos o tres plataformas incompatibles. Para empeorar las cosas, los navegadores en uso alrededor de 2003 estaban llenos de ((error))es y, por supuesto los errores eran diferentes para cada ((navegador)). La vida era difícil para las personas que escribían páginas web. {{index Apple, "Internet Explorer", Mozilla}} -Mozilla ((Firefox)), un derivado sin ánimo de lucro de ((Netscape)), desafió la posición de Internet Explorer a finales de la década de 2000. Debido a que ((Microsoft)) no estaba particularmente interesado en mantenerse competitivo en ese momento, Firefox le quitó mucho cuota de mercado. Alrededor del mismo tiempo, ((Google)) introdujo su navegador ((Chrome)) y el navegador de Apple ((Safari)) ganó popularidad, lo que llevó a una situación en la que había cuatro actores principales, en lugar de uno solo. +Mozilla ((Firefox)), un derivado sin ánimo de lucro de ((Netscape)), desafió la posición de Internet Explorer a finales de la década de 2000. Como ((Microsoft)) no estaba particularmente interesado en mantenerse competitivo en ese momento, Firefox le quitó mucho cuota de mercado. Alrededor del mismo tiempo, ((Google)) introdujo su navegador ((Chrome)) y el navegador de Apple ((Safari)) ganó popularidad, lo que llevó a una situación en la que había cuatro actores principales, en lugar de uno solo. {{index compatibilidad}} -Los nuevos actores tenían una actitud más seria hacia los ((estándares)) y mejores prácticas de ((ingeniería)), lo que nos dio menos incompatibilidad y menos ((error))es. Microsoft, viendo cómo su cuota de mercado se desmoronaba, adoptó estas actitudes en su navegador Edge, que reemplaza a Internet Explorer. Si estás empezando a aprender desarrollo web hoy, considérate afortunado. Las últimas versiones de los principales navegadores se comportan de manera bastante uniforme y tienen relativamente pocos errores.Desafortunadamente, con la disminución constante de la cuota de mercado de Firefox y Edge convirtiéndose en simplemente un contenedor alrededor del núcleo de Chrome en 2018, esta uniformidad podría una vez más tomar la forma de un único proveedor —Google en este caso— teniendo el suficiente control sobre el mercado de navegadores para imponer su idea de cómo debería lucir la Web al resto del mundo. \ No newline at end of file +Los nuevos actores tenían una actitud más seria hacia los ((estándares)) y mejores prácticas de ((ingeniería)), lo que nos dio menos incompatibilidad y menos ((error))es. Microsoft, viendo cómo su cuota de mercado se desmoronaba, adoptó estas actitudes en su navegador Edge, que reemplaza a Internet Explorer. Si estás empezando a aprender desarrollo web hoy, considérate afortunado. Las últimas versiones de los principales navegadores se comportan de manera bastante uniforme y tienen relativamente pocos errores.Desafortunadamente, con la disminución constante de la cuota de mercado de Firefox y Edge convirtiéndose en simplemente un contenedor alrededor del núcleo de Chrome en 2018, esta uniformidad podría una vez más tomar la forma de un único proveedor —Google en este caso— teniendo el suficiente control sobre el mercado de navegadores para imponer su idea de cómo debería ser la Web al resto del mundo. \ No newline at end of file diff --git a/html/13_browser.html b/html/13_browser.html index c416bdd9..064daa18 100644 --- a/html/13_browser.html +++ b/html/13_browser.html @@ -14,23 +14,23 @@ <h1>JavaScript y el Navegador</h1> <blockquote> -<p><a class="p_ident" id="p-x5Ki4lNe1x" href="#p-x5Ki4lNe1x" tabindex="-1" role="presentation"></a>El sueño detrás de la Web es de un espacio de información común en el que nos comunicamos compartiendo información. Su universalidad es esencial: el hecho de que un enlace de hipertexto pueda apuntar a cualquier cosa, ya sea personal, local o global, ya sea un borrador o altamente pulido.</p> +<p><a class="p_ident" id="p-v2AVa9KTtK" href="#p-v2AVa9KTtK" tabindex="-1" role="presentation"></a>El sueño detrás de la Web es el de un espacio de información común en el que nos comunicamos compartiendo información. Su universalidad es esencial: el hecho de que un enlace de hipertexto pueda apuntar a cualquier cosa, ya sea personal, local o global, ya sea un borrador o algo muy trabajado.</p> -<footer>Tim Berners-Lee, <cite>La World Wide Web: Una historia personal muy breve</cite></footer> +<footer>Tim Berners-Lee, <cite>The World Wide Web: A very short personal history</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_13.jpg" alt="Ilustración que muestra una central telefónica"></figure> <p><a class="p_ident" id="p-5mbmb1PwCH" href="#p-5mbmb1PwCH" tabindex="-1" role="presentation"></a>Los próximos capítulos de este libro hablarán sobre los navegadores web. Sin los navegadores web, no habría JavaScript. O incluso si existiera, nadie le habría prestado atención.</p> -<p><a class="p_ident" id="p-an+O4Rstt4" href="#p-an+O4Rstt4" tabindex="-1" role="presentation"></a>La tecnología web ha sido descentralizada desde el principio, no solo técnicamente, sino también en la forma en que evolucionó. Varios fabricantes de navegadores han añadido nueva funcionalidad de manera ad hoc y a veces sin mucho sentido, que luego, a veces, terminaba siendo adoptada por otros, y finalmente establecida como en los estándares.</p> +<p><a class="p_ident" id="p-an+O4Rstt4" href="#p-an+O4Rstt4" tabindex="-1" role="presentation"></a>La tecnología web ha sido descentralizada desde el principio, no solo técnicamente, sino también en la forma en que evolucionó. Varios desarrolladores de navegadores han añadido nuevas funcionalidades de manera ad hoc y a veces sin mucho sentido, que luego, a veces, han terminado siendo adoptadas por otros, y finalmente establecidas como en los estándares.</p> -<p><a class="p_ident" id="p-MDcWfub6W4" href="#p-MDcWfub6W4" tabindex="-1" role="presentation"></a>Esto es a la vez una bendición y una maldición. Por un lado, es empoderador no tener a una parte central controlando un sistema, sino mejorando con la contribución de diferentes partes que trabajan en una colaboración laxa (o a veces en abierta hostilidad). Por otro lado, la forma caótica en que se desarrolló la Web significa que el sistema resultante no es precisamente un ejemplo brillante de coherencia interna. Algunas partes son directamente confusas y están mal diseñadas.</p> +<p><a class="p_ident" id="p-MDcWfub6W4" href="#p-MDcWfub6W4" tabindex="-1" role="presentation"></a>Esto es a la vez una bendición y una maldición. Por un lado, es empoderador no tener a nadie controlando un sistema, sino mejorando con la contribución de diferentes grupos que trabajan en una colaboración laxa (o a veces en abierta hostilidad). Por otro lado, la forma caótica en que se desarrolló la Webha llevado a que el sistema resultante no sea precisamente un ejemplo brillante de coherencia interna. Algunas partes son directamente confusas y están mal diseñadas.</p> -<h2><a class="h_ident" id="h-K5HX9aqefS" href="#h-K5HX9aqefS" tabindex="-1" role="presentation"></a>Redes y el Internet</h2> +<h2><a class="h_ident" id="h-PKMwESXLxU" href="#h-PKMwESXLxU" tabindex="-1" role="presentation"></a>Redes y la Internet</h2> -<p><a class="p_ident" id="p-fLCU4ZZZrA" href="#p-fLCU4ZZZrA" tabindex="-1" role="presentation"></a>Las redes de computadoras existen desde la década de 1950. Si conectas cables entre dos o más computadoras y les permites enviar datos de ida y vuelta a través de estos cables, puedes hacer todo tipo de cosas maravillosas.</p> +<p><a class="p_ident" id="p-ELjy/6a7mg" href="#p-ELjy/6a7mg" tabindex="-1" role="presentation"></a>Las redes de computadoras existen desde la década de 1950. Si conectas cables entre dos o más computadoras y les permites enviar datos de ida y vuelta a través de estos cables, puedes hacer todo tipo de maravillas.</p> -<p><a class="p_ident" id="p-N/GvOwwucp" href="#p-N/GvOwwucp" tabindex="-1" role="presentation"></a>Y si conectar dos máquinas en el mismo edificio nos permite hacer cosas maravillosas, conectar máquinas en todo el planeta debería ser aún mejor. La tecnología para comenzar a implementar esta visión se desarrolló en la década de 1980, y la red resultante se llama el <em>Internet</em>. Ha cumplido su promesa.</p> +<p><a class="p_ident" id="p-N/GvOwwucp" href="#p-N/GvOwwucp" tabindex="-1" role="presentation"></a>Y si conectar dos máquinas en el mismo edificio nos permite hacer cosas maravillosas, conectar máquinas en todo el planeta debería ser aún mejor. La tecnología para comenzar a implementar esta visión se desarrolló en la década de 1980, y la red resultante se llama la (o el) <em>Internet</em>. Ha cumplido su promesa.</p> <p><a class="p_ident" id="p-4uFR3JU4yz" href="#p-4uFR3JU4yz" tabindex="-1" role="presentation"></a>Una computadora puede usar esta red para enviar bits a otra computadora. Para que surja una comunicación efectiva de este envío de bits, las computadoras en ambos extremos deben saber qué se supone que representan los bits. El significado de cualquier secuencia dada de bits depende enteramente del tipo de cosa que está tratando de expresar y del mecanismo de codificación utilizado.</p> @@ -50,23 +50,23 @@ <h2><a class="h_ident" id="h-K5HX9aqefS" href="#h-K5HX9aqefS" tabindex="-1" role <p><a class="p_ident" id="p-m0VyUhW1W1" href="#p-m0VyUhW1W1" tabindex="-1" role="presentation"></a>Otra computadora puede establecer entonces una conexión conectándose a la máquina de destino usando el número de puerto correcto. Si la máquina de destino es alcanzable y está escuchando en ese puerto, la conexión se crea con éxito. La computadora que escucha se llama el <em>servidor</em>, y la computadora que se conecta se llama el <em>cliente</em>.</p> -<p><a class="p_ident" id="p-eb8sx6auML" href="#p-eb8sx6auML" tabindex="-1" role="presentation"></a>Dicha conexión actúa como un conducto bidireccional a través del cual pueden fluir los bits: las máquinas en ambos extremos pueden insertar datos en él. Una vez que los bits se transmiten con éxito, pueden volver a ser leídos por la máquina del otro lado. Este es un modelo conveniente. Se podría decir que TCP proporciona una abstracción de la red.</p> +<p><a class="p_ident" id="p-eb8sx6auML" href="#p-eb8sx6auML" tabindex="-1" role="presentation"></a>Dicha conexión actúa como un conducto bidireccional a través del cual pueden fluir los bits: las máquinas en ambos extremos pueden insertar datos en él. Una vez que los bits se transmiten con éxito, pueden ser leídos por la máquina del otro lado. Este es un modelo muy cómodo. Se podría decir que TCP proporciona una abstracción de la red.</p> <h2 id="web"><a class="h_ident" id="h-t3F6j+LOAA" href="#h-t3F6j+LOAA" tabindex="-1" role="presentation"></a>La Web</h2> -<p><a class="p_ident" id="p-9H4JK3AyFP" href="#p-9H4JK3AyFP" tabindex="-1" role="presentation"></a>El <em>World Wide Web</em> (no se debe confundir con el Internet en su totalidad) es un conjunto de protocolos y formatos que nos permiten visitar páginas web en un navegador. La parte “Web” en el nombre se refiere al hecho de que estas páginas pueden enlazarse fácilmente entre sí, conectándose así en una gran malla por la que los usuarios pueden moverse.</p> +<p><a class="p_ident" id="p-JbXOHZ9diD" href="#p-JbXOHZ9diD" tabindex="-1" role="presentation"></a>La <em>World Wide Web</em> (no se debe confundir con la Internet en su totalidad) es un conjunto de protocolos y formatos que nos permiten visitar páginas web en un navegador. La parte “Web” en el nombre se refiere al hecho de que estas páginas pueden enlazarse fácilmente entre sí, conectándose así todas en una gran malla por la que los usuarios pueden moverse.</p> -<p><a class="p_ident" id="p-wzOTsLscH/" href="#p-wzOTsLscH/" tabindex="-1" role="presentation"></a>Para formar parte de la Web, todo lo que necesitas hacer es conectar una máquina al Internet y hacer que escuche en el puerto 80 con el protocolo HTTP para que otras computadoras puedan solicitarle documentos.</p> +<p><a class="p_ident" id="p-wzOTsLscH/" href="#p-wzOTsLscH/" tabindex="-1" role="presentation"></a>Para formar parte de la Web, todo lo que necesitas hacer es conectar una máquina a Internet y hacer que escuche en el puerto 80 con el protocolo HTTP para que otras computadoras puedan solicitarle documentos.</p> -<p><a class="p_ident" id="p-X25i1TfJPc" href="#p-X25i1TfJPc" tabindex="-1" role="presentation"></a>Cada documento en la Web está nombrado por un <em>Localizador de Recursos Uniforme</em> (URL), que se ve algo así:</p> +<p><a class="p_ident" id="p-skQdNYQBGj" href="#p-skQdNYQBGj" tabindex="-1" role="presentation"></a>Cada documento en la Web está nombrado por un <em>Localizador de Recursos Uniforme</em> (URL), que tiene un aspecto como este:</p> -<pre class="snippet" data-language="null" ><a class="c_ident" id="c-trYcgkrqxy" href="#c-trYcgkrqxy" tabindex="-1" role="presentation"></a> http://eloquentjavascript.net/13_browser.html - | | | | - protocol servidor ruta</pre> +<pre class="snippet" data-language="null" ><a class="c_ident" id="c-LDW7SSu9b5" href="#c-LDW7SSu9b5" tabindex="-1" role="presentation"></a> https://eloquentjavascript.es/13_browser.html + | | | | + protocolo servidor ruta</pre> -<p><a class="p_ident" id="p-SieVfgKDpW" href="#p-SieVfgKDpW" tabindex="-1" role="presentation"></a>La primera parte nos dice que esta URL utiliza el protocolo HTTP (en contraposición, por ejemplo, a HTTP cifrado, que sería <em>https://</em>). Luego viene la parte que identifica desde qué servidor estamos solicitando el documento. Por último está una cadena de ruta que identifica el documento específico (o <em>recurso</em>) en el que estamos interesados.</p> +<p><a class="p_ident" id="p-SieVfgKDpW" href="#p-SieVfgKDpW" tabindex="-1" role="presentation"></a>La primera parte nos dice que esta URL utiliza el protocolo HTTP cifrado (en contraposición, por ejemplo, a HTTP, que sería solamente <em>http://</em>). Luego viene la parte que identifica a qué servidor estamos solicitando el documento. Por último está una cadena de ruta que identifica el documento específico (o <em>recurso</em>) en el que estamos interesados.</p> -<p><a class="p_ident" id="p-84iTEQR+qg" href="#p-84iTEQR+qg" tabindex="-1" role="presentation"></a>Las máquinas conectadas a Internet tienen una <em>dirección IP</em>, que es un número que se puede utilizar para enviar mensajes a esa máquina, y se ve algo así como <code>149.210.142.219</code> o <code>2001:4860:4860::8888</code>. Pero las listas de números más o menos aleatorios son difíciles de recordar y complicados de escribir, así que en su lugar puedes registrar un <em>nombre de dominio</em> para una dirección específica o un conjunto de direcciones. Registré <em>eloquentjavascript.net</em> para apuntar a la dirección IP de una máquina que controlo y, por lo tanto, puedo usar ese nombre de dominio para servir páginas web.</p> +<p><a class="p_ident" id="p-84iTEQR+qg" href="#p-84iTEQR+qg" tabindex="-1" role="presentation"></a>Las máquinas conectadas a Internet tienen una <em>dirección IP</em>, que es un número que se puede utilizar para enviar mensajes a esa máquina, y tiene un aspecto como <code>149.210.142.219</code> o <code>2001:4860:4860::8888</code>. Como unas listas de números medio aleatorios son difíciles de recordar y complicadas de escribir, en su lugar puedes registrar un <em>nombre de dominio</em> para una dirección específica o un conjunto de direcciones. Registré _eloquentjavascript.es para apuntar a la dirección IP de una máquina que controlo y, por lo tanto, puedo usar ese nombre de dominio para servir páginas web.</p> <p><a class="p_ident" id="p-zTJEhpWfrb" href="#p-zTJEhpWfrb" tabindex="-1" role="presentation"></a>Si escribes esta URL en la barra de direcciones de tu navegador, el navegador intentará recuperar y mostrar el documento en esa URL. Primero, tu navegador tiene que averiguar a qué dirección se refiere <em>eloquentjavascript.net</em>. Luego, utilizando el protocolo HTTP, hará una conexión con el servidor en esa dirección y solicitará el recurso <em>/13_browser.html</em>. Si todo va bien, el servidor enviará un documento, que tu navegador mostrará en tu pantalla.</p> @@ -74,7 +74,7 @@ <h2><a class="h_ident" id="h-n3OM6EV/KR" href="#h-n3OM6EV/KR" tabindex="-1" role <p><a class="p_ident" id="p-fyOUwCz4XS" href="#p-fyOUwCz4XS" tabindex="-1" role="presentation"></a>HTML, que significa <em>Lenguaje de Marcado de Hipertexto</em>, es el formato de documento utilizado para páginas web. Un documento HTML contiene texto, así como <em>etiquetas</em> que estructuran el texto, describiendo cosas como enlaces, párrafos y encabezados.</p> -<p><a class="p_ident" id="p-rjgl2FygfC" href="#p-rjgl2FygfC" tabindex="-1" role="presentation"></a>Un documento HTML corto podría lucir así:</p> +<p><a class="p_ident" id="p-QjrJ0MYdW7" href="#p-QjrJ0MYdW7" tabindex="-1" role="presentation"></a>Un documento HTML corto podría tener esta pinta:</p> <pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-ckwQsm/t0t" href="#c-ckwQsm/t0t" tabindex="-1" role="presentation"></a><span class="tok-meta"><!doctype html></span> <<span class="tok-typeName">html</span>> @@ -92,17 +92,17 @@ <h2><a class="h_ident" id="h-n3OM6EV/KR" href="#h-n3OM6EV/KR" tabindex="-1" role <p><a class="p_ident" id="p-nRWX2BtQ3x" href="#p-nRWX2BtQ3x" tabindex="-1" role="presentation"></a>Las etiquetas, encerradas en corchetes angulares (<code><</code> y <code>></code>, los símbolos de <em>menor que</em> y <em>mayor que</em>), proporcionan información sobre la estructura del documento. El otro texto es simplemente texto plano.</p> -<p><a class="p_ident" id="p-WEv2oTZCiY" href="#p-WEv2oTZCiY" tabindex="-1" role="presentation"></a>El documento comienza con <code><!doctype html></code>, lo que indica al navegador interpretar la página como HTML <em>moderno</em>, en contraposición a estilos obsoletos que se utilizaban en el pasado.</p> +<p><a class="p_ident" id="p-WEv2oTZCiY" href="#p-WEv2oTZCiY" tabindex="-1" role="presentation"></a>El documento comienza con <code><!doctype html></code>, lo que le dice al navegador que interprete la página como HTML <em>moderno</em>, en contraposición a estilos obsoletos que se utilizaban en el pasado.</p> -<p><a class="p_ident" id="p-BDo3C4e6rk" href="#p-BDo3C4e6rk" tabindex="-1" role="presentation"></a>Los documentos HTML tienen una cabecera y un cuerpo. La cabecera contiene información <em>sobre</em> el documento, y el cuerpo contiene el documento en sí. En este caso, la cabecera declara que el título de este documento es “Mi página de inicio” y que utiliza la codificación UTF-8, que es una forma de codificar texto Unicode como datos binarios. El cuerpo del documento contiene un encabezado (<code><h1></code>, que significa “encabezado 1” —<code><h2></code> a <code><h6></code> producen subencabezados) y dos párrafos (<code><p></code>).</p> +<p><a class="p_ident" id="p-BDo3C4e6rk" href="#p-BDo3C4e6rk" tabindex="-1" role="presentation"></a>Los documentos HTML tienen una cabecera y un cuerpo. La cabecera contiene información <em>sobre</em> el documento, y el cuerpo contiene el documento en sí. En este caso, la cabecera declara que el título de este documento es “Mi página de inicio” y que utiliza la codificación UTF-8, que es una forma de codificar texto Unicode como datos binarios. El cuerpo del documento contiene un encabezado (<code><h1></code>, que significa “encabezado 1” —las etiquetas <code><h2></code> a <code><h6></code> producen subencabezados—) y dos párrafos (<code><p></code>).</p> -<p><a class="p_ident" id="p-+R+Har4W0v" href="#p-+R+Har4W0v" tabindex="-1" role="presentation"></a>Las etiquetas vienen en varias formas. Un elemento, como el cuerpo, un párrafo o un enlace, comienza con una <em>etiqueta de apertura</em> como <code><p></code> y finaliza con una <em>etiqueta de cierre</em> como <code></p></code>. Algunas etiquetas de apertura, como la de enlace (<code><a></code>), contienen información adicional en forma de pares <code>nombre="valor"</code>. Estos se llaman <em>atributos</em>. En este caso, el destino del enlace se indica con <code>href="http://<wbr>eloquentjavascript.<wbr>net"</code>, donde <code>href</code> significa “hipervínculo de referencia”.</p> +<p><a class="p_ident" id="p-+R+Har4W0v" href="#p-+R+Har4W0v" tabindex="-1" role="presentation"></a>Las etiquetas vienen en varias formas. Un elemento, como el cuerpo, un párrafo o un enlace, comienza con una <em>etiqueta de apertura</em> como <code><p></code> y finaliza con una <em>etiqueta de cierre</em> como <code></p></code>. Algunas etiquetas de apertura, como la de enlace (<code><a></code>), contienen información adicional en forma de pares <code>nombre="valor"</code>. Estos se llaman <em>atributos</em>. En este caso, el destino del enlace se indica con <code>href="https://<wbr>eloquentjavascript.<wbr>es"</code>, donde <code>href</code> significa “hipervínculo de referencia”.</p> <p><a class="p_ident" id="p-ZBZSwe4Ocm" href="#p-ZBZSwe4Ocm" tabindex="-1" role="presentation"></a>Algunos tipos de etiquetas no contienen nada y por lo tanto no necesitan ser cerradas. La etiqueta de metadatos <code><meta charset="utf-8"></code> es un ejemplo de esto.</p> -<p><a class="p_ident" id="p-fhmD+Pj/SE" href="#p-fhmD+Pj/SE" tabindex="-1" role="presentation"></a>Para poder incluir corchetes angulares en el texto de un documento, a pesar de que tienen un significado especial en HTML, se debe introducir otra forma especial de notación. Un simple signo menor que se escribe como <code>&lt;</code> (“menor que”), y un signo mayor que se escribe como <code>&gt;</code> (“mayor que”). En HTML, un carácter y comercial (<code>&</code>) seguido de un nombre o código de carácter y un punto y coma (<code>;</code>) se llama una <em>entidad</em> y será reemplazado por el carácter que codifica.</p> +<p><a class="p_ident" id="p-fhmD+Pj/SE" href="#p-fhmD+Pj/SE" tabindex="-1" role="presentation"></a>Para poder incluir corchetes angulares en el texto de un documento, a pesar de que tienen un significado especial en HTML, se debe introducir otra forma especial de notación. Un simple signo de menor que se escribe <code>&lt;</code>, y un signo mayor que se escribe <code>&gt;</code>. En HTML, un carácter <em>et</em> (es decir, el carácter <code>&</code>, también conocido en inglés y en general en informática como <em>ampersand</em>) seguido de un nombre o código de carácter y un punto y coma (<code>;</code>), se llama <em>entidad</em>, y será reemplazada por el carácter que codifica.</p> -<p><a class="p_ident" id="p-hdA6Kb/Y9d" href="#p-hdA6Kb/Y9d" tabindex="-1" role="presentation"></a>Esto es análogo a la manera en que se utilizan las barras invertidas en las cadenas de texto de JavaScript. Dado que este mecanismo también otorga un significado especial a los caracteres de y comercial, necesitan ser escapados como <code>&amp;</code>. Dentro de los valores de los atributos, que están entre comillas dobles, se puede usar <code>&quot;</code> para insertar un carácter de comillas real.</p> +<p><a class="p_ident" id="p-hdA6Kb/Y9d" href="#p-hdA6Kb/Y9d" tabindex="-1" role="presentation"></a>Esto es análogo a la manera en que se utilizan las barras invertidas en las cadenas de texto de JavaScript. Dado que este mecanismo también da un significado especial a los caracteres de ampersand, estos necesitan ser escapados como <code>&amp;</code>. Dentro de los valores de los atributos, que están entre comillas dobles, se puede usar <code>&quot;</code> para insertar un carácter de comillas real.</p> <p><a class="p_ident" id="p-Tqt8OOQWi8" href="#p-Tqt8OOQWi8" tabindex="-1" role="presentation"></a>HTML se analiza de una manera notablemente tolerante a errores. Cuando faltan etiquetas que deberían estar ahí, el navegador las agrega automáticamente. La forma en que se hace esto se ha estandarizado, y puedes confiar en que todos los navegadores modernos lo harán de la misma manera.</p> @@ -120,9 +120,9 @@ <h2><a class="h_ident" id="h-n3OM6EV/KR" href="#h-n3OM6EV/KR" tabindex="-1" role <p><a class="p_ident" id="p-IVRV+HvpSP" href="#p-IVRV+HvpSP" tabindex="-1" role="presentation"></a>Las etiquetas <code><html></code>, <code><head></code> y <code><body></code> han desaparecido por completo. El navegador sabe que <code><meta></code> y <code><title></code> pertenecen a la cabecera y que <code><h1></code> significa que el cuerpo ha comenzado. Además, ya no cierro explícitamente los párrafos, ya que abrir un nuevo párrafo o finalizar el documento los cerrará implícitamente. Las comillas alrededor de los valores de los atributos también han desaparecido.</p> -<p><a class="p_ident" id="p-H9u9IuMfoH" href="#p-H9u9IuMfoH" tabindex="-1" role="presentation"></a>Este libro generalmente omitirá las etiquetas <code><html></code>, <code><head></code> y <code><body></code> en ejemplos para mantenerlos cortos y libres de desorden. Pero <em>sí</em> cerraré las etiquetas e incluiré comillas alrededor de los atributos.</p> +<p><a class="p_ident" id="p-H9u9IuMfoH" href="#p-H9u9IuMfoH" tabindex="-1" role="presentation"></a>Este libro generalmente omitirá las etiquetas <code><html></code>, <code><head></code> y <code><body></code> en ejemplos para mantenerlos cortos y ordenados. Pero <em>sí</em> cerraré las etiquetas e incluiré comillas alrededor de los atributos.</p> -<p><a class="p_ident" id="p-YmufKuQA2D" href="#p-YmufKuQA2D" tabindex="-1" role="presentation"></a>También generalmente omitiré el doctype y la declaración <code>charset</code>. Esto no debe interpretarse como una recomendación para omitirlos de documentos HTML. Los navegadores a menudo hacen cosas ridículas cuando los olvidas. Deberías considerar que el doctype y los metadatos del <code>charset</code> están implícitamente presentes en los ejemplos, incluso cuando no se muestran realmente en el texto.</p> +<p><a class="p_ident" id="p-KusfOIIx86" href="#p-KusfOIIx86" tabindex="-1" role="presentation"></a>También omitiré generalmente el doctype y la declaración <code>charset</code>. Esto no debe interpretarse como una recomendación para omitirlos de documentos HTML. Los navegadores a menudo hacen cosas ridículas cuando los olvidas. Deberías considerar que el doctype y los metadatos del <code>charset</code> están implícitamente presentes en los ejemplos, incluso cuando no se muestran realmente en el texto.</p> <h2 id="script_tag"><a class="h_ident" id="h-MAYmG7Zxt0" href="#h-MAYmG7Zxt0" tabindex="-1" role="presentation"></a>HTML y JavaScript</h2> @@ -131,14 +131,14 @@ <h2 id="script_tag"><a class="h_ident" id="h-MAYmG7Zxt0" href="#h-MAYmG7Zxt0" ta <pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-cHDylq2BVC" href="#c-cHDylq2BVC" tabindex="-1" role="presentation"></a><<span class="tok-typeName">h1</span>>Probando alerta</<span class="tok-typeName">h1</span>> <<span class="tok-typeName">script</span>>alert(<span class="tok-string">"¡hola!"</span>);</<span class="tok-typeName">script</span>></pre> -<p><a class="p_ident" id="p-iPjsaWsMFr" href="#p-iPjsaWsMFr" tabindex="-1" role="presentation"></a>Dicho script se ejecutará tan pronto como su etiqueta <code><script></code> sea encontrada mientras el navegador lee el HTML. Esta página mostrará un cuadro de diálogo al abrirla—la función <code>alert</code> se asemeja a <code>prompt</code>, en que muestra una ventana pequeña, pero solo muestra un mensaje sin solicitar entrada.</p> +<p><a class="p_ident" id="p-iPjsaWsMFr" href="#p-iPjsaWsMFr" tabindex="-1" role="presentation"></a>Dicho script se ejecutará tan pronto como su etiqueta <code><script></code> sea encontrada mientras el navegador lee el HTML. Esta página mostrará un cuadro de diálogo al abrirla —la función <code>alert</code> se asemeja a <code>prompt</code> en que muestra una ventana pequeña, pero solo muestra un mensaje sin solicitar entrada.</p> -<p><a class="p_ident" id="p-CNd/Qg0y1o" href="#p-CNd/Qg0y1o" tabindex="-1" role="presentation"></a>Incluir programas extensos directamente en documentos HTML a menudo es poco práctico. La etiqueta <code><script></code> puede recibir un atributo <code>src</code> para obtener un archivo de script (un archivo de texto que contiene un programa JavaScript) desde una URL.</p> +<p><a class="p_ident" id="p-CNd/Qg0y1o" href="#p-CNd/Qg0y1o" tabindex="-1" role="presentation"></a>Incluir programas extensos directamente en documentos HTML a menudo resulta poco práctico. La etiqueta <code><script></code> puede recibir un atributo <code>src</code> para obtener un archivo de script (un archivo de texto que contiene un programa JavaScript) desde una URL.</p> <pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-L9+ROXJtBz" href="#c-L9+ROXJtBz" tabindex="-1" role="presentation"></a><<span class="tok-typeName">h1</span>>Probando alerta</<span class="tok-typeName">h1</span>> <<span class="tok-typeName">script</span> src=<span class="tok-string">"code/hello.js"</span>></<span class="tok-typeName">script</span>></pre> -<p><a class="p_ident" id="p-wZSqBAlJRv" href="#p-wZSqBAlJRv" tabindex="-1" role="presentation"></a>El archivo <em>code/hello.js</em> incluido aquí contiene el mismo programa—<code>alert("¡hola!")</code>. Cuando una página HTML referencia otras URL como parte de sí misma—por ejemplo, un archivo de imagen o un script—los navegadores web los recuperarán inmediatamente e incluirán en la página.</p> +<p><a class="p_ident" id="p-wZSqBAlJRv" href="#p-wZSqBAlJRv" tabindex="-1" role="presentation"></a>El archivo <em>code/hello.js</em> incluido aquí contiene el mismo programa —<code>alert("¡hola!")</code>— que vimos antes. Cuando una página HTML referencia otras URL como parte de sí misma —por ejemplo, un archivo de imagen o un script— los navegadores web los recuperarán inmediatamente e incluirán en la página.</p> <p><a class="p_ident" id="p-QIJfIPTHnj" href="#p-QIJfIPTHnj" tabindex="-1" role="presentation"></a>Una etiqueta de script siempre debe cerrarse con <code></script></code>, incluso si hace referencia a un archivo de script y no contiene ningún código. Si olvidas esto, el resto de la página se interpretará como parte del script.</p> @@ -148,29 +148,29 @@ <h2 id="script_tag"><a class="h_ident" id="h-MAYmG7Zxt0" href="#h-MAYmG7Zxt0" ta <pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-ccR2XVZigP" href="#c-ccR2XVZigP" tabindex="-1" role="presentation"></a><<span class="tok-typeName">button</span> onclick=<span class="tok-string">"</span>alert(<span class="tok-string">'¡Boom!'</span>);<span class="tok-string">"</span>>¡NO PRESIONES!</<span class="tok-typeName">button</span>></pre> -<p><a class="p_ident" id="p-O9Nf633e6a" href="#p-O9Nf633e6a" tabindex="-1" role="presentation"></a>Nota que tuve que utilizar comillas simples para el string en el atributo <code>onclick</code> porque las comillas dobles ya se usan para citar todo el atributo. También podría haber utilizado <code>&quot;</code>.</p> +<p><a class="p_ident" id="p-vOOEU2zkXR" href="#p-vOOEU2zkXR" tabindex="-1" role="presentation"></a>Fíjate en que he tenido que utilizar comillas simples para el string en el atributo <code>onclick</code> porque las comillas dobles ya se usan para citar todo el atributo. También podría haber utilizado <code>&quot;</code>.</p> -<h2><a class="h_ident" id="h-degK7BPBJO" href="#h-degK7BPBJO" tabindex="-1" role="presentation"></a>En el entorno controlado</h2> +<h2><a class="h_ident" id="h-b4D3LWbBMg" href="#h-b4D3LWbBMg" tabindex="-1" role="presentation"></a>En el sandbox</h2> -<p><a class="p_ident" id="p-1KrKvhnT3g" href="#p-1KrKvhnT3g" tabindex="-1" role="presentation"></a>Ejecutar programas descargados de Internet es potencialmente peligroso. No sabes mucho sobre las personas detrás de la mayoría de los sitios que visitas, y no necesariamente tienen buenas intenciones. Ejecutar programas de personas que no tienen buenas intenciones es cómo se infecta tu computadora con virus, te roban tus datos y hackean tus cuentas.</p> +<p><a class="p_ident" id="p-1KrKvhnT3g" href="#p-1KrKvhnT3g" tabindex="-1" role="presentation"></a>Ejecutar programas descargados de Internet es potencialmente peligroso. No sabes mucho sobre la gente detrás de la mayoría de los sitios que visitas, y no necesariamente tienen buenas intenciones. Ejecutar programas de gente que no tienen buenas intenciones es la manera en que se infecta tu computadora con virus, te roban tus datos y hackean tus cuentas.</p> -<p><a class="p_ident" id="p-gOImrkQHw7" href="#p-gOImrkQHw7" tabindex="-1" role="presentation"></a>Sin embargo, la atracción de la Web es que puedes navegar por ella sin necesariamente confiar en todas las páginas que visitas. Por eso, los navegadores limitan severamente las cosas que un programa JavaScript puede hacer: no puede ver los archivos en tu computadora ni modificar nada que no esté relacionado con la página web en la que estaba incrustado.</p> +<p><a class="p_ident" id="p-gOImrkQHw7" href="#p-gOImrkQHw7" tabindex="-1" role="presentation"></a>Sin embargo, la gracia de la Web es que puedes navegar por ella sin necesariamente confiar en todas las páginas que visitas. Por eso, los navegadores limitan severamente las cosas que un programa JavaScript puede hacer: no puede ver los archivos en tu computadora ni modificar nada que no esté relacionado con la página web en la que estaba incrustado.</p> -<p><a class="p_ident" id="p-aDb57RTnLu" href="#p-aDb57RTnLu" tabindex="-1" role="presentation"></a>Aislar un entorno de programación de esta manera se llama <em>sandboxing</em>, la idea es que el programa está jugando inofensivamente en un arenero. Pero debes imaginar este tipo particular de arenero como teniendo una jaula de barras de acero gruesas sobre él para que los programas que juegan en él no puedan salir realmente.</p> +<p><a class="p_ident" id="p-0c4uH3QUJ0" href="#p-0c4uH3QUJ0" tabindex="-1" role="presentation"></a>Aislar un entorno de programación de esta manera se llama <em>sandboxing</em>, la idea es que el programa está jugando inofensivamente en un arenero. Pero debes imaginar este tipo particular de arenero como uno que tiene una jaula de barrotes de acero bien gruesas sobre él para que los programas que juegan en él de verdad no puedan salir.</p> -<p><a class="p_ident" id="p-Cb5C1Erp8g" href="#p-Cb5C1Erp8g" tabindex="-1" role="presentation"></a>La parte difícil del sandboxing es permitir que los programas tengan suficiente espacio para ser útiles y al mismo tiempo restringirlos para que no hagan nada peligroso. Muchas funcionalidades útiles, como comunicarse con otros servidores o leer el contenido del portapapeles, también pueden usarse para hacer cosas problemáticas que invaden la privacidad.</p> +<p><a class="p_ident" id="p-Cb5C1Erp8g" href="#p-Cb5C1Erp8g" tabindex="-1" role="presentation"></a>La parte difícil del sandboxing es permitir que los programas tengan suficiente espacio para ser útiles y, al mismo tiempo, restringirlos lo suficiente para que no hagan nada peligroso. Muchas funcionalidades útiles, como comunicarse con otros servidores o leer el contenido del portapapeles, también pueden usarse para hacer cosas problemáticas que invaden la privacidad.</p> -<p><a class="p_ident" id="p-W+mAjjJrya" href="#p-W+mAjjJrya" tabindex="-1" role="presentation"></a>De vez en cuando, alguien encuentra una nueva forma de evitar las limitaciones de un navegador y hacer algo dañino, que va desde filtrar información privada menor hasta tomar el control de toda la máquina en la que se ejecuta el navegador. Los desarrolladores de navegadores responden reparando el agujero, y todo vuelve a estar bien, hasta que se descubre el próximo problema, y con suerte se publicita, en lugar de ser explotado en secreto por alguna agencia gubernamental u organización criminal.</p> +<p><a class="p_ident" id="p-W+mAjjJrya" href="#p-W+mAjjJrya" tabindex="-1" role="presentation"></a>De vez en cuando, alguien encuentra una nueva forma de evitar las limitaciones de un navegador y hacer algo dañino, que va desde filtrar información privada no demasiado relevante hasta tomar el control de toda la máquina en la que se ejecuta el navegador. Los desarrolladores de navegadores responden reparando el agujero, y todo vuelve a estar bien, hasta que se descubre el próximo problema (y con suerte se publica, en lugar de ser explotado en secreto por alguna agencia gubernamental u organización criminal).</p> <h2><a class="h_ident" id="h-jRhvsSHqzI" href="#h-jRhvsSHqzI" tabindex="-1" role="presentation"></a>Compatibilidad y las guerras de navegadores</h2> <p><a class="p_ident" id="p-/AYh9qvwu+" href="#p-/AYh9qvwu+" tabindex="-1" role="presentation"></a>En las etapas iniciales de la Web, un navegador llamado Mosaic dominaba el mercado. Después de unos años, el equilibrio se desplazó a Netscape, que a su vez fue en gran medida reemplazado por Internet Explorer de Microsoft. En cualquier punto en el que un único navegador era dominante, el fabricante de ese navegador se creía con derecho a inventar nuevas funciones para la Web unilateralmente. Dado que la mayoría de usuarios usaban el navegador más popular, los sitio webs simplemente comenzaban a usar esas características, sin importar los otros navegadores.</p> -<p><a class="p_ident" id="p-YhUzAjEVXJ" href="#p-YhUzAjEVXJ" tabindex="-1" role="presentation"></a>Esta fue la era oscura de la compatibilidad, a menudo llamada las <em>guerras de navegadores</em>. Los desarrolladores web se quedaron con no una Web unificada, sino dos o tres plataformas incompatibles. Para empeorar las cosas, los navegadores en uso alrededor de 2003 estaban llenos de errores, y por supuesto los errores eran diferentes para cada navegador. La vida era difícil para las personas que escribían páginas web.</p> +<p><a class="p_ident" id="p-YhUzAjEVXJ" href="#p-YhUzAjEVXJ" tabindex="-1" role="presentation"></a>Esta fue la era oscura de la compatibilidad, a menudo llamada la <em>guerra de navegadores</em>. Los desarrolladores web se quedaron con no una Web unificada, sino dos o tres plataformas incompatibles. Para empeorar las cosas, los navegadores en uso alrededor de 2003 estaban llenos de errores y, por supuesto los errores eran diferentes para cada navegador. La vida era difícil para las personas que escribían páginas web.</p> -<p><a class="p_ident" id="p-J2nIZs5K87" href="#p-J2nIZs5K87" tabindex="-1" role="presentation"></a>Mozilla Firefox, un derivado sin ánimo de lucro de Netscape, desafió la posición de Internet Explorer a finales de la década de 2000. Debido a que Microsoft no estaba particularmente interesado en mantenerse competitivo en ese momento, Firefox le quitó mucho cuota de mercado. Alrededor del mismo tiempo, Google introdujo su navegador Chrome y el navegador de Apple Safari ganó popularidad, lo que llevó a una situación en la que había cuatro actores principales, en lugar de uno solo.</p> +<p><a class="p_ident" id="p-J2nIZs5K87" href="#p-J2nIZs5K87" tabindex="-1" role="presentation"></a>Mozilla Firefox, un derivado sin ánimo de lucro de Netscape, desafió la posición de Internet Explorer a finales de la década de 2000. Como Microsoft no estaba particularmente interesado en mantenerse competitivo en ese momento, Firefox le quitó mucho cuota de mercado. Alrededor del mismo tiempo, Google introdujo su navegador Chrome y el navegador de Apple Safari ganó popularidad, lo que llevó a una situación en la que había cuatro actores principales, en lugar de uno solo.</p> -<p><a class="p_ident" id="p-Xwnv73zbXe" href="#p-Xwnv73zbXe" tabindex="-1" role="presentation"></a>Los nuevos actores tenían una actitud más seria hacia los estándares y mejores prácticas de ingeniería, lo que nos dio menos incompatibilidad y menos errores. Microsoft, viendo cómo su cuota de mercado se desmoronaba, adoptó estas actitudes en su navegador Edge, que reemplaza a Internet Explorer. Si estás empezando a aprender desarrollo web hoy, considérate afortunado. Las últimas versiones de los principales navegadores se comportan de manera bastante uniforme y tienen relativamente pocos errores.Desafortunadamente, con la disminución constante de la cuota de mercado de Firefox y Edge convirtiéndose en simplemente un contenedor alrededor del núcleo de Chrome en 2018, esta uniformidad podría una vez más tomar la forma de un único proveedor —Google en este caso— teniendo el suficiente control sobre el mercado de navegadores para imponer su idea de cómo debería lucir la Web al resto del mundo.</p><nav><a href="12_language.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="14_dom.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button> +<p><a class="p_ident" id="p-Xwnv73zbXe" href="#p-Xwnv73zbXe" tabindex="-1" role="presentation"></a>Los nuevos actores tenían una actitud más seria hacia los estándares y mejores prácticas de ingeniería, lo que nos dio menos incompatibilidad y menos errores. Microsoft, viendo cómo su cuota de mercado se desmoronaba, adoptó estas actitudes en su navegador Edge, que reemplaza a Internet Explorer. Si estás empezando a aprender desarrollo web hoy, considérate afortunado. Las últimas versiones de los principales navegadores se comportan de manera bastante uniforme y tienen relativamente pocos errores.Desafortunadamente, con la disminución constante de la cuota de mercado de Firefox y Edge convirtiéndose en simplemente un contenedor alrededor del núcleo de Chrome en 2018, esta uniformidad podría una vez más tomar la forma de un único proveedor —Google en este caso— teniendo el suficiente control sobre el mercado de navegadores para imponer su idea de cómo debería ser la Web al resto del mundo.</p><nav><a href="12_language.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="14_dom.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button> </nav> </article> From de94cf1501710558c5a1a1ebbafd8a9aef529303 Mon Sep 17 00:00:00 2001 From: ckdvk <javicemarpe@gmail.com> Date: Sat, 22 Feb 2025 03:14:27 +0800 Subject: [PATCH 25/36] =?UTF-8?q?revisado=20el=20cap=C3=ADtulo=2014?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 14_dom.md | 259 ++++++++++++++++++++++---------------------- html/14_dom.html | 276 ++++++++++++++++++++++++----------------------- 2 files changed, 273 insertions(+), 262 deletions(-) diff --git a/14_dom.md b/14_dom.md index a1d48dae..709c09f6 100644 --- a/14_dom.md +++ b/14_dom.md @@ -2,7 +2,7 @@ {{quote {author: "Friedrich Nietzsche", title: "Más allá del bien y del mal", chapter: true} -¡Qué mal! ¡La misma vieja historia! Una vez que has terminado de construir tu casa, te das cuenta de que has aprendido accidentalmente algo que realmente deberías haber sabido antes de comenzar. +¡Tanto peor! ¡Otra vez la vieja historia! Cuando uno ha acabado de construir su casa advierte que, mientras la construía, ha aprendido, sin darse cuenta, algo que tendría que haber sabido absolutamente antes de comenzar a construir. quote}} @@ -10,17 +10,17 @@ quote}} {{index dibujo, "análisis"}} -Cuando abres una página web, tu navegador recupera el texto ((HTML)) de la página y lo analiza, de manera similar a como nuestro analizador de [Capítulo ?](language#parsing) analizaba programas. El navegador construye un modelo de la ((estructura)) del documento y utiliza este modelo para dibujar la página en la pantalla. +Cuando abres una página web, tu navegador recupera el texto ((HTML)) de la página y lo analiza, de manera similar a como nuestro analizador del [Capítulo ?](language#parsing) analizaba programas. El navegador construye un modelo de la ((estructura)) del documento y utiliza este modelo para dibujar la página en la pantalla. {{index "estructura de datos en vivo"}} -Esta representación del ((documento)) es uno de los juguetes que un programa JavaScript tiene disponible en su ((caja de arena)). Es una ((estructura de datos)) que puedes leer o modificar. Actúa como una estructura de datos _en vivo_: cuando se modifica, la página en la pantalla se actualiza para reflejar los cambios. +Esta representación del ((documento)) es uno de los recursos que un programa JavaScript tiene disponible en su ((sandbox)). Es una ((estructura de datos)) que puedes leer o modificar. Actúa como una estructura de datos _en vivo_: cuando se modifica, la página en la pantalla se actualiza para reflejar los cambios. ## Estructura del documento {{index [HTML, estructura]}} -Puedes imaginar un documento HTML como un conjunto anidado de ((caja))s. Etiquetas como `<body>` y `</body>` encierran otras ((etiqueta))s, que a su vez contienen otras etiquetas o ((texto)). Aquí está el documento de ejemplo del [capítulo anterior](browser): +Puedes imaginar un documento HTML como un conjunto anidado de ((caja))s. Etiquetas como `<body>` y `</body>` encierran otras ((etiqueta))s que, a su vez, contienen otras etiquetas o ((texto)). Aquí está el documento de ejemplo del [capítulo anterior](browser): ```{lang: html, sandbox: "homepage"} <!doctype html> @@ -47,21 +47,21 @@ La estructura de datos que el navegador utiliza para representar el documento si {{index "propiedad documentElement", "propiedad head", "propiedad body", "html (etiqueta HTML)", "body (etiqueta HTML)", "head (etiqueta HTML)"}} -El enlace global `document` nos da acceso a estos objetos. Su propiedad `documentElement` se refiere al objeto que representa la etiqueta `<html>`. Dado que cada documento HTML tiene una cabeza y un cuerpo, también tiene propiedades `head` y `body`, que apuntan a esos elementos. +La variable global `document` nos da acceso a estos objetos. Su propiedad `documentElement` se refiere al objeto que representa la etiqueta `<html>`. Dado que cada documento HTML tiene una cabecera y un cuerpo, también tiene propiedades `head` y `body`, que apuntan a esos elementos. ## Árboles {{index [anidamiento, "de objetos"]}} -Piensa en los ((árbol sintáctico))s del [Capítulo ?](language#parsing) por un momento. Sus estructuras son sorprendentemente similares a la estructura de un documento de un navegador. Cada _((nodo))_ puede referirse a otros nodos, _hijos_, que a su vez pueden tener sus propios hijos. Esta forma es típica de estructuras anidadas donde los elementos pueden contener subelementos que son similares a ellos mismos. +Piensa en los ((árboles sintáctico))s del [Capítulo ?](language#parsing) por un momento. Sus estructuras son sorprendentemente similares a la estructura de un documento de un navegador. Cada _((nodo))_ puede referirse a otros nodos, _hijos_, que a su vez pueden tener sus propios hijos. Esta forma es típica de estructuras anidadas donde los elementos pueden contener subelementos que son similares a ellos mismos. {{index "propiedad documentElement", [DOM, "árbol"]}} -Llamamos a una estructura de datos un _((árbol))_ cuando tiene una estructura de ramificación, no tiene ((ciclo))s (un nodo no puede contenerse a sí mismo, directa o indirectamente), y tiene un _((raíz))_ única y bien definida. En el caso del DOM, `document.documentElement` sirve como la raíz. +Llamamos _((árbol))_ a una estructura de datos cuando tiene una estructura de ramificación, no tiene ((ciclo))s (un nodo no puede contenerse a sí mismo, directa o indirectamente), y tiene una _((raíz))_ única y bien definida. En el caso del DOM, `document.documentElement` representa la raíz. {{index ordenamiento, ["estructura de datos", "árbol"], "árbol de sintaxis"}} -Los árboles son comunes en la informática. Además de representar estructuras recursivas como documentos HTML o programas, a menudo se utilizan para mantener ((conjunto))s de datos ordenados porque los elementos generalmente se pueden encontrar o insertar de manera más eficiente en un árbol que en un arreglo plano. +Los árboles son comunes en informática. Además de representar estructuras recursivas como documentos HTML o programas, a menudo se utilizan para mantener ((conjunto))s de datos ordenados porque los elementos generalmente se pueden encontrar o insertar de manera más eficiente en un árbol que en un array plano. {{index "nodo hoja", "Lenguaje Egg"}} @@ -69,11 +69,11 @@ Un árbol típico tiene diferentes tipos de ((nodo))s. El árbol de sintaxis par {{index "propiedad body", [HTML, estructura]}} -Lo mismo ocurre para el DOM. Los nodos de los _((elemento))s_, que representan etiquetas HTML, determinan la estructura del documento. Estos pueden tener ((nodo hijo))s. Un ejemplo de dicho nodo es `document.body`. Algunos de estos hijos pueden ser ((nodo hoja)), como fragmentos de ((texto)) o nodos ((comentario)). +Lo mismo ocurre para el DOM. Los nodos de los _((elemento))s_, que representan etiquetas HTML, determinan la estructura del documento. Estos pueden tener ((nodos hijo))s. Un ejemplo de dicho nodo es `document.body`. Algunos de estos hijos pueden ser ((nodos hoja)), como fragmentos de ((texto)) o nodos ((comentario)). {{index "nodo de texto", elemento, "código NODE_ELEMENT", "código NODE_COMMENT", "código NODE_TEXT", "propiedad nodeType"}} -Cada objeto de nodo del DOM tiene una propiedad `nodeType`, que contiene un código (número) que identifica el tipo de nodo. Los elementos tienen el código 1, que también se define como la propiedad constante `Node.ELEMENT_NODE`. Los nodos de texto, que representan una sección de texto en el documento, obtienen el código 3 (`Node.TEXT_NODE`). Los comentarios tienen el código 8 (`Node.COMMENT_NODE`). +Cada objeto de nodo del DOM tiene una propiedad `nodeType`, que contiene un código (un número) que identifica el tipo de nodo. Los elementos tienen el código 1, que también se define como la propiedad constante `Node.ELEMENT_NODE`. Los nodos de texto, que representan una sección de texto en el documento, obtienen el código 3 (`Node.TEXT_NODE`). Los comentarios tienen el código 8 (`Node.COMMENT_NODE`). Otra forma de visualizar nuestro ((árbol)) de documento es la siguiente: @@ -87,19 +87,19 @@ Las hojas son nodos de texto, y las flechas indican las relaciones padre-hijo en {{index "lenguaje de programación", [interfaz, "diseño"], [DOM, interfaz]}} -Usar códigos numéricos crípticos para representar tipos de nodos no es algo muy propio de JavaScript. Más adelante en este capítulo, veremos que otras partes de la interfaz del DOM también se sienten incómodas y extrañas. La razón de esto es que la interfaz del DOM no fue diseñada exclusivamente para JavaScript. Más bien, intenta ser una interfaz neutral en cuanto a lenguaje que también pueda utilizarse en otros sistemas, no solo para HTML, sino también para ((XML)), que es un formato de datos genérico con una sintaxis similar a HTML. +Usar códigos numéricos crípticos para representar tipos de nodos no es algo muy propio de JavaScript. Más adelante en este capítulo, veremos que otras partes de la interfaz del DOM también son un poco incómodas y extrañas. La razón de esto es que la interfaz del DOM no fue diseñada exclusivamente para JavaScript. Más bien, intenta ser una interfaz neutral en cuanto a lenguaje que también pueda utilizarse en otros sistemas, no solo para HTML, sino también para ((XML)), que es un formato de datos genérico con una sintaxis similar a HTML. {{index consistencia, "integración"}} -Esto es lamentable. Los estándares a menudo son útiles. Pero en este caso, la ventaja (consistencia entre lenguajes) no es tan convincente. Tener una interfaz que esté correctamente integrada con el lenguaje que estás utilizando te ahorrará más tiempo que tener una interfaz familiar en varios lenguajes. +Esto es una pena. Los estándares a menudo son útiles. Pero en este caso, la ventaja (consistencia entre lenguajes) no es tan convincente. Tener una interfaz que esté correctamente integrada con el lenguaje que estás utilizando te ahorrará más tiempo que tener una interfaz familiar para varios lenguajes. {{index "objeto similar a arreglo", "tipo NodeList"}} -Como ejemplo de esta mala integración, considera la propiedad `childNodes` que tienen los nodos de elementos en el DOM. Esta propiedad contiene un objeto similar a un array, con una propiedad `length` y propiedades etiquetadas por números para acceder a los nodos hijos. Pero es una instancia del tipo `NodeList`, no un array real, por lo que no tiene métodos como `slice` y `map`. +Como ejemplo de esta mala integración, considera la propiedad `childNodes` que tienen los nodos elemento en el DOM. Esta propiedad contiene un objeto similar a un array, con una propiedad `length` y propiedades etiquetadas por números para acceder a los nodos hijos. Pero es una instancia del tipo `NodeList`, no un array real, por lo que no tiene métodos como `slice` y `map`. {{index [interface, design], [DOM, construction], "side effect"}} -Luego, hay problemas que son simplemente de mala diseño. Por ejemplo, no hay forma de crear un nuevo nodo y agregar inmediatamente hijos o ((atributos)) a él. En su lugar, primero tienes que crearlo y luego agregar los hijos y atributos uno por uno, usando efectos secundarios. El código que interactúa mucho con el DOM tiende a ser largo, repetitivo y feo. +Entonces hay problemas que vienen simplemente de un mal diseño. Por ejemplo, no hay forma de crear un nuevo nodo y agregar inmediatamente hijos o ((atributos)) a él. En su lugar, primero tienes que crearlo y luego agregar los hijos y atributos uno por uno, usando efectos secundarios. El código que interactúa mucho con el DOM tiende a ser largo, repetitivo y feo. {{index library}} @@ -111,41 +111,41 @@ Pero estos defectos no son fatales. Dado que JavaScript nos permite crear nuestr Los nodos DOM contienen una gran cantidad de ((enlace))s a otros nodos cercanos. El siguiente diagrama ilustra esto: -{{figure {url: "img/html-links.svg", alt: "Diagrama que muestra los enlaces entre nodos DOM. El nodo 'body' se muestra como un cuadro, con una flecha 'firstChild' apuntando al nodo 'h1' en su inicio, una flecha 'lastChild' apuntando al último nodo de párrafo, y una flecha 'childNodes' apuntando a un array de enlaces a todos sus hijos. El párrafo del medio tiene una flecha 'previousSibling' apuntando al nodo anterior, una flecha 'nextSibling' al nodo siguiente, y una flecha 'parentNode' apuntando al nodo 'body'.", width: "6cm"}}} +{{figure {url: "img/html-links.svg", alt: "Diagrama que muestra los enlaces entre nodos del DOM. El nodo 'body' se muestra como un cuadro, con una flecha 'firstChild' apuntando al nodo 'h1' en su inicio, una flecha 'lastChild' apuntando al último nodo de párrafo, y una flecha 'childNodes' apuntando a un array de enlaces a todos sus hijos. El párrafo del medio tiene una flecha 'previousSibling' apuntando al nodo anterior, una flecha 'nextSibling' al nodo siguiente, y una flecha 'parentNode' apuntando al nodo 'body'.", width: "6cm"}}} {{index "child node", "parentNode property", "childNodes property"}} -Aunque el diagrama muestra solo un enlace de cada tipo, cada nodo tiene una propiedad `parentNode` que apunta al nodo del que forma parte, si lo hay. De igual manera, cada nodo de elemento (tipo 1) tiene una propiedad `childNodes` que apunta a un objeto similar a un array que contiene sus hijos. +Aunque el diagrama muestra solo un enlace de cada tipo, cada nodo tiene una propiedad `parentNode` que apunta al nodo del que forma parte, si lo hay. De igual manera, cada nodo elemento (tipo 1) tiene una propiedad `childNodes` que apunta a un objeto similar a un array que contiene sus hijos. {{index "firstChild property", "lastChild property", "previousSibling property", "nextSibling property"}} -En teoría, podrías moverte por todo el árbol utilizando solo estos enlaces padre e hijo. Pero JavaScript también te da acceso a varios enlaces de conveniencia adicionales. Las propiedades `firstChild` y `lastChild` apuntan a los primeros y últimos elementos hijos o tienen el valor `null` para nodos sin hijos. De manera similar, `previousSibling` y `nextSibling` apuntan a nodos adyacentes, que son nodos con el mismo padre que aparecen inmediatamente antes o después del nodo en sí. Para un primer hijo, `previousSibling` será nulo, y para un último hijo, `nextSibling` será nulo. +En teoría, podrías moverte por todo el árbol utilizando solo estos enlaces padre e hijo. Pero JavaScript también te da acceso a varios enlaces adicionales que resultan muy cómodos. Las propiedades `firstChild` y `lastChild` apuntan a los primeros y últimos elementos hijos o tienen el valor `null` para nodos sin hijos. De manera similar, `previousSibling` y `nextSibling` apuntan a nodos adyacentes, que son nodos con el mismo padre que aparecen inmediatamente antes o después del nodo en sí. Para un primer hijo, `previousSibling` será nulo, y para un último hijo, `nextSibling` será nulo. {{index "children property", "text node", element}} -También está la propiedad `children`, que es como `childNodes` pero contiene solo hijos de elementos (tipo 1), no otros tipos de nodos hijos. Esto puede ser útil cuando no estás interesado en nodos de texto. +También está la propiedad `children`, que es como `childNodes` pero contiene solo hijos elementos (tipo 1), no otros tipos de nodos hijos. Esto puede ser útil cuando no estás interesado en nodos de texto. {{index "función talksAbout", "recursión", [anidamiento, "de objetos"]}} -Cuando se trabaja con una estructura de datos anidada como esta, las funciones recursivas son frecuentemente útiles. La siguiente función examina un documento en busca de nodos de texto que contengan una cadena específica y devuelve `true` cuando ha encontrado uno: +Cuando se trabaja con una estructura de datos anidada como esta, las funciones recursivas suelen ser útiles. La siguiente función examina un documento en busca de nodos de texto que contengan una cadena específica y devuelve `true` cuando ha encontrado uno: {{id talksAbout}} ```{sandbox: "homepage"} -function talksAbout(node, cadena) { - if (node.nodeType == Node.ELEMENT_NODE) { - for (let child of node.childNodes) { - if (talksAbout(child, cadena)) { +function hablaSobre(nodo, cadena) { + if (nodo.nodeType == Node.ELEMENT_NODE) { + for (let hijo of nodo.childNodes) { + if (hablaSobre(hijo, cadena)) { return true; } } return false; - } else if (node.nodeType == Node.TEXT_NODE) { - return node.nodeValue.indexOf(cadena) > -1; + } else if (nodo.nodeType == Node.TEXT_NODE) { + return nodo.nodeValue.indexOf(cadena) > -1; } } -console.log(talksAbout(document.body, "libro")); +console.log(hablaSobre(document.body, "libro")); // → true ``` @@ -157,11 +157,11 @@ La propiedad `nodeValue` de un nodo de texto contiene la cadena de texto que rep {{index [DOM, consultas], "propiedad body", "codificación en duro", [espacios en blanco, "en HTML"]}} -Navegar por estos enlaces entre padres, hijos y hermanos a menudo es útil. Pero si queremos encontrar un nodo específico en el documento, llegar a él empezando por `document.body` y siguiendo un camino fijo de propiedades no es una buena idea. Hacerlo implica hacer suposiciones en nuestro programa sobre la estructura precisa del documento, una estructura que podrías querer cambiar más adelante. Otro factor complicador es que se crean nodos de texto incluso para los espacios en blanco entre nodos. La etiqueta `<body>` del documento de ejemplo no tiene solo tres hijos (`<h1>` y dos elementos `<p>`) sino que en realidad tiene siete: esos tres, más los espacios en blanco antes, después y entre ellos. +Navegar por estos enlaces entre padres, hijos y hermanos a menudo es útil. Pero si queremos encontrar un nodo específico en el documento, llegar a él empezando por `document.body` y siguiendo un camino fijo de propiedades no es una buena idea. Hacerlo implica hacer suposiciones en nuestro programa sobre la estructura precisa del documento, una estructura que podrías querer cambiar más adelante. Otro factor que complica el asunto es que se crean nodos de texto incluso para los espacios en blanco entre nodos. La etiqueta `<body>` del documento de ejemplo no tiene solo tres hijos (`<h1>` y dos elementos `<p>`) sino que en realidad tiene siete: esos tres, más los espacios en blanco antes, después y entre ellos. {{index "problema de búsqueda", "atributo href", "método getElementsByTagName"}} -Por lo tanto, si queremos obtener el atributo `href` del enlace en ese documento, no queremos decir algo como "Obtener el segundo hijo del sexto hijo del cuerpo del documento". Sería mejor si pudiéramos decir "Obtener el primer enlace en el documento". Y podemos hacerlo. +Por lo tanto, si quisiéramos obtener el atributo `href` del enlace en ese documento, no nos gustaría tener que decir algo como "Obtener el segundo hijo del sexto hijo del cuerpo del documento". Sería mejor si pudiéramos decir "Obtener el primer enlace en el documento". Y podemos hacerlo. ```{sandbox: "homepage"} let enlace = document.body.getElementsByTagName("a")[0]; @@ -170,7 +170,7 @@ console.log(enlace.href); {{index "nodo hijo"}} -Todos los nodos de elemento tienen un método `getElementsByTagName`, que recoge todos los elementos con el nombre de etiqueta dado que son descendientes (hijos directos o indirectos) de ese nodo y los devuelve como un ((objeto similar a un array)). +Todos los nodos elemento tienen un método `getElementsByTagName`, que recoge todos los elementos con el nombre de etiqueta dado que son descendientes (hijos directos o indirectos) de ese nodo y los devuelve como un ((objeto parecido a un array)). {{index "atributo id", "método getElementById"}} @@ -181,8 +181,8 @@ Para encontrar un nodo específico _único_, puedes darle un atributo `id` y usa <p><img id="gertrudis" src="img/ostrich.png"></p> <script> - let ostrich = document.getElementById("gertrudis"); - console.log(ostrich.src); + let avestruz = document.getElementById("gertrudis"); + console.log(avestruz.src); </script> ``` @@ -194,7 +194,7 @@ Un tercer método similar es `getElementsByClassName`, que, al igual que `getEle {{index "efecto secundario", "método removeChild", "método appendChild", "método insertBefore", ["construcción", DOM], ["modificación", DOM]}} -Casi todo se puede cambiar en la estructura de datos del DOM. La forma del árbol del documento se puede modificar cambiando las relaciones padre-hijo. Los nodos tienen un método `remove` para removerlos de su nodo padre actual. Para añadir un nodo hijo a un nodo de elemento, podemos usar `appendChild`, que lo coloca al final de la lista de hijos, o `insertBefore`, que inserta el nodo dado como primer argumento antes del nodo dado como segundo argumento. +Casi todo se puede cambiar en la estructura de datos del DOM. La forma del árbol del documento se puede modificar cambiando las relaciones padre-hijo. Los nodos tienen un método `remove` para eliminarlos de su nodo padre actual. Para añadir un nodo hijo a un nodo elemento, podemos usar `appendChild`, que lo coloca al final de la lista de hijos, o `insertBefore`, que inserta el nodo dado como primer argumento antes del nodo dado como segundo argumento. ```{lang: html} <p>Uno</p> @@ -224,19 +224,19 @@ Digamos que queremos escribir un script que reemplace todas las ((imágenes)) (e Esto implica no solo eliminar las imágenes sino agregar un nuevo nodo de texto para reemplazarlas. ```{lang: html} -<p>The <img src="img/cat.png" alt="Cat"> in the - <img src="img/hat.png" alt="Hat">.</p> +<p>El <img src="img/cat.png" alt="Gato"> en el + <img src="img/hat.png" alt="Sombrero">.</p> -<p><button onclick="replaceImages()">Replace</button></p> +<p><button onclick="reemplazarImágenes()">Replace</button></p> <script> - function replaceImages() { - let images = document.body.getElementsByTagName("img"); - for (let i = images.length - 1; i >= 0; i--) { - let image = images[i]; - if (image.alt) { - let text = document.createTextNode(image.alt); - image.parentNode.replaceChild(text, image); + function reemplazarImágenes() { + let imágenes = document.body.getElementsByTagName("img"); + for (let i = imágenes.length - 1; i >= 0; i--) { + let imagen = imágenes[i]; + if (imagen.alt) { + let texto = document.createTextNode(imagen.alt); + imagen.parentNode.replaceChild(texto, imagen); } } } @@ -256,8 +256,8 @@ El bucle que recorre las imágenes comienza al final de la lista. Esto es necesa Si quieres tener una colección _sólida_ de nodos, en lugar de una en vivo, puedes convertir la colección en un array real llamando a `Array.from`. ``` -let arrayish = {0: "uno", 1: "dos", length: 2}; -let array = Array.from(arrayish); +let arrayoso = {0: "uno", 1: "dos", length: 2}; +let array = Array.from(arrayoso); console.log(array.map(s => s.toUpperCase())); // → ["UNO", "DOS"] ``` @@ -274,18 +274,18 @@ El siguiente ejemplo define una utilidad `elt`, que crea un nodo de elemento y t ```{lang: html} <blockquote id="quote"> - Ningún libro puede considerarse terminado. Mientras trabajamos en él aprendemos - lo suficiente como para encontrarlo inmaduro en el momento en que lo dejamos. + Ningún libro puede considerarse jamás terminado. Mientras trabajamos en él aprendemos + lo suficiente como para considerarlo inmaduro en el momento en que lo dejamos. </blockquote> <script> - function elt(type, ...children) { - let node = document.createElement(type); - for (let child of children) { - if (typeof child != "string") node.appendChild(child); - else node.appendChild(document.createTextNode(child)); + function elt(tipo, ...hijos) { + let nodo = document.createElement(tipo); + for (let hijo of hijos) { + if (typeof hijo != "string") nodo.appendChild(hijo); + else nodo.appendChild(document.createTextNode(hijo)); } - return node; + return nodo; } document.getElementById("quote").appendChild( @@ -309,21 +309,21 @@ if}} {{index "atributo href", [DOM, atributos]}} -Algunos ((atributo))s de elementos, como `href` para enlaces, pueden ser accedidos a través de una propiedad con el mismo nombre en el objeto ((DOM)) del elemento. Este es el caso para la mayoría de atributos estándar comúnmente usados. +Algunos ((atributo))s de elementos, como `href` para enlaces, pueden ser accedidos a través de una propiedad con el mismo nombre en el objeto ((DOM)) del elemento. Este es el caso para la mayoría de atributos estándar más frecuentes. {{index "atributo data", "método getAttribute", "método setAttribute", atributo}} -HTML te permite establecer cualquier atributo que desees en los nodos. Esto puede ser útil porque te permite almacenar información adicional en un documento. Para leer o cambiar atributos personalizados, que no están disponibles como propiedades regulares del objeto, debes usar los métodos `getAttribute` y `setAttribute`. +HTML te permite establecer cualquier atributo que desees en los nodos. Esto puede ser útil porque te permite almacenar información adicional en un documento. Para leer o cambiar atributos personalizados, que no están disponibles como propiedades normales del objeto, debes usar los métodos `getAttribute` y `setAttribute`. ```{lang: html} <p data-classified="secreto">El código de lanzamiento es 00000000.</p> <p data-classified="no clasificado">Tengo dos pies.</p> <script> - let paras = document.body.getElementsByTagName("p"); - for (let para of Array.from(paras)) { - if (para.getAttribute("data-classified") == "secreto") { - para.remove(); + let párrafos = document.body.getElementsByTagName("p"); + for (let párrafo of Array.from(párrafos)) { + if (párrafo.getAttribute("data-classified") == "secreto") { + párrafo.remove(); } } </script> @@ -333,13 +333,13 @@ Se recomienda prefijar los nombres de estos atributos inventados con `data-` par {{index "método getAttribute", "método setAttribute", "propiedad className", "atributo class"}} -Existe un atributo comúnmente usado, `class`, que es una ((palabra clave)) en el lenguaje JavaScript. Por razones históricas—algunas implementaciones antiguas de JavaScript no podían manejar nombres de propiedades que coincidieran con palabras clave—la propiedad utilizada para acceder a este atributo se llama `className`. También puedes acceder a él con su nombre real, `"class"`, utilizando los métodos `getAttribute` y `setAttribute`. +Existe un atributo comúnmente usado, `class`, que es una ((palabra clave)) en el lenguaje JavaScript. Por razones históricas —algunas implementaciones antiguas de JavaScript no podían manejar nombres de propiedades que coincidieran con palabras clave— la propiedad utilizada para acceder a este atributo se llama `className`. También puedes acceder a él con su nombre real, `"class"`, utilizando los métodos `getAttribute` y `setAttribute`. ## Diseño {{index "diseño", "elemento de bloque", "elemento en línea", "etiqueta `p` (HTML)", "etiqueta `h1` (HTML)", "etiqueta `a` (HTML)", "etiqueta `strong` (HTML)"}} -Puede que hayas notado que diferentes tipos de elementos se disponen de manera diferente. Algunos, como párrafos (`<p>`) o encabezados (`<h1>`), ocupan todo el ancho del documento y se muestran en líneas separadas. Estos se llaman elementos de _bloque_. Otros, como enlaces (`<a>`) o el elemento `<strong>`, se muestran en la misma línea que el texto que los rodea. A estos elementos se les llama elementos _en línea_. +Puede que hayas notado que diferentes tipos de elementos se disponen de manera diferente. Algunos, como párrafos (`<p>`) o encabezados (`<h1>`), ocupan todo el ancho del documento y se muestran en líneas separadas. A estos elementos los llamamos elementos de _bloque_. Otros, como enlaces (`<a>`) o el elemento `<strong>`, se muestran en la misma línea que el texto que los rodea. A estos elementos se les llama elementos _en línea_. {{index dibujo}} @@ -357,10 +357,10 @@ De manera similar, `clientWidth` y `clientHeight` te dan el tamaño del espacio </p> <script> - let para = document.body.getElementsByTagName("p")[0]; - console.log("clientHeight:", para.clientHeight); + let párrafo = document.body.getElementsByTagName("p")[0]; + console.log("clientHeight:", párrafo.clientHeight); // → 19 - console.log("offsetHeight:", para.offsetHeight); + console.log("offsetHeight:", párrafo.offsetHeight); // → 25 </script> ``` @@ -385,34 +385,34 @@ Diseñar un documento puede ser bastante trabajo. En aras de la rapidez, los mot {{index "side effect", "optimización", benchmark}} -Un programa que alterna repetidamente entre la lectura de información de diseño del DOM y el cambio del DOM provoca que se realicen muchas computaciones de diseño y, en consecuencia, se ejecute muy lentamente. El siguiente código es un ejemplo de esto. Contiene dos programas diferentes que construyen una línea de caracteres _X_ de 2,000 píxeles de ancho y mide el tiempo que lleva cada uno. +Un programa que alterna repetidamente entre la lectura de información de diseño del DOM y el cambio del DOM provoca que se realicen muchos cálculos de diseño y, en consecuencia, se ejecute muy lentamente. El siguiente código es un ejemplo de esto. Contiene dos programas diferentes que construyen una línea de caracteres _X_ de 2,000 píxeles de ancho y mide el tiempo que lleva cada uno. ```{lang: html, test: nonumbers} <p><span id="one"></span></p> <p><span id="two"></span></p> <script> - function time(name, action) { - let start = Date.now(); // Tiempo actual en milisegundos - action(); - console.log(name, "tomó", Date.now() - start, "ms"); + function tiempo(nombre, acción) { + let comienzo = Date.now(); // Tiempo actual en milisegundos + acción(); + console.log("El método", nombre, "ha tomado", Date.now() - comienzo, "ms"); } - time("ingenuo", () => { - let target = document.getElementById("one"); - while (target.offsetWidth < 2000) { - target.appendChild(document.createTextNode("X")); + tiempo("naíf", () => { + let objetivo = document.getElementById("one"); + while (objetivo.offsetWidth < 2000) { + objetivo.appendChild(document.createTextNode("X")); } }); - // → ingenuo tomó 32 ms + // → El método naíf ha tomado 12 ms - time("astuto", function() { - let target = document.getElementById("two"); - target.appendChild(document.createTextNode("XXXXX")); - let total = Math.ceil(2000 / (target.offsetWidth / 5)); - target.firstChild.nodeValue = "X".repeat(total); + tiempo("inteligente", function() { + let objetivo = document.getElementById("two"); + objetivo.appendChild(document.createTextNode("XXXXX")); + let total = Math.ceil(2000 / (objetivo.offsetWidth / 5)); + objetivo.firstChild.nodeValue = "X".repeat(total); }); - // → astuto tomó 1 ms + // → El método inteligente ha tomado 1 ms </script> ``` @@ -441,21 +441,21 @@ if}} {{index "borde (CSS)", "color (CSS)", CSS, "carácter dos puntos"}} -Un atributo de estilo puede contener uno o más _((declaración))es_, que son una propiedad (como `color`) seguida de dos puntos y un valor (como `verde`). Cuando hay más de una declaración, deben separarse por ((punto y coma))s, como en `"color: rojo; border: ninguno"`. +Un atributo de estilo puede contener una _((declaración))_ o más de una, siendo una propiedades (como `color`) seguidas de dos puntos y un valor (como `green`). Cuando hay más de una declaración, deben separarse por ((punto y coma)), como en `"color: red; border: none"`. {{index "display (CSS)", "diseño"}} Muchos aspectos del documento pueden ser influenciados por el estilo. Por ejemplo, la propiedad `display` controla si un elemento se muestra como un bloque o como un elemento en línea. ```{lang: html} -Este texto se muestra de forma <strong>en línea</strong>, -<strong style="display: block">como un bloque</strong>, y -<strong style="display: none">no del todo</strong>. +Este texto se muestra <strong>en línea</strong>, +<strong style="display: block"> este como un bloque</strong> y +<strong style="display: none">este no se muestra</strong>. ``` {{index "elemento oculto"}} -La etiqueta `block` terminará en su propia línea ya que los ((elementos de bloque)) no se muestran en línea con el texto que los rodea. La última etiqueta no se muestra en absoluto: `display: none` evita que un elemento aparezca en la pantalla. Esta es una forma de ocultar elementos. A menudo es preferible a eliminarlos completamente del documento porque facilita revelarlos nuevamente más tarde. +La etiqueta `block` terminará en su propia línea ya que los ((elementos de bloque)) no se muestran en línea con el texto que los rodea. La última etiqueta no se muestra: `display: none` evita que un elemento aparezca en la pantalla. Esta es una forma de ocultar elementos. A menudo es preferible a eliminarlos completamente del documento porque facilita revelarlos nuevamente más tarde. {{if book @@ -468,20 +468,20 @@ if}} El código JavaScript puede manipular directamente el estilo de un elemento a través de la propiedad `style` del elemento. Esta propiedad contiene un objeto que tiene propiedades para todas las posibles propiedades de estilo. Los valores de estas propiedades son cadenas de texto, a las cuales podemos escribir para cambiar un aspecto particular del estilo del elemento. ```{lang: html} -<p id="para" style="color: purple"> +<p id="parr" style="color: purple"> Texto bonito </p> <script> - let para = document.getElementById("para"); - console.log(para.style.color); - para.style.color = "magenta"; + let parr = document.getElementById("parr"); + console.log(parr.style.color); + parr.style.color = "magenta"; </script> ``` {{index "camel case", "capitalización", "carácter guion", "font-family (CSS)"}} -Algunos nombres de propiedades de estilo contienen guiones, como `font-family`. Debido a que trabajar con estos nombres de propiedades en JavaScript es incómodo (tendrías que decir `style["font-family"]`), los nombres de las propiedades en el objeto `style` para tales propiedades tienen los guiones eliminados y las letras posterior a ellos en mayúscula (`style.fontFamily`). +Algunos nombres de propiedades de estilo contienen guiones, como `font-family`. Como trabajar con estos nombres de propiedades en JavaScript es raro (tendrías que decir `style["font-family"]`), los nombres de las propiedades en el objeto `style` para tales propiedades tienen los guiones eliminados y las letras posteriores a ellos en mayúscula (`style.fontFamily`). ## Estilos en cascada @@ -499,7 +499,7 @@ El sistema de estilos para HTML se llama ((CSS)), por sus siglas en inglés, _Ca color: gray; } </style> -<p>Ahora el <strong>texto fuerte</strong> es cursiva y gris.</p> +<p>Ahora el <strong>texto fuerte</strong> está escrito en cursiva y de color gris.</p> ``` {{index "regla (CSS)", "font-weight (CSS)", overlay}} @@ -508,7 +508,7 @@ El _((cascada))_ en el nombre se refiere al hecho de que múltiples reglas de es {{index "estilo (etiqueta HTML)", "atributo de estilo"}} -Cuando múltiples reglas definen un valor para la misma propiedad, la regla más recientemente leída obtiene una ((precedencia)) más alta y gana. Por lo tanto, si la regla en la etiqueta `<style>` incluyera `font-weight: normal`, contradiciendo la regla predeterminada de `font-weight`, el texto sería normal, _no_ negrita. Los estilos en un atributo `style` aplicado directamente al nodo tienen la mayor precedencia y siempre prevalecen. +Cuando hay varias reglas definiendo un valor para la misma propiedad, la última regla que se lee obtiene una ((precedencia)) más alta y gana. Por lo tanto, si la regla en la etiqueta `<style>` incluyera `font-weight: normal`, contradiciendo la regla predeterminada de `font-weight`, el texto sería normal, _no_ negrita. Los estilos en un atributo `style` aplicado directamente al nodo tienen la mayor precedencia y siempre prevalecen. {{index unicidad, "atributo class", "atributo id"}} @@ -531,7 +531,7 @@ p#main.a.b { {{index "regla (CSS)"}} -La regla de ((precedencia)) que favorece a la regla más recientemente definida se aplica solo cuando las reglas tienen la misma _((especificidad))_. La especificidad de una regla es una medida de qué tan precisamente describe los elementos que coinciden, determinada por el número y tipo (etiqueta, clase o ID) de aspectos de elementos que requiere. Por ejemplo, una regla que apunta a `p.a` es más específica que las reglas que apuntan a `p` o simplemente `.a` y, por lo tanto, tendría precedencia sobre ellas. +La regla de ((precedencia)) que favorece a la regla más recientemente definida se aplica solo cuando las reglas tienen la misma _((especificidad))_. La especificidad de una regla es una medida de cómo de precisamente describe los elementos que coinciden, determinada por el número y tipo (etiqueta, clase o ID) de aspectos de elementos que requiere. Por ejemplo, una regla que apunta a `p.a` es más específica que las reglas que apuntan a `p` o simplemente `.a` y, por lo tanto, tendría precedencia sobre ellas. {{index "direct child node"}} @@ -541,29 +541,29 @@ La notación `p > a {…}` aplica los estilos dados a todas las etiquetas `<a>` {{index complexity, CSS}} -No vamos a usar hojas de estilo demasiado en este libro. Entenderlas es útil cuando se programa en el navegador, pero son lo suficientemente complicadas como para justificar un libro aparte. +No vamos a usar hojas de estilo demasiado en este libro. Entenderlas es útil cuando se programa en el navegador, pero son lo suficientemente complicadas como para necesitar un libro aparte. {{index "domain-specific language", [DOM, querying]}} -La razón principal por la que introduje la sintaxis _((selector))_—la notación utilizada en las hojas de estilo para determinar a qué elementos se aplican un conjunto de estilos— es que podemos utilizar este mismo mini-lenguaje como una forma efectiva de encontrar elementos del DOM. +La razón principal por la que introduje la sintaxis _((selector))_ —la notación utilizada en las hojas de estilo para determinar a qué elementos se aplican un conjunto de estilos— es que podemos utilizar este mismo mini-lenguaje como una forma efectiva de encontrar elementos del DOM. {{index "querySelectorAll method", "NodeList type"}} -El método `querySelectorAll`, que está definido tanto en el objeto `document` como en los nodos de elementos, toma una cadena de selector y devuelve un `NodeList` que contiene todos los elementos que encuentra. +El método `querySelectorAll`, que está definido tanto en el objeto `document` como en los nodos elemento, toma una cadena de selector y devuelve un `NodeList` que contiene todos los elementos que encuentra. ```{lang: html} <p>And if you go chasing <span class="animal">rabbits</span></p> <p>And you know you're going to fall</p> -<p>Tell 'em a <span class="character">hookah smoking +<p>Tell 'em a <span class="personaje">hookah smoking <span class="animal">caterpillar</span></span></p> <p>Has given you the call</p> <script> - function count(selector) { + function contar(selector) { return document.querySelectorAll(selector).length; } - console.log(count("p")); // Todos los elementos <p> + console.log(contar("p")); // Todos los elementos <p> // → 4 console.log(count(".animal")); // Clase animal // → 2 @@ -576,7 +576,7 @@ El método `querySelectorAll`, que está definido tanto en el objeto `document` {{index "live data structure"}} -A diferencia de métodos como `getElementsByTagName`, el objeto devuelto por `querySelectorAll` _no_ es dinámico. No cambiará cuando cambies el documento. Aun así, no es un array real, por lo que necesitas llamar a `Array.from` si deseas tratarlo como tal. +A diferencia de métodos como `getElementsByTagName`, el objeto devuelto por `querySelectorAll` _no_ es dinámico. No cambiará cuando cambies el documento. Aun así, no es un array de verdad, por lo que tendrás que llamar a `Array.from` si quieres tratarlo como tal. {{index "querySelector method"}} @@ -588,7 +588,7 @@ El método `querySelector` (sin la parte `All`) funciona de manera similar. Este {{index "position (CSS)", "relative positioning", "top (CSS)", "left (CSS)", "absolute positioning"}} -La propiedad de estilo `position` influye en el diseño de una manera poderosa. De forma predeterminada, tiene un valor de `static`, lo que significa que el elemento se sitúa en su lugar normal en el documento. Cuando se establece en `relative`, el elemento sigue ocupando espacio en el documento, pero ahora las propiedades de estilo `top` y `left` se pueden usar para moverlo con respecto a ese lugar normal. Cuando `position` se establece en `absolute`, el elemento se elimina del flujo normal del documento, es decir, ya no ocupa espacio y puede superponerse con otros elementos. Además, sus propiedades de `top` y `left` se pueden usar para posicionarlo absolutamente con respecto a la esquina superior izquierda del elemento contenedor más cercano cuya propiedad de `position` no sea `static`, o con respecto al documento si no existe tal elemento contenedor. +La propiedad de estilo `position` influye en el diseño de manera importante. De forma predeterminada, tiene un valor de `static`, lo que significa que el elemento se sitúa en su lugar normal en el documento. Cuando se establece en `relative`, el elemento sigue ocupando espacio en el documento, pero ahora las propiedades de estilo `top` y `left` se pueden usar para moverlo con respecto a ese lugar normal. Cuando `position` se establece en `absolute`, el elemento se elimina del flujo normal del documento, es decir, ya no ocupa espacio y puede superponerse con otros elementos. Además, sus propiedades de `top` y `left` se pueden usar para posicionarlo absolutamente con respecto a la esquina superior izquierda del elemento contenedor más cercano cuya propiedad de `position` no sea `static`, o con respecto al documento si no existe tal elemento contenedor. {{index ["animación", "gato giratorio"]}} @@ -599,17 +599,17 @@ Podemos usar esto para crear una animación. El siguiente documento muestra una <img src="img/cat.png" style="position: relative"> </p> <script> - let cat = document.querySelector("img"); - let angle = Math.PI / 2; - function animate(time, lastTime) { - if (lastTime != null) { - angle += (time - lastTime) * 0.001; + let gato = document.querySelector("img"); + let ángulo = Math.PI / 2; + function animar(esteMomento, últimaVez) { + if (últimaVez != null) { + ángulo += (esteMomento - últimaVez) * 0.001; } - cat.style.top = (Math.sin(angle) * 20) + "px"; - cat.style.left = (Math.cos(angle) * 200) + "px"; - requestAnimationFrame(newTime => animate(newTime, time)); + gato.style.top = (Math.sin(ángulo) * 20) + "px"; + gato.style.left = (Math.cos(ángulo) * 200) + "px"; + requestAnimationFrame(nuevoMomento => animar(nuevoMomento, esteMomento)); } - requestAnimationFrame(animate); + requestAnimationFrame(animar); </script> ``` @@ -617,7 +617,7 @@ Podemos usar esto para crear una animación. El siguiente documento muestra una La flecha gris muestra la trayectoria a lo largo de la cual se mueve la imagen. -{{figure {url: "img/cat-animation.png", alt: "A diagram showing a picture of a cat with a circular arrow indicating its motion", width: "8cm"}}} +{{figure {url: "img/cat-animation.png", alt: "Un diagrama mostrando una imagen de un gato con una flecha circular que indica su movimiento.", width: "8cm"}}} if}} @@ -629,43 +629,48 @@ Nuestra imagen está centrada en la página y tiene una `posición` de `relative {{id animationFrame}} -El script utiliza `requestAnimationFrame` para programar la ejecución de la función `animar` siempre que el navegador esté listo para repintar la pantalla. La función `animar` a su vez vuelve a llamar a `requestAnimationFrame` para programar la siguiente actualización. Cuando la ventana del navegador (o pestaña) está activa, esto provocará que las actualizaciones ocurran a una velocidad de aproximadamente 60 por segundo, lo que suele producir una animación atractiva. +El script utiliza `requestAnimationFrame` para programar la ejecución de la función `animar` siempre que el navegador esté listo para repintar la pantalla. La función `animar` a su vez vuelve a llamar a `requestAnimationFrame` para programar la siguiente actualización. Cuando la ventana del navegador (o pestaña) está activa, esto provocará que las actualizaciones ocurran a una velocidad de aproximadamente 60 actualizaciones por segundo, lo que suele producir una animación bastante vistosa. + +{{note "**N. del T.:** La función `requestAnimationFrame()` es una función estándar en JavaScript que forma parte de la especificación de la Web API, diseñada para facilitar la creación de animaciones en el navegador de manera eficiente. Esta solicita al navegador que ejecute una función antes del próximo repintado de pantalla, pasando como argumento a dicha función la marca de tiempo actual."}} {{index "línea" de tiempo, bloqueo}} -Si simplemente actualizáramos el DOM en un bucle, la página se congelaría y nada aparecería en la pantalla. Los navegadores no actualizan su pantalla mientras se ejecuta un programa JavaScript, ni permiten ninguna interacción con la página. Por eso necesitamos `requestAnimationFrame` — le indica al navegador que hemos terminado por ahora, y puede continuar haciendo las cosas que hacen los navegadores, como actualizar la pantalla y responder a las acciones del usuario. +Si simplemente actualizáramos el DOM en un bucle, la página se congelaría y no aparecería nada en la pantalla. Los navegadores no actualizan su pantalla mientras se ejecuta un programa JavaScript, ni permiten ninguna interacción con la página. Por eso necesitamos `requestAnimationFrame` —le indica al navegador que hemos terminado por ahora, y puede continuar haciendo las cosas que hacen los navegadores, como actualizar la pantalla y responder a las acciones del usuario. {{index "animación suave"}} -La función de animación recibe el ((tiempo)) actual como argumento. Para asegurar que el movimiento del gato por milisegundo sea estable, basa la velocidad a la que cambia el ángulo en la diferencia entre el tiempo actual y el último tiempo en que se ejecutó la función. Si simplemente moviera el ángulo por una cantidad fija por paso, el movimiento se interrumpiría si, por ejemplo, otra tarea pesada que se está ejecutando en la misma computadora impidiera que la función se ejecutara durante una fracción de segundo. +La función de animación recibe el ((tiempo)) actual como argumento. Para asegurar que el movimiento del gato por milisegundo sea estable, basa la velocidad a la que cambia el ángulo en la diferencia entre el momento actual y el último momento en que se ejecutó la función. Si simplemente modificara el ángulo en una cantidad fija por paso, el movimiento se interrumpiría si, por ejemplo, otra tarea pesada que se está ejecutando en la misma computadora impidiera que la función se ejecutara durante una fracción de segundo. {{index "función Math.cos", "función Math.sin", coseno, seno, "trigonometría"}} {{id sin_cos}} -Moverse en ((círculos)) se hace utilizando las funciones trigonométricas `Math.cos` y `Math.sin`. Para aquellos que no estén familiarizados con ellas, las presentaré brevemente ya que ocasionalmente las utilizaremos en este libro. +Moverse en ((círculos)) es algo que puede hacerse utilizando las funciones trigonométricas `Math.cos` y `Math.sin`. Para aquellos que no estén familiarizados con ellas, las presentaré brevemente ya que ocasionalmente las utilizaremos en este libro. {{index coordenadas, pi}} -`Math.cos` y `Math.sin` son útiles para encontrar puntos que se encuentran en un círculo alrededor del punto (0,0) con un radio de uno. Ambas funciones interpretan su argumento como la posición en este círculo, con cero denotando el punto en el extremo derecho del círculo, avanzando en el sentido de las agujas del reloj hasta que 2π (aproximadamente 6,28) nos ha llevado alrededor de todo el círculo. `Math.cos` te indica la coordenada x del punto que corresponde a la posición dada, y `Math.sin` devuelve la coordenada y. Las posiciones (o ángulos) mayores que 2π o menores que 0 son válidos, la rotación se repite de manera que _a_+2π se refiere al mismo ((ángulo)) que _a_. +`Math.cos` y `Math.sin` son útiles para encontrar puntos que se encuentran en un círculo alrededor del punto (0,0) con un radio de longitud uno. Ambas funciones interpretan su argumento como la posición en este círculo, correspondiendo el 0 con el punto en el extremo derecho del círculo, avanzando en el sentido de las agujas del reloj hasta llegamos a 2π (aproximadamente 6,28) habiendo recorrido así todo el círculo. `Math.cos` te indica la coordenada x del punto que corresponde a la posición dada, y `Math.sin` devuelve la coordenada y. Las posiciones (o ángulos) mayores que 2π o menores que 0 son válidos, la rotación se repite de manera que _a_+2π se refiere al mismo ((ángulo)) que _a_. + +{{note "**N. del T.:** normalmente, las funciones `cos` y `sin` en matemáticas, al aumentar su argumento desde 0 hasta 2π, describen una circunferencia en el sentido **opuesto** al de las agujas del reloj. No obstante, en tal caso, las coordenadas están expresadas de modo que el eje vertical apunta hacia arriba. En el caso de las funciones `cos` y `sin` que estamos usando en este código en JavaScript, el eje vertical apunta hacia abajo (pues se mide respecto del `top`), por lo que el sentido de rotación es el opuesto al usual que encontramos en matemáticas."}} + {{index "constante PI"}} -Esta unidad para medir ángulos se llama ((radianes)) — un círculo completo son 2π radianes, similar a cómo son 360 grados al medir en grados. La constante π está disponible como `Math.PI` en JavaScript. +Esta unidad para medir ángulos se llama ((radián)) —un círculo completo comprende 2π radianes, igual que con los 360 grados al medir en grados. La constante π está disponible como `Math.PI` en JavaScript. {{figure {url: "img/cos_sin.svg", alt: "Diagrama que muestra el uso del coseno y el seno para calcular coordenadas. Se muestra un círculo con radio 1 con dos puntos en él. El ángulo desde el lado derecho del círculo hasta el punto, en radianes, se utiliza para calcular la posición de cada punto usando 'cos(ángulo)' para la distancia horizontal desde el centro del círculo y sin(ángulo) para la distancia vertical.", width: "6cm"}}} {{index "variable contador", "función Math.sin", "top (CSS)", "función Math.cos", "left (CSS)", elipse}} -El código de animación del gato mantiene un contador, `angle`, para el ángulo actual de la animación e incrementa el mismo cada vez que se llama la función `animate`. Luego puede usar este ángulo para calcular la posición actual del elemento de imagen. El estilo `top` es calculado con `Math.sin` y multiplicado por 20, que es el radio vertical de nuestra elipse. El estilo `left` se basa en `Math.cos` y multiplicado por 200 para que la elipse sea mucho más ancha que alta. +El código de animación del gato mantiene un contador, `ángulo`, para el ángulo actual de la animación e incrementa el mismo cada vez que se llama la función `animar`. Luego puede usar este ángulo para calcular la posición actual del elemento de imagen. El estilo `top` es calculado con `Math.sin` y multiplicado por 20, que es el radio vertical de nuestra elipse. El estilo `left` se basa en `Math.cos` y multiplicado por 200 para que la elipse sea mucho más ancha que alta. {{index "unidad (CSS)"}} -Ten en cuenta que los estilos usualmente necesitan _unidades_. En este caso, tenemos que añadir `"px"` al número para indicarle al navegador que estamos contando en ((píxeles)) (en lugar de centímetros, "ems" u otras unidades). Esto es fácil de olvidar. Usar números sin unidades resultará en que tu estilo sea ignorado — a menos que el número sea 0, lo cual siempre significa lo mismo, independientemente de su unidad. +Ten en cuenta que los estilos normalmente necesitan _unidades_. En este caso, tenemos que añadir `"px"` al número para indicarle al navegador que estamos contando en ((píxeles)) (en lugar de centímetros, "ems" u otras unidades). Esto es fácil de olvidar. Usar números sin unidades resultará en que tu estilo sea ignorado — a menos que el número sea 0, lo cual siempre representa lo mismo, independientemente de su unidad. ## Resumen -Los programas de JavaScript pueden inspeccionar e interferir con el documento que el navegador está mostrando a través de una estructura de datos llamada el DOM. Esta estructura de datos representa el modelo del documento del navegador, y un programa de JavaScript puede modificarlo para cambiar el documento visible. +Los programas de JavaScript pueden inspeccionar e interferir con el documento que el navegador está mostrando a través de una estructura de datos conocida como el DOM. Esta estructura de datos representa el modelo del documento del navegador, y un programa de JavaScript puede modificarlo para cambiar el documento visible. El DOM está organizado como un árbol, en el cual los elementos están dispuestos jerárquicamente de acuerdo a la estructura del documento. Los objetos que representan elementos tienen propiedades como `parentNode` y `childNodes`, las cuales pueden ser usadas para navegar a través de este árbol. @@ -684,9 +689,9 @@ Una tabla HTML se construye con la siguiente estructura de etiquetas: ```{lang: html} <table> <tr> - <th>nombre</th> - <th>altura</th> - <th>lugar</th> + <th>name</th> + <th>height</th> + <th>place</th> </tr> <tr> <td>Kilimanjaro</td> @@ -700,7 +705,7 @@ Una tabla HTML se construye con la siguiente estructura de etiquetas: Dado un conjunto de datos de montañas, un array de objetos con propiedades `name`, `height`, y `place`, genera la estructura DOM para una tabla que enumera los objetos. Debería haber una columna por clave y una fila por objeto, además de una fila de encabezado con elementos `<th>` en la parte superior, enumerando los nombres de las columnas. -Escribe esto de manera que las columnas se deriven automáticamente de los objetos, tomando los nombres de las propiedades del primer objeto en los datos. +Escribe esto de manera que las columnas se objengan automáticamente de los objetos, tomando los nombres de las propiedades del primer objeto en los datos. Muestra la tabla resultante en el documento agregándola al elemento que tenga un atributo `id` de `"mountains"`. @@ -738,7 +743,7 @@ Puedes usar `document.createElement` para crear nuevos nodos de elementos, `docu {{index "Object.keys function"}} -Querrás iterar sobre los nombres de las claves una vez para completar la fila superior y luego nuevamente para cada objeto en el array para construir las filas de datos. Para obtener un array de nombres de claves del primer objeto, `Object.keys` será útil. +Querrás iterar sobre los nombres de las claves una vez para completar la fila superior y luego nuevamente para cada objeto en el array para construir las filas de datos. Para obtener un array de nombres de claves del primer objeto, te será útil el método `Object.keys`. {{index "getElementById method", "querySelector method"}} @@ -783,11 +788,11 @@ if}} {{index "getElementsByTagName method", recursion}} -La solución es más fácil de expresar con una función recursiva, similar a la [función `talksAbout`](dom#talksAbout) definida anteriormente en este capítulo. +La solución es más fácil de expresar con una función recursiva, similar a la [función `hablaSobre`](dom#talksAbout) definida anteriormente en este capítulo. {{index concatenation, "concat method", closure}} -Puedes llamar a `byTagname` a sí misma de manera recursiva, concatenando los arrays resultantes para producir la salida. O puedes crear una función interna que se llame a sí misma de manera recursiva y que tenga acceso a un enlace de array definido en la función externa, al cual puede agregar los elementos coincidentes que encuentre. No olvides llamar a la función interna una vez desde la función externa para iniciar el proceso. +Puedes llamar a la misma `byTagname` de manera recursiva, concatenando los arrays resultantes para producir la salida. O puedes crear una función interna que se llame a sí misma de manera recursiva y que tenga acceso a una asociación de array definida en la función externa, a la cual puede agregar los elementos coincidentes que encuentre. No olvides llamar a la función interna una vez desde la función externa para iniciar el proceso. {{index "nodeType property", "ELEMENT_NODE code"}} @@ -836,6 +841,6 @@ if}} {{hint -`Math.cos` y `Math.sin` miden los ángulos en radianes, donde un círculo completo es 2π. Para un ángulo dado, puedes obtener el ángulo opuesto sumando la mitad de este, que es `Math.PI`. Esto puede ser útil para poner el sombrero en el lado opuesto de la órbita. +`Math.cos` y `Math.sin` miden los ángulos en radianes, donde una circunferencia completa consta de 2π. Para un ángulo dado, puedes obtener el ángulo opuesto sumando `Math.PI`, que es la mitad de los radianes de los que consta una circunferencia. Esto puede ser útil para poner el sombrero en el lado opuesto de la órbita. hint}} \ No newline at end of file diff --git a/html/14_dom.html b/html/14_dom.html index 50d910d5..9fc3d79b 100644 --- a/html/14_dom.html +++ b/html/14_dom.html @@ -14,19 +14,19 @@ <h1>El Modelo de Objetos del Documento</h1> <blockquote> -<p><a class="p_ident" id="p-nAKastT8US" href="#p-nAKastT8US" tabindex="-1" role="presentation"></a>¡Qué mal! ¡La misma vieja historia! Una vez que has terminado de construir tu casa, te das cuenta de que has aprendido accidentalmente algo que realmente deberías haber sabido antes de comenzar.</p> +<p><a class="p_ident" id="p-0iPv3akwQt" href="#p-0iPv3akwQt" tabindex="-1" role="presentation"></a>¡Tanto peor! ¡Otra vez la vieja historia! Cuando uno ha acabado de construir su casa advierte que, mientras la construía, ha aprendido, sin darse cuenta, algo que tendría que haber sabido absolutamente antes de comenzar a construir.</p> <footer>Friedrich Nietzsche, <cite>Más allá del bien y del mal</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_14.jpg" alt="Ilustración que muestra un árbol con letras, imágenes y engranajes colgando de sus ramas"></figure> -<p><a class="p_ident" id="p-VW462YK2br" href="#p-VW462YK2br" tabindex="-1" role="presentation"></a>Cuando abres una página web, tu navegador recupera el texto HTML de la página y lo analiza, de manera similar a como nuestro analizador de <a href="12_language.html#parsing">Capítulo 12</a> analizaba programas. El navegador construye un modelo de la estructura del documento y utiliza este modelo para dibujar la página en la pantalla.</p> +<p><a class="p_ident" id="p-VW462YK2br" href="#p-VW462YK2br" tabindex="-1" role="presentation"></a>Cuando abres una página web, tu navegador recupera el texto HTML de la página y lo analiza, de manera similar a como nuestro analizador del <a href="12_language.html#parsing">Capítulo 12</a> analizaba programas. El navegador construye un modelo de la estructura del documento y utiliza este modelo para dibujar la página en la pantalla.</p> -<p><a class="p_ident" id="p-Tut29UcFsf" href="#p-Tut29UcFsf" tabindex="-1" role="presentation"></a>Esta representación del documento es uno de los juguetes que un programa JavaScript tiene disponible en su caja de arena. Es una estructura de datos que puedes leer o modificar. Actúa como una estructura de datos <em>en vivo</em>: cuando se modifica, la página en la pantalla se actualiza para reflejar los cambios.</p> +<p><a class="p_ident" id="p-Tut29UcFsf" href="#p-Tut29UcFsf" tabindex="-1" role="presentation"></a>Esta representación del documento es uno de los recursos que un programa JavaScript tiene disponible en su sandbox. Es una estructura de datos que puedes leer o modificar. Actúa como una estructura de datos <em>en vivo</em>: cuando se modifica, la página en la pantalla se actualiza para reflejar los cambios.</p> <h2><a class="h_ident" id="h-F42NKMypcy" href="#h-F42NKMypcy" tabindex="-1" role="presentation"></a>Estructura del documento</h2> -<p><a class="p_ident" id="p-tEZIS6bhYG" href="#p-tEZIS6bhYG" tabindex="-1" role="presentation"></a>Puedes imaginar un documento HTML como un conjunto anidado de cajas. Etiquetas como <code><body></code> y <code></body></code> encierran otras etiquetas, que a su vez contienen otras etiquetas o texto. Aquí está el documento de ejemplo del <a href="13_browser.html">capítulo anterior</a>:</p> +<p><a class="p_ident" id="p-tEZIS6bhYG" href="#p-tEZIS6bhYG" tabindex="-1" role="presentation"></a>Puedes imaginar un documento HTML como un conjunto anidado de cajas. Etiquetas como <code><body></code> y <code></body></code> encierran otras etiquetas que, a su vez, contienen otras etiquetas o texto. Aquí está el documento de ejemplo del <a href="13_browser.html">capítulo anterior</a>:</p> <pre tabindex="0" class="snippet" data-language="html" data-sandbox="homepage"><a class="c_ident" id="c-TwHTaVSOV7" href="#c-TwHTaVSOV7" tabindex="-1" role="presentation"></a><span class="tok-meta"><!doctype html></span> <<span class="tok-typeName">html</span>> @@ -45,21 +45,21 @@ <h2><a class="h_ident" id="h-F42NKMypcy" href="#h-F42NKMypcy" tabindex="-1" role <p><a class="p_ident" id="p-Odqnxn1Di6" href="#p-Odqnxn1Di6" tabindex="-1" role="presentation"></a>La estructura de datos que el navegador utiliza para representar el documento sigue esta forma. Para cada caja, hay un objeto con el que podemos interactuar para saber cosas como qué etiqueta HTML representa y qué cajas y texto contiene. Esta representación se llama <em>Modelo de Objetos del Documento</em>, o DOM en resumen.</p> -<p><a class="p_ident" id="p-JHj4fJGf+a" href="#p-JHj4fJGf+a" tabindex="-1" role="presentation"></a>El enlace global <code>document</code> nos da acceso a estos objetos. Su propiedad <code>documentElement</code> se refiere al objeto que representa la etiqueta <code><html></code>. Dado que cada documento HTML tiene una cabeza y un cuerpo, también tiene propiedades <code>head</code> y <code>body</code>, que apuntan a esos elementos.</p> +<p><a class="p_ident" id="p-MzOvcidSdu" href="#p-MzOvcidSdu" tabindex="-1" role="presentation"></a>La variable global <code>document</code> nos da acceso a estos objetos. Su propiedad <code>documentElement</code> se refiere al objeto que representa la etiqueta <code><html></code>. Dado que cada documento HTML tiene una cabecera y un cuerpo, también tiene propiedades <code>head</code> y <code>body</code>, que apuntan a esos elementos.</p> <h2><a class="h_ident" id="h-haqdrtbJUM" href="#h-haqdrtbJUM" tabindex="-1" role="presentation"></a>Árboles</h2> -<p><a class="p_ident" id="p-Wb02JK1JGs" href="#p-Wb02JK1JGs" tabindex="-1" role="presentation"></a>Piensa en los árbol sintácticos del <a href="12_language.html#parsing">Capítulo 12</a> por un momento. Sus estructuras son sorprendentemente similares a la estructura de un documento de un navegador. Cada <em>nodo</em> puede referirse a otros nodos, <em>hijos</em>, que a su vez pueden tener sus propios hijos. Esta forma es típica de estructuras anidadas donde los elementos pueden contener subelementos que son similares a ellos mismos.</p> +<p><a class="p_ident" id="p-Wb02JK1JGs" href="#p-Wb02JK1JGs" tabindex="-1" role="presentation"></a>Piensa en los árboles sintácticos del <a href="12_language.html#parsing">Capítulo 12</a> por un momento. Sus estructuras son sorprendentemente similares a la estructura de un documento de un navegador. Cada <em>nodo</em> puede referirse a otros nodos, <em>hijos</em>, que a su vez pueden tener sus propios hijos. Esta forma es típica de estructuras anidadas donde los elementos pueden contener subelementos que son similares a ellos mismos.</p> -<p><a class="p_ident" id="p-Ix0SqSUNHG" href="#p-Ix0SqSUNHG" tabindex="-1" role="presentation"></a>Llamamos a una estructura de datos un <em>árbol</em> cuando tiene una estructura de ramificación, no tiene ciclos (un nodo no puede contenerse a sí mismo, directa o indirectamente), y tiene un <em>raíz</em> única y bien definida. En el caso del DOM, <code>document.<wbr>documentElement</code> sirve como la raíz.</p> +<p><a class="p_ident" id="p-tuGv0kjxWS" href="#p-tuGv0kjxWS" tabindex="-1" role="presentation"></a>Llamamos <em>árbol</em> a una estructura de datos cuando tiene una estructura de ramificación, no tiene ciclos (un nodo no puede contenerse a sí mismo, directa o indirectamente), y tiene una <em>raíz</em> única y bien definida. En el caso del DOM, <code>document.<wbr>documentElement</code> representa la raíz.</p> -<p><a class="p_ident" id="p-fm9xchFx+E" href="#p-fm9xchFx+E" tabindex="-1" role="presentation"></a>Los árboles son comunes en la informática. Además de representar estructuras recursivas como documentos HTML o programas, a menudo se utilizan para mantener conjuntos de datos ordenados porque los elementos generalmente se pueden encontrar o insertar de manera más eficiente en un árbol que en un arreglo plano.</p> +<p><a class="p_ident" id="p-oM+4GIiTSd" href="#p-oM+4GIiTSd" tabindex="-1" role="presentation"></a>Los árboles son comunes en informática. Además de representar estructuras recursivas como documentos HTML o programas, a menudo se utilizan para mantener conjuntos de datos ordenados porque los elementos generalmente se pueden encontrar o insertar de manera más eficiente en un árbol que en un array plano.</p> <p><a class="p_ident" id="p-wpb3ismqJB" href="#p-wpb3ismqJB" tabindex="-1" role="presentation"></a>Un árbol típico tiene diferentes tipos de nodos. El árbol de sintaxis para <a href="12_language.html">el lenguaje Egg</a> tenía identificadores, valores y nodos de aplicación. Los nodos de aplicación pueden tener hijos, mientras que los identificadores y valores son <em>hojas</em>, o nodos sin hijos.</p> -<p><a class="p_ident" id="p-dJHszzYpf7" href="#p-dJHszzYpf7" tabindex="-1" role="presentation"></a>Lo mismo ocurre para el DOM. Los nodos de los <em>elementos</em>, que representan etiquetas HTML, determinan la estructura del documento. Estos pueden tener nodo hijos. Un ejemplo de dicho nodo es <code>document.body</code>. Algunos de estos hijos pueden ser nodo hoja, como fragmentos de texto o nodos comentario.</p> +<p><a class="p_ident" id="p-dJHszzYpf7" href="#p-dJHszzYpf7" tabindex="-1" role="presentation"></a>Lo mismo ocurre para el DOM. Los nodos de los <em>elementos</em>, que representan etiquetas HTML, determinan la estructura del documento. Estos pueden tener nodos hijos. Un ejemplo de dicho nodo es <code>document.body</code>. Algunos de estos hijos pueden ser nodos hoja, como fragmentos de texto o nodos comentario.</p> -<p><a class="p_ident" id="p-Bc8agQfTFl" href="#p-Bc8agQfTFl" tabindex="-1" role="presentation"></a>Cada objeto de nodo del DOM tiene una propiedad <code>nodeType</code>, que contiene un código (número) que identifica el tipo de nodo. Los elementos tienen el código 1, que también se define como la propiedad constante <code>Node.<wbr>ELEMENT_NODE</code>. Los nodos de texto, que representan una sección de texto en el documento, obtienen el código 3 (<code>Node.TEXT_NODE</code>). Los comentarios tienen el código 8 (<code>Node.<wbr>COMMENT_NODE</code>).</p> +<p><a class="p_ident" id="p-Bc8agQfTFl" href="#p-Bc8agQfTFl" tabindex="-1" role="presentation"></a>Cada objeto de nodo del DOM tiene una propiedad <code>nodeType</code>, que contiene un código (un número) que identifica el tipo de nodo. Los elementos tienen el código 1, que también se define como la propiedad constante <code>Node.<wbr>ELEMENT_NODE</code>. Los nodos de texto, que representan una sección de texto en el documento, obtienen el código 3 (<code>Node.TEXT_NODE</code>). Los comentarios tienen el código 8 (<code>Node.<wbr>COMMENT_NODE</code>).</p> <p><a class="p_ident" id="p-GSzk68pPQY" href="#p-GSzk68pPQY" tabindex="-1" role="presentation"></a>Otra forma de visualizar nuestro árbol de documento es la siguiente:</p><figure><img src="img/html-tree.svg" alt="Diagrama que muestra el documento HTML como un árbol, con flechas de nodos padres a nodos hijos"></figure> @@ -67,72 +67,72 @@ <h2><a class="h_ident" id="h-haqdrtbJUM" href="#h-haqdrtbJUM" tabindex="-1" role <h2 id="estándar"><a class="h_ident" id="h-CFjVAEqJy7" href="#h-CFjVAEqJy7" tabindex="-1" role="presentation"></a>El estándar</h2> -<p><a class="p_ident" id="p-d46TYUYjnb" href="#p-d46TYUYjnb" tabindex="-1" role="presentation"></a>Usar códigos numéricos crípticos para representar tipos de nodos no es algo muy propio de JavaScript. Más adelante en este capítulo, veremos que otras partes de la interfaz del DOM también se sienten incómodas y extrañas. La razón de esto es que la interfaz del DOM no fue diseñada exclusivamente para JavaScript. Más bien, intenta ser una interfaz neutral en cuanto a lenguaje que también pueda utilizarse en otros sistemas, no solo para HTML, sino también para XML, que es un formato de datos genérico con una sintaxis similar a HTML.</p> +<p><a class="p_ident" id="p-d46TYUYjnb" href="#p-d46TYUYjnb" tabindex="-1" role="presentation"></a>Usar códigos numéricos crípticos para representar tipos de nodos no es algo muy propio de JavaScript. Más adelante en este capítulo, veremos que otras partes de la interfaz del DOM también son un poco incómodas y extrañas. La razón de esto es que la interfaz del DOM no fue diseñada exclusivamente para JavaScript. Más bien, intenta ser una interfaz neutral en cuanto a lenguaje que también pueda utilizarse en otros sistemas, no solo para HTML, sino también para XML, que es un formato de datos genérico con una sintaxis similar a HTML.</p> -<p><a class="p_ident" id="p-VodjM/oXmV" href="#p-VodjM/oXmV" tabindex="-1" role="presentation"></a>Esto es lamentable. Los estándares a menudo son útiles. Pero en este caso, la ventaja (consistencia entre lenguajes) no es tan convincente. Tener una interfaz que esté correctamente integrada con el lenguaje que estás utilizando te ahorrará más tiempo que tener una interfaz familiar en varios lenguajes.</p> +<p><a class="p_ident" id="p-XSaCXCQnkF" href="#p-XSaCXCQnkF" tabindex="-1" role="presentation"></a>Esto es una pena. Los estándares a menudo son útiles. Pero en este caso, la ventaja (consistencia entre lenguajes) no es tan convincente. Tener una interfaz que esté correctamente integrada con el lenguaje que estás utilizando te ahorrará más tiempo que tener una interfaz familiar para varios lenguajes.</p> -<p><a class="p_ident" id="p-xQFUpUkmCN" href="#p-xQFUpUkmCN" tabindex="-1" role="presentation"></a>Como ejemplo de esta mala integración, considera la propiedad <code>childNodes</code> que tienen los nodos de elementos en el DOM. Esta propiedad contiene un objeto similar a un array, con una propiedad <code>length</code> y propiedades etiquetadas por números para acceder a los nodos hijos. Pero es una instancia del tipo <code>NodeList</code>, no un array real, por lo que no tiene métodos como <code>slice</code> y <code>map</code>.</p> +<p><a class="p_ident" id="p-xQFUpUkmCN" href="#p-xQFUpUkmCN" tabindex="-1" role="presentation"></a>Como ejemplo de esta mala integración, considera la propiedad <code>childNodes</code> que tienen los nodos elemento en el DOM. Esta propiedad contiene un objeto similar a un array, con una propiedad <code>length</code> y propiedades etiquetadas por números para acceder a los nodos hijos. Pero es una instancia del tipo <code>NodeList</code>, no un array real, por lo que no tiene métodos como <code>slice</code> y <code>map</code>.</p> -<p><a class="p_ident" id="p-tzOiNEaMGp" href="#p-tzOiNEaMGp" tabindex="-1" role="presentation"></a>Luego, hay problemas que son simplemente de mala diseño. Por ejemplo, no hay forma de crear un nuevo nodo y agregar inmediatamente hijos o atributos a él. En su lugar, primero tienes que crearlo y luego agregar los hijos y atributos uno por uno, usando efectos secundarios. El código que interactúa mucho con el DOM tiende a ser largo, repetitivo y feo.</p> +<p><a class="p_ident" id="p-LhNbMoCs3P" href="#p-LhNbMoCs3P" tabindex="-1" role="presentation"></a>Entonces hay problemas que vienen simplemente de un mal diseño. Por ejemplo, no hay forma de crear un nuevo nodo y agregar inmediatamente hijos o atributos a él. En su lugar, primero tienes que crearlo y luego agregar los hijos y atributos uno por uno, usando efectos secundarios. El código que interactúa mucho con el DOM tiende a ser largo, repetitivo y feo.</p> <p><a class="p_ident" id="p-Y1sw4qlX+/" href="#p-Y1sw4qlX+/" tabindex="-1" role="presentation"></a>Pero estos defectos no son fatales. Dado que JavaScript nos permite crear nuestras propias abstracciones, es posible diseñar formas mejoradas de expresar las operaciones que estás realizando. Muchas bibliotecas destinadas a la programación del navegador vienen con herramientas de este tipo.</p> <h2><a class="h_ident" id="h-hqeynBcGZ3" href="#h-hqeynBcGZ3" tabindex="-1" role="presentation"></a>Movimiento a través del árbol</h2> -<p><a class="p_ident" id="p-kI4PvyIWyL" href="#p-kI4PvyIWyL" tabindex="-1" role="presentation"></a>Los nodos DOM contienen una gran cantidad de enlaces a otros nodos cercanos. El siguiente diagrama ilustra esto:</p><figure><img src="img/html-links.svg" alt="Diagrama que muestra los enlaces entre nodos DOM. El nodo 'body' se muestra como un cuadro, con una flecha 'firstChild' apuntando al nodo 'h1' en su inicio, una flecha 'lastChild' apuntando al último nodo de párrafo, y una flecha 'childNodes' apuntando a un array de enlaces a todos sus hijos. El párrafo del medio tiene una flecha 'previousSibling' apuntando al nodo anterior, una flecha 'nextSibling' al nodo siguiente, y una flecha 'parentNode' apuntando al nodo 'body'."></figure> +<p><a class="p_ident" id="p-kI4PvyIWyL" href="#p-kI4PvyIWyL" tabindex="-1" role="presentation"></a>Los nodos DOM contienen una gran cantidad de enlaces a otros nodos cercanos. El siguiente diagrama ilustra esto:</p><figure><img src="img/html-links.svg" alt="Diagrama que muestra los enlaces entre nodos del DOM. El nodo 'body' se muestra como un cuadro, con una flecha 'firstChild' apuntando al nodo 'h1' en su inicio, una flecha 'lastChild' apuntando al último nodo de párrafo, y una flecha 'childNodes' apuntando a un array de enlaces a todos sus hijos. El párrafo del medio tiene una flecha 'previousSibling' apuntando al nodo anterior, una flecha 'nextSibling' al nodo siguiente, y una flecha 'parentNode' apuntando al nodo 'body'."></figure> -<p><a class="p_ident" id="p-ODAPWxxB4g" href="#p-ODAPWxxB4g" tabindex="-1" role="presentation"></a>Aunque el diagrama muestra solo un enlace de cada tipo, cada nodo tiene una propiedad <code>parentNode</code> que apunta al nodo del que forma parte, si lo hay. De igual manera, cada nodo de elemento (tipo 1) tiene una propiedad <code>childNodes</code> que apunta a un objeto similar a un array que contiene sus hijos.</p> +<p><a class="p_ident" id="p-ODAPWxxB4g" href="#p-ODAPWxxB4g" tabindex="-1" role="presentation"></a>Aunque el diagrama muestra solo un enlace de cada tipo, cada nodo tiene una propiedad <code>parentNode</code> que apunta al nodo del que forma parte, si lo hay. De igual manera, cada nodo elemento (tipo 1) tiene una propiedad <code>childNodes</code> que apunta a un objeto similar a un array que contiene sus hijos.</p> -<p><a class="p_ident" id="p-qKmzlzsrsb" href="#p-qKmzlzsrsb" tabindex="-1" role="presentation"></a>En teoría, podrías moverte por todo el árbol utilizando solo estos enlaces padre e hijo. Pero JavaScript también te da acceso a varios enlaces de conveniencia adicionales. Las propiedades <code>firstChild</code> y <code>lastChild</code> apuntan a los primeros y últimos elementos hijos o tienen el valor <code>null</code> para nodos sin hijos. De manera similar, <code>previousSibling</code> y <code>nextSibling</code> apuntan a nodos adyacentes, que son nodos con el mismo padre que aparecen inmediatamente antes o después del nodo en sí. Para un primer hijo, <code>previousSibling</code> será nulo, y para un último hijo, <code>nextSibling</code> será nulo.</p> +<p><a class="p_ident" id="p-qKmzlzsrsb" href="#p-qKmzlzsrsb" tabindex="-1" role="presentation"></a>En teoría, podrías moverte por todo el árbol utilizando solo estos enlaces padre e hijo. Pero JavaScript también te da acceso a varios enlaces adicionales que resultan muy cómodos. Las propiedades <code>firstChild</code> y <code>lastChild</code> apuntan a los primeros y últimos elementos hijos o tienen el valor <code>null</code> para nodos sin hijos. De manera similar, <code>previousSibling</code> y <code>nextSibling</code> apuntan a nodos adyacentes, que son nodos con el mismo padre que aparecen inmediatamente antes o después del nodo en sí. Para un primer hijo, <code>previousSibling</code> será nulo, y para un último hijo, <code>nextSibling</code> será nulo.</p> -<p><a class="p_ident" id="p-/JUBta+lKY" href="#p-/JUBta+lKY" tabindex="-1" role="presentation"></a>También está la propiedad <code>children</code>, que es como <code>childNodes</code> pero contiene solo hijos de elementos (tipo 1), no otros tipos de nodos hijos. Esto puede ser útil cuando no estás interesado en nodos de texto.</p> +<p><a class="p_ident" id="p-/JUBta+lKY" href="#p-/JUBta+lKY" tabindex="-1" role="presentation"></a>También está la propiedad <code>children</code>, que es como <code>childNodes</code> pero contiene solo hijos elementos (tipo 1), no otros tipos de nodos hijos. Esto puede ser útil cuando no estás interesado en nodos de texto.</p> -<p><a class="p_ident" id="p-/XA4Ivap77" href="#p-/XA4Ivap77" tabindex="-1" role="presentation"></a>Cuando se trabaja con una estructura de datos anidada como esta, las funciones recursivas son frecuentemente útiles. La siguiente función examina un documento en busca de nodos de texto que contengan una cadena específica y devuelve <code>true</code> cuando ha encontrado uno:</p> +<p><a class="p_ident" id="p-/XA4Ivap77" href="#p-/XA4Ivap77" tabindex="-1" role="presentation"></a>Cuando se trabaja con una estructura de datos anidada como esta, las funciones recursivas suelen ser útiles. La siguiente función examina un documento en busca de nodos de texto que contengan una cadena específica y devuelve <code>true</code> cuando ha encontrado uno:</p> -<pre id="talksAbout" tabindex="0" class="snippet" data-language="javascript" data-sandbox="homepage"><a class="c_ident" id="c-6KSTCjrgG9" href="#c-6KSTCjrgG9" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">talksAbout</span>(<span class="tok-definition">node</span>, <span class="tok-definition">cadena</span>) { - <span class="tok-keyword">if</span> (node.nodeType == Node.ELEMENT_NODE) { - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">child</span> <span class="tok-keyword">of</span> node.childNodes) { - <span class="tok-keyword">if</span> (talksAbout(child, cadena)) { +<pre id="talksAbout" tabindex="0" class="snippet" data-language="javascript" data-sandbox="homepage"><a class="c_ident" id="c-TC4dWhiDeY" href="#c-TC4dWhiDeY" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">hablaSobre</span>(<span class="tok-definition">nodo</span>, <span class="tok-definition">cadena</span>) { + <span class="tok-keyword">if</span> (nodo.nodeType == Node.ELEMENT_NODE) { + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">hijo</span> <span class="tok-keyword">of</span> nodo.childNodes) { + <span class="tok-keyword">if</span> (hablaSobre(hijo, cadena)) { <span class="tok-keyword">return</span> true; } } <span class="tok-keyword">return</span> false; - } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (node.nodeType == Node.TEXT_NODE) { - <span class="tok-keyword">return</span> node.nodeValue.indexOf(cadena) > -<span class="tok-number">1</span>; + } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (nodo.nodeType == Node.TEXT_NODE) { + <span class="tok-keyword">return</span> nodo.nodeValue.indexOf(cadena) > -<span class="tok-number">1</span>; } } -console.log(talksAbout(document.body, <span class="tok-string">"libro"</span>)); +console.log(hablaSobre(document.body, <span class="tok-string">"libro"</span>)); <span class="tok-comment">// → true</span></pre> <p><a class="p_ident" id="p-Ie2TK4QlBd" href="#p-Ie2TK4QlBd" tabindex="-1" role="presentation"></a>La propiedad <code>nodeValue</code> de un nodo de texto contiene la cadena de texto que representa.</p> <h2><a class="h_ident" id="h-k3Afv/jCsp" href="#h-k3Afv/jCsp" tabindex="-1" role="presentation"></a>Encontrando elementos</h2> -<p><a class="p_ident" id="p-wxJsE++RA+" href="#p-wxJsE++RA+" tabindex="-1" role="presentation"></a>Navegar por estos enlaces entre padres, hijos y hermanos a menudo es útil. Pero si queremos encontrar un nodo específico en el documento, llegar a él empezando por <code>document.body</code> y siguiendo un camino fijo de propiedades no es una buena idea. Hacerlo implica hacer suposiciones en nuestro programa sobre la estructura precisa del documento, una estructura que podrías querer cambiar más adelante. Otro factor complicador es que se crean nodos de texto incluso para los espacios en blanco entre nodos. La etiqueta <code><body></code> del documento de ejemplo no tiene solo tres hijos (<code><h1></code> y dos elementos <code><p></code>) sino que en realidad tiene siete: esos tres, más los espacios en blanco antes, después y entre ellos.</p> +<p><a class="p_ident" id="p-wxJsE++RA+" href="#p-wxJsE++RA+" tabindex="-1" role="presentation"></a>Navegar por estos enlaces entre padres, hijos y hermanos a menudo es útil. Pero si queremos encontrar un nodo específico en el documento, llegar a él empezando por <code>document.body</code> y siguiendo un camino fijo de propiedades no es una buena idea. Hacerlo implica hacer suposiciones en nuestro programa sobre la estructura precisa del documento, una estructura que podrías querer cambiar más adelante. Otro factor que complica el asunto es que se crean nodos de texto incluso para los espacios en blanco entre nodos. La etiqueta <code><body></code> del documento de ejemplo no tiene solo tres hijos (<code><h1></code> y dos elementos <code><p></code>) sino que en realidad tiene siete: esos tres, más los espacios en blanco antes, después y entre ellos.</p> -<p><a class="p_ident" id="p-8a6WVHx6KJ" href="#p-8a6WVHx6KJ" tabindex="-1" role="presentation"></a>Por lo tanto, si queremos obtener el atributo <code>href</code> del enlace en ese documento, no queremos decir algo como “Obtener el segundo hijo del sexto hijo del cuerpo del documento”. Sería mejor si pudiéramos decir “Obtener el primer enlace en el documento”. Y podemos hacerlo.</p> +<p><a class="p_ident" id="p-8a6WVHx6KJ" href="#p-8a6WVHx6KJ" tabindex="-1" role="presentation"></a>Por lo tanto, si quisiéramos obtener el atributo <code>href</code> del enlace en ese documento, no nos gustaría tener que decir algo como “Obtener el segundo hijo del sexto hijo del cuerpo del documento”. Sería mejor si pudiéramos decir “Obtener el primer enlace en el documento”. Y podemos hacerlo.</p> <pre tabindex="0" class="snippet" data-language="javascript" data-sandbox="homepage"><a class="c_ident" id="c-Q+UpozZHWD" href="#c-Q+UpozZHWD" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">enlace</span> = document.body.getElementsByTagName(<span class="tok-string">"a"</span>)[<span class="tok-number">0</span>]; console.log(enlace.href);</pre> -<p><a class="p_ident" id="p-iLuQIDPO93" href="#p-iLuQIDPO93" tabindex="-1" role="presentation"></a>Todos los nodos de elemento tienen un método <code>getElementsByTagName</code>, que recoge todos los elementos con el nombre de etiqueta dado que son descendientes (hijos directos o indirectos) de ese nodo y los devuelve como un objeto similar a un array.</p> +<p><a class="p_ident" id="p-iLuQIDPO93" href="#p-iLuQIDPO93" tabindex="-1" role="presentation"></a>Todos los nodos elemento tienen un método <code>getElementsByTagName</code>, que recoge todos los elementos con el nombre de etiqueta dado que son descendientes (hijos directos o indirectos) de ese nodo y los devuelve como un objeto parecido a un array.</p> <p><a class="p_ident" id="p-Bhw4JRPAcq" href="#p-Bhw4JRPAcq" tabindex="-1" role="presentation"></a>Para encontrar un nodo específico <em>único</em>, puedes darle un atributo <code>id</code> y usar <code>document.<wbr>getElementById</code> en su lugar.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-uZyzwJ0nAb" href="#c-uZyzwJ0nAb" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Mi avestruz Gertrudis:</<span class="tok-typeName">p</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-NBid1TLslE" href="#c-NBid1TLslE" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Mi avestruz Gertrudis:</<span class="tok-typeName">p</span>> <<span class="tok-typeName">p</span>><<span class="tok-typeName">img</span> id=<span class="tok-string">"gertrudis"</span> src=<span class="tok-string">"img/ostrich.png"</span>></<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">ostrich</span> = document.getElementById(<span class="tok-string">"gertrudis"</span>); - console.log(ostrich.src); + <span class="tok-keyword">let</span> <span class="tok-definition">avestruz</span> = document.getElementById(<span class="tok-string">"gertrudis"</span>); + console.log(avestruz.src); </<span class="tok-typeName">script</span>></pre> <p><a class="p_ident" id="p-EywnjYOy/I" href="#p-EywnjYOy/I" tabindex="-1" role="presentation"></a>Un tercer método similar es <code>getElementsByClassName</code>, que, al igual que <code>getElementsByTagName</code>, busca a través del contenido de un nodo de elemento y recupera todos los elementos que tienen la cadena dada en su atributo <code>class</code>.</p> <h2><a class="h_ident" id="h-EjVaQgERdd" href="#h-EjVaQgERdd" tabindex="-1" role="presentation"></a>Cambiando el documento</h2> -<p><a class="p_ident" id="p-41N9V1W1OM" href="#p-41N9V1W1OM" tabindex="-1" role="presentation"></a>Casi todo se puede cambiar en la estructura de datos del DOM. La forma del árbol del documento se puede modificar cambiando las relaciones padre-hijo. Los nodos tienen un método <code>remove</code> para removerlos de su nodo padre actual. Para añadir un nodo hijo a un nodo de elemento, podemos usar <code>appendChild</code>, que lo coloca al final de la lista de hijos, o <code>insertBefore</code>, que inserta el nodo dado como primer argumento antes del nodo dado como segundo argumento.</p> +<p><a class="p_ident" id="p-41N9V1W1OM" href="#p-41N9V1W1OM" tabindex="-1" role="presentation"></a>Casi todo se puede cambiar en la estructura de datos del DOM. La forma del árbol del documento se puede modificar cambiando las relaciones padre-hijo. Los nodos tienen un método <code>remove</code> para eliminarlos de su nodo padre actual. Para añadir un nodo hijo a un nodo elemento, podemos usar <code>appendChild</code>, que lo coloca al final de la lista de hijos, o <code>insertBefore</code>, que inserta el nodo dado como primer argumento antes del nodo dado como segundo argumento.</p> <pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-DiYstIpu+b" href="#c-DiYstIpu+b" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Uno</<span class="tok-typeName">p</span>> <<span class="tok-typeName">p</span>>Dos</<span class="tok-typeName">p</span>> @@ -153,19 +153,19 @@ <h2><a class="h_ident" id="h-3fA2hLFRzg" href="#h-3fA2hLFRzg" tabindex="-1" role <p><a class="p_ident" id="p-AxXG9B1BFJ" href="#p-AxXG9B1BFJ" tabindex="-1" role="presentation"></a>Esto implica no solo eliminar las imágenes sino agregar un nuevo nodo de texto para reemplazarlas.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-x13nsyh4X4" href="#c-x13nsyh4X4" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>The <<span class="tok-typeName">img</span> src=<span class="tok-string">"img/cat.png"</span> alt=<span class="tok-string">"Cat"</span>> in the - <<span class="tok-typeName">img</span> src=<span class="tok-string">"img/hat.png"</span> alt=<span class="tok-string">"Hat"</span>>.</<span class="tok-typeName">p</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-TID8DBAoT/" href="#c-TID8DBAoT/" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>El <<span class="tok-typeName">img</span> src=<span class="tok-string">"img/cat.png"</span> alt=<span class="tok-string">"Gato"</span>> en el + <<span class="tok-typeName">img</span> src=<span class="tok-string">"img/hat.png"</span> alt=<span class="tok-string">"Sombrero"</span>>.</<span class="tok-typeName">p</span>> -<<span class="tok-typeName">p</span>><<span class="tok-typeName">button</span> onclick=<span class="tok-string">"</span>replaceImages()<span class="tok-string">"</span>>Replace</<span class="tok-typeName">button</span>></<span class="tok-typeName">p</span>> +<<span class="tok-typeName">p</span>><<span class="tok-typeName">button</span> onclick=<span class="tok-string">"</span>reemplazarImágenes()<span class="tok-string">"</span>>Replace</<span class="tok-typeName">button</span>></<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">function</span> <span class="tok-definition">replaceImages</span>() { - <span class="tok-keyword">let</span> <span class="tok-definition">images</span> = document.body.getElementsByTagName(<span class="tok-string">"img"</span>); - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = images.length - <span class="tok-number">1</span>; i >= <span class="tok-number">0</span>; i--) { - <span class="tok-keyword">let</span> <span class="tok-definition">image</span> = images[i]; - <span class="tok-keyword">if</span> (image.alt) { - <span class="tok-keyword">let</span> <span class="tok-definition">text</span> = document.createTextNode(image.alt); - image.parentNode.replaceChild(text, image); + <span class="tok-keyword">function</span> <span class="tok-definition">reemplazarImágenes</span>() { + <span class="tok-keyword">let</span> <span class="tok-definition">imágenes</span> = document.body.getElementsByTagName(<span class="tok-string">"img"</span>); + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = imágenes.length - <span class="tok-number">1</span>; i >= <span class="tok-number">0</span>; i--) { + <span class="tok-keyword">let</span> <span class="tok-definition">imagen</span> = imágenes[i]; + <span class="tok-keyword">if</span> (imagen.alt) { + <span class="tok-keyword">let</span> <span class="tok-definition">texto</span> = document.createTextNode(imagen.alt); + imagen.parentNode.replaceChild(texto, imagen); } } } @@ -177,8 +177,8 @@ <h2><a class="h_ident" id="h-3fA2hLFRzg" href="#h-3fA2hLFRzg" tabindex="-1" role <p><a class="p_ident" id="p-LbdZbfAq1V" href="#p-LbdZbfAq1V" tabindex="-1" role="presentation"></a>Si quieres tener una colección <em>sólida</em> de nodos, en lugar de una en vivo, puedes convertir la colección en un array real llamando a <code>Array.from</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-UMv2sA5GAX" href="#c-UMv2sA5GAX" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">arrayish</span> = {<span class="tok-number">0</span>: <span class="tok-string">"uno"</span>, <span class="tok-number">1</span>: <span class="tok-string">"dos"</span>, <span class="tok-definition">length</span>: <span class="tok-number">2</span>}; -<span class="tok-keyword">let</span> <span class="tok-definition">array</span> = Array.from(arrayish); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-D7eFP5rI5o" href="#c-D7eFP5rI5o" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">arrayoso</span> = {<span class="tok-number">0</span>: <span class="tok-string">"uno"</span>, <span class="tok-number">1</span>: <span class="tok-string">"dos"</span>, <span class="tok-definition">length</span>: <span class="tok-number">2</span>}; +<span class="tok-keyword">let</span> <span class="tok-definition">array</span> = Array.from(arrayoso); console.log(array.map(<span class="tok-definition">s</span> => s.toUpperCase())); <span class="tok-comment">// → ["UNO", "DOS"]</span></pre> @@ -186,19 +186,19 @@ <h2><a class="h_ident" id="h-3fA2hLFRzg" href="#h-3fA2hLFRzg" tabindex="-1" role <p id="elt"><a class="p_ident" id="p-a4L1nizZRc" href="#p-a4L1nizZRc" tabindex="-1" role="presentation"></a>El siguiente ejemplo define una utilidad <code>elt</code>, que crea un nodo de elemento y trata el resto de sus argumentos como hijos de ese nodo. Luego, esta función se utiliza para agregar una atribución a una cita.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-aBeAygY96j" href="#c-aBeAygY96j" tabindex="-1" role="presentation"></a><<span class="tok-typeName">blockquote</span> id=<span class="tok-string">"quote"</span>> - Ningún libro puede considerarse terminado. Mientras trabajamos en él aprendemos - lo suficiente como para encontrarlo inmaduro en el momento en que lo dejamos. +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-X8Sc6NGTLG" href="#c-X8Sc6NGTLG" tabindex="-1" role="presentation"></a><<span class="tok-typeName">blockquote</span> id=<span class="tok-string">"quote"</span>> + Ningún libro puede considerarse jamás terminado. Mientras trabajamos en él aprendemos + lo suficiente como para considerarlo inmaduro en el momento en que lo dejamos. </<span class="tok-typeName">blockquote</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">function</span> <span class="tok-definition">elt</span>(<span class="tok-definition">type</span>, ...<span class="tok-definition">children</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">node</span> = document.createElement(type); - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">child</span> <span class="tok-keyword">of</span> children) { - <span class="tok-keyword">if</span> (<span class="tok-keyword">typeof</span> child != <span class="tok-string">"string"</span>) node.appendChild(child); - <span class="tok-keyword">else</span> node.appendChild(document.createTextNode(child)); + <span class="tok-keyword">function</span> <span class="tok-definition">elt</span>(<span class="tok-definition">tipo</span>, ...<span class="tok-definition">hijos</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">nodo</span> = document.createElement(tipo); + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">hijo</span> <span class="tok-keyword">of</span> hijos) { + <span class="tok-keyword">if</span> (<span class="tok-keyword">typeof</span> hijo != <span class="tok-string">"string"</span>) nodo.appendChild(hijo); + <span class="tok-keyword">else</span> nodo.appendChild(document.createTextNode(hijo)); } - <span class="tok-keyword">return</span> node; + <span class="tok-keyword">return</span> nodo; } document.getElementById(<span class="tok-string">"quote"</span>).appendChild( @@ -211,29 +211,29 @@ <h2><a class="h_ident" id="h-3fA2hLFRzg" href="#h-3fA2hLFRzg" tabindex="-1" role <h2><a class="h_ident" id="h-vZcxSH0zRq" href="#h-vZcxSH0zRq" tabindex="-1" role="presentation"></a>Atributos</h2> -<p><a class="p_ident" id="p-bZqyuYKpeZ" href="#p-bZqyuYKpeZ" tabindex="-1" role="presentation"></a>Algunos atributos de elementos, como <code>href</code> para enlaces, pueden ser accedidos a través de una propiedad con el mismo nombre en el objeto DOM del elemento. Este es el caso para la mayoría de atributos estándar comúnmente usados.</p> +<p><a class="p_ident" id="p-VH50FCR97b" href="#p-VH50FCR97b" tabindex="-1" role="presentation"></a>Algunos atributos de elementos, como <code>href</code> para enlaces, pueden ser accedidos a través de una propiedad con el mismo nombre en el objeto DOM del elemento. Este es el caso para la mayoría de atributos estándar más frecuentes.</p> -<p><a class="p_ident" id="p-LxuPYiH1Kn" href="#p-LxuPYiH1Kn" tabindex="-1" role="presentation"></a>HTML te permite establecer cualquier atributo que desees en los nodos. Esto puede ser útil porque te permite almacenar información adicional en un documento. Para leer o cambiar atributos personalizados, que no están disponibles como propiedades regulares del objeto, debes usar los métodos <code>getAttribute</code> y <code>setAttribute</code>.</p> +<p><a class="p_ident" id="p-LxuPYiH1Kn" href="#p-LxuPYiH1Kn" tabindex="-1" role="presentation"></a>HTML te permite establecer cualquier atributo que desees en los nodos. Esto puede ser útil porque te permite almacenar información adicional en un documento. Para leer o cambiar atributos personalizados, que no están disponibles como propiedades normales del objeto, debes usar los métodos <code>getAttribute</code> y <code>setAttribute</code>.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-zGszwELC18" href="#c-zGszwELC18" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span> data-classified=<span class="tok-string">"secreto"</span>>El código de lanzamiento es 00000000.</<span class="tok-typeName">p</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-asYsKsXjR+" href="#c-asYsKsXjR+" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span> data-classified=<span class="tok-string">"secreto"</span>>El código de lanzamiento es 00000000.</<span class="tok-typeName">p</span>> <<span class="tok-typeName">p</span> data-classified=<span class="tok-string">"no clasificado"</span>>Tengo dos pies.</<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">paras</span> = document.body.getElementsByTagName(<span class="tok-string">"p"</span>); - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">para</span> <span class="tok-keyword">of</span> Array.from(paras)) { - <span class="tok-keyword">if</span> (para.getAttribute(<span class="tok-string">"data-classified"</span>) == <span class="tok-string">"secreto"</span>) { - para.remove(); + <span class="tok-keyword">let</span> <span class="tok-definition">párrafos</span> = document.body.getElementsByTagName(<span class="tok-string">"p"</span>); + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">párrafo</span> <span class="tok-keyword">of</span> Array.from(párrafos)) { + <span class="tok-keyword">if</span> (párrafo.getAttribute(<span class="tok-string">"data-classified"</span>) == <span class="tok-string">"secreto"</span>) { + párrafo.remove(); } } </<span class="tok-typeName">script</span>></pre> <p><a class="p_ident" id="p-yy4/AgoR0n" href="#p-yy4/AgoR0n" tabindex="-1" role="presentation"></a>Se recomienda prefijar los nombres de estos atributos inventados con <code>data-</code> para asegurarse de que no entren en conflicto con otros atributos.</p> -<p><a class="p_ident" id="p-rasbS3lHsv" href="#p-rasbS3lHsv" tabindex="-1" role="presentation"></a>Existe un atributo comúnmente usado, <code>class</code>, que es una palabra clave en el lenguaje JavaScript. Por razones históricas—algunas implementaciones antiguas de JavaScript no podían manejar nombres de propiedades que coincidieran con palabras clave—la propiedad utilizada para acceder a este atributo se llama <code>className</code>. También puedes acceder a él con su nombre real, <code>"class"</code>, utilizando los métodos <code>getAttribute</code> y <code>setAttribute</code>.</p> +<p><a class="p_ident" id="p-rasbS3lHsv" href="#p-rasbS3lHsv" tabindex="-1" role="presentation"></a>Existe un atributo comúnmente usado, <code>class</code>, que es una palabra clave en el lenguaje JavaScript. Por razones históricas —algunas implementaciones antiguas de JavaScript no podían manejar nombres de propiedades que coincidieran con palabras clave— la propiedad utilizada para acceder a este atributo se llama <code>className</code>. También puedes acceder a él con su nombre real, <code>"class"</code>, utilizando los métodos <code>getAttribute</code> y <code>setAttribute</code>.</p> <h2><a class="h_ident" id="h-3+hJ1EAoa5" href="#h-3+hJ1EAoa5" tabindex="-1" role="presentation"></a>Diseño</h2> -<p><a class="p_ident" id="p-vgjsqh+iz1" href="#p-vgjsqh+iz1" tabindex="-1" role="presentation"></a>Puede que hayas notado que diferentes tipos de elementos se disponen de manera diferente. Algunos, como párrafos (<code><p></code>) o encabezados (<code><h1></code>), ocupan todo el ancho del documento y se muestran en líneas separadas. Estos se llaman elementos de <em>bloque</em>. Otros, como enlaces (<code><a></code>) o el elemento <code><strong></code>, se muestran en la misma línea que el texto que los rodea. A estos elementos se les llama elementos <em>en línea</em>.</p> +<p><a class="p_ident" id="p-vgjsqh+iz1" href="#p-vgjsqh+iz1" tabindex="-1" role="presentation"></a>Puede que hayas notado que diferentes tipos de elementos se disponen de manera diferente. Algunos, como párrafos (<code><p></code>) o encabezados (<code><h1></code>), ocupan todo el ancho del documento y se muestran en líneas separadas. A estos elementos los llamamos elementos de <em>bloque</em>. Otros, como enlaces (<code><a></code>) o el elemento <code><strong></code>, se muestran en la misma línea que el texto que los rodea. A estos elementos se les llama elementos <em>en línea</em>.</p> <p><a class="p_ident" id="p-UhmyZIH23N" href="#p-UhmyZIH23N" tabindex="-1" role="presentation"></a>Para cualquier documento dado, los navegadores son capaces de calcular un diseño, que le da a cada elemento un tamaño y posición basados en su tipo y contenido. Luego, este diseño se usa para dibujar el documento realmente.</p> @@ -241,15 +241,15 @@ <h2><a class="h_ident" id="h-3+hJ1EAoa5" href="#h-3+hJ1EAoa5" tabindex="-1" role <p><a class="p_ident" id="p-pTjdvMWejj" href="#p-pTjdvMWejj" tabindex="-1" role="presentation"></a>De manera similar, <code>clientWidth</code> y <code>clientHeight</code> te dan el tamaño del espacio <em>dentro</em> del elemento, ignorando el ancho del borde.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-EyWKHP9vll" href="#c-EyWKHP9vll" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span> style=<span class="tok-string">"</span>border: <span class="tok-number">3</span><span class="tok-keyword">px</span> <span class="tok-atom">solid</span> <span class="tok-atom">red</span><span class="tok-string">"</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-2GFiplh9n9" href="#c-2GFiplh9n9" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span> style=<span class="tok-string">"</span>border: <span class="tok-number">3</span><span class="tok-keyword">px</span> <span class="tok-atom">solid</span> <span class="tok-atom">red</span><span class="tok-string">"</span>> Estoy enmarcado </<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">para</span> = document.body.getElementsByTagName(<span class="tok-string">"p"</span>)[<span class="tok-number">0</span>]; - console.log(<span class="tok-string">"clientHeight:"</span>, para.clientHeight); + <span class="tok-keyword">let</span> <span class="tok-definition">párrafo</span> = document.body.getElementsByTagName(<span class="tok-string">"p"</span>)[<span class="tok-number">0</span>]; + console.log(<span class="tok-string">"clientHeight:"</span>, párrafo.clientHeight); <span class="tok-comment">// → 19</span> - console.log(<span class="tok-string">"offsetHeight:"</span>, para.offsetHeight); + console.log(<span class="tok-string">"offsetHeight:"</span>, párrafo.offsetHeight); <span class="tok-comment">// → 25</span> </<span class="tok-typeName">script</span>></pre> @@ -257,33 +257,33 @@ <h2><a class="h_ident" id="h-3+hJ1EAoa5" href="#h-3+hJ1EAoa5" tabindex="-1" role <p><a class="p_ident" id="p-1CP2gBKXRb" href="#p-1CP2gBKXRb" tabindex="-1" role="presentation"></a>Diseñar un documento puede ser bastante trabajo. En aras de la rapidez, los motores de los navegadores no vuelven a diseñar inmediatamente un documento cada vez que se modifica, sino que esperan tanto como pueden. Cuando un programa de JavaScript que ha modificado el documento finaliza su ejecución, el navegador tendrá que calcular un nuevo diseño para dibujar el documento modificado en la pantalla. Cuando un programa <em>pide</em> la posición o tamaño de algo leyendo propiedades como <code>offsetHeight</code> o llamando a <code>getBoundingClientRect</code>, proporcionar esa información también requiere calcular un diseño.</p> -<p><a class="p_ident" id="p-xFjwC6rkaF" href="#p-xFjwC6rkaF" tabindex="-1" role="presentation"></a>Un programa que alterna repetidamente entre la lectura de información de diseño del DOM y el cambio del DOM provoca que se realicen muchas computaciones de diseño y, en consecuencia, se ejecute muy lentamente. El siguiente código es un ejemplo de esto. Contiene dos programas diferentes que construyen una línea de caracteres <em>X</em> de 2,000 píxeles de ancho y mide el tiempo que lleva cada uno.</p> +<p><a class="p_ident" id="p-xFjwC6rkaF" href="#p-xFjwC6rkaF" tabindex="-1" role="presentation"></a>Un programa que alterna repetidamente entre la lectura de información de diseño del DOM y el cambio del DOM provoca que se realicen muchos cálculos de diseño y, en consecuencia, se ejecute muy lentamente. El siguiente código es un ejemplo de esto. Contiene dos programas diferentes que construyen una línea de caracteres <em>X</em> de 2,000 píxeles de ancho y mide el tiempo que lleva cada uno.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-aqCEO54/Rg" href="#c-aqCEO54/Rg" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>><<span class="tok-typeName">span</span> id=<span class="tok-string">"one"</span>></<span class="tok-typeName">span</span>></<span class="tok-typeName">p</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-AqvyyPAbjP" href="#c-AqvyyPAbjP" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>><<span class="tok-typeName">span</span> id=<span class="tok-string">"one"</span>></<span class="tok-typeName">span</span>></<span class="tok-typeName">p</span>> <<span class="tok-typeName">p</span>><<span class="tok-typeName">span</span> id=<span class="tok-string">"two"</span>></<span class="tok-typeName">span</span>></<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">function</span> <span class="tok-definition">time</span>(<span class="tok-definition">name</span>, <span class="tok-definition">action</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">start</span> = Date.now(); <span class="tok-comment">// Tiempo actual en milisegundos</span> - action(); - console.log(name, <span class="tok-string">"tomó"</span>, Date.now() - start, <span class="tok-string">"ms"</span>); + <span class="tok-keyword">function</span> <span class="tok-definition">tiempo</span>(<span class="tok-definition">nombre</span>, <span class="tok-definition">acción</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">comienzo</span> = Date.now(); <span class="tok-comment">// Tiempo actual en milisegundos</span> + acción(); + console.log(<span class="tok-string">"El método"</span>, nombre, <span class="tok-string">"ha tomado"</span>, Date.now() - comienzo, <span class="tok-string">"ms"</span>); } - time(<span class="tok-string">"ingenuo"</span>, () => { - <span class="tok-keyword">let</span> <span class="tok-definition">target</span> = document.getElementById(<span class="tok-string">"one"</span>); - <span class="tok-keyword">while</span> (target.offsetWidth < <span class="tok-number">2000</span>) { - target.appendChild(document.createTextNode(<span class="tok-string">"X"</span>)); + tiempo(<span class="tok-string">"naíf"</span>, () => { + <span class="tok-keyword">let</span> <span class="tok-definition">objetivo</span> = document.getElementById(<span class="tok-string">"one"</span>); + <span class="tok-keyword">while</span> (objetivo.offsetWidth < <span class="tok-number">2000</span>) { + objetivo.appendChild(document.createTextNode(<span class="tok-string">"X"</span>)); } }); - <span class="tok-comment">// → ingenuo tomó 32 ms</span> + <span class="tok-comment">// → El método naíf ha tomado 12 ms</span> - time(<span class="tok-string">"astuto"</span>, <span class="tok-keyword">function</span>() { - <span class="tok-keyword">let</span> <span class="tok-definition">target</span> = document.getElementById(<span class="tok-string">"two"</span>); - target.appendChild(document.createTextNode(<span class="tok-string">"XXXXX"</span>)); - <span class="tok-keyword">let</span> <span class="tok-definition">total</span> = Math.ceil(<span class="tok-number">2000</span> / (target.offsetWidth / <span class="tok-number">5</span>)); - target.firstChild.nodeValue = <span class="tok-string">"X"</span>.repeat(total); + tiempo(<span class="tok-string">"inteligente"</span>, <span class="tok-keyword">function</span>() { + <span class="tok-keyword">let</span> <span class="tok-definition">objetivo</span> = document.getElementById(<span class="tok-string">"two"</span>); + objetivo.appendChild(document.createTextNode(<span class="tok-string">"XXXXX"</span>)); + <span class="tok-keyword">let</span> <span class="tok-definition">total</span> = Math.ceil(<span class="tok-number">2000</span> / (objetivo.offsetWidth / <span class="tok-number">5</span>)); + objetivo.firstChild.nodeValue = <span class="tok-string">"X"</span>.repeat(total); }); - <span class="tok-comment">// → astuto tomó 1 ms</span> + <span class="tok-comment">// → El método inteligente ha tomado 1 ms</span> </<span class="tok-typeName">script</span>></pre> <h2><a class="h_ident" id="h-cv0naTq5te" href="#h-cv0naTq5te" tabindex="-1" role="presentation"></a>Estilos</h2> @@ -295,45 +295,45 @@ <h2><a class="h_ident" id="h-cv0naTq5te" href="#h-cv0naTq5te" tabindex="-1" role <pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-bgo31+NA58" href="#c-bgo31+NA58" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>><<span class="tok-typeName">a</span> href=<span class="tok-string">"."</span>>Enlace normal</<span class="tok-typeName">a</span>></<span class="tok-typeName">p</span>> <<span class="tok-typeName">p</span>><<span class="tok-typeName">a</span> href=<span class="tok-string">"."</span> style=<span class="tok-string">"</span>color: <span class="tok-atom">green</span><span class="tok-string">"</span>>Enlace verde</<span class="tok-typeName">a</span>></<span class="tok-typeName">p</span>></pre> -<p><a class="p_ident" id="p-AYyLF9kYh3" href="#p-AYyLF9kYh3" tabindex="-1" role="presentation"></a>Un atributo de estilo puede contener uno o más <em>declaraciónes</em>, que son una propiedad (como <code>color</code>) seguida de dos puntos y un valor (como <code>verde</code>). Cuando hay más de una declaración, deben separarse por punto y comas, como en <code>"color: rojo; border: ninguno"</code>.</p> +<p><a class="p_ident" id="p-4eXT2OJNhQ" href="#p-4eXT2OJNhQ" tabindex="-1" role="presentation"></a>Un atributo de estilo puede contener una <em>declaración</em> o más de una, siendo una propiedades (como <code>color</code>) seguidas de dos puntos y un valor (como <code>green</code>). Cuando hay más de una declaración, deben separarse por punto y coma, como en <code>"color: red; border: none"</code>.</p> <p><a class="p_ident" id="p-jmgtDPsCf2" href="#p-jmgtDPsCf2" tabindex="-1" role="presentation"></a>Muchos aspectos del documento pueden ser influenciados por el estilo. Por ejemplo, la propiedad <code>display</code> controla si un elemento se muestra como un bloque o como un elemento en línea.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-WQQ5vzt2Oo" href="#c-WQQ5vzt2Oo" tabindex="-1" role="presentation"></a>Este texto se muestra de forma <<span class="tok-typeName">strong</span>>en línea</<span class="tok-typeName">strong</span>>, -<<span class="tok-typeName">strong</span> style=<span class="tok-string">"</span>display: <span class="tok-atom">block</span><span class="tok-string">"</span>>como un bloque</<span class="tok-typeName">strong</span>>, y -<<span class="tok-typeName">strong</span> style=<span class="tok-string">"</span>display: <span class="tok-atom">none</span><span class="tok-string">"</span>>no del todo</<span class="tok-typeName">strong</span>>.</pre> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-QFTYk+KIrj" href="#c-QFTYk+KIrj" tabindex="-1" role="presentation"></a>Este texto se muestra <<span class="tok-typeName">strong</span>>en línea</<span class="tok-typeName">strong</span>>, +<<span class="tok-typeName">strong</span> style=<span class="tok-string">"</span>display: <span class="tok-atom">block</span><span class="tok-string">"</span>> este como un bloque</<span class="tok-typeName">strong</span>> y +<<span class="tok-typeName">strong</span> style=<span class="tok-string">"</span>display: <span class="tok-atom">none</span><span class="tok-string">"</span>>este no se muestra</<span class="tok-typeName">strong</span>>.</pre> -<p><a class="p_ident" id="p-RuhythK22j" href="#p-RuhythK22j" tabindex="-1" role="presentation"></a>La etiqueta <code>block</code> terminará en su propia línea ya que los elementos de bloque no se muestran en línea con el texto que los rodea. La última etiqueta no se muestra en absoluto: <code>display: none</code> evita que un elemento aparezca en la pantalla. Esta es una forma de ocultar elementos. A menudo es preferible a eliminarlos completamente del documento porque facilita revelarlos nuevamente más tarde.</p> +<p><a class="p_ident" id="p-RuhythK22j" href="#p-RuhythK22j" tabindex="-1" role="presentation"></a>La etiqueta <code>block</code> terminará en su propia línea ya que los elementos de bloque no se muestran en línea con el texto que los rodea. La última etiqueta no se muestra: <code>display: none</code> evita que un elemento aparezca en la pantalla. Esta es una forma de ocultar elementos. A menudo es preferible a eliminarlos completamente del documento porque facilita revelarlos nuevamente más tarde.</p> <p><a class="p_ident" id="p-qNgOLOJTyi" href="#p-qNgOLOJTyi" tabindex="-1" role="presentation"></a>El código JavaScript puede manipular directamente el estilo de un elemento a través de la propiedad <code>style</code> del elemento. Esta propiedad contiene un objeto que tiene propiedades para todas las posibles propiedades de estilo. Los valores de estas propiedades son cadenas de texto, a las cuales podemos escribir para cambiar un aspecto particular del estilo del elemento.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-hETupBqRzy" href="#c-hETupBqRzy" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span> id=<span class="tok-string">"para"</span> style=<span class="tok-string">"</span>color: <span class="tok-atom">purple</span><span class="tok-string">"</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-VNsSkFxzGm" href="#c-VNsSkFxzGm" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span> id=<span class="tok-string">"parr"</span> style=<span class="tok-string">"</span>color: <span class="tok-atom">purple</span><span class="tok-string">"</span>> Texto bonito </<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">para</span> = document.getElementById(<span class="tok-string">"para"</span>); - console.log(para.style.color); - para.style.color = <span class="tok-string">"magenta"</span>; + <span class="tok-keyword">let</span> <span class="tok-definition">parr</span> = document.getElementById(<span class="tok-string">"parr"</span>); + console.log(parr.style.color); + parr.style.color = <span class="tok-string">"magenta"</span>; </<span class="tok-typeName">script</span>></pre> -<p><a class="p_ident" id="p-YruEPufhyn" href="#p-YruEPufhyn" tabindex="-1" role="presentation"></a>Algunos nombres de propiedades de estilo contienen guiones, como <code>font-family</code>. Debido a que trabajar con estos nombres de propiedades en JavaScript es incómodo (tendrías que decir <code>style["font-family"]</code>), los nombres de las propiedades en el objeto <code>style</code> para tales propiedades tienen los guiones eliminados y las letras posterior a ellos en mayúscula (<code>style.fontFamily</code>).</p> +<p><a class="p_ident" id="p-YruEPufhyn" href="#p-YruEPufhyn" tabindex="-1" role="presentation"></a>Algunos nombres de propiedades de estilo contienen guiones, como <code>font-family</code>. Como trabajar con estos nombres de propiedades en JavaScript es raro (tendrías que decir <code>style["font-family"]</code>), los nombres de las propiedades en el objeto <code>style</code> para tales propiedades tienen los guiones eliminados y las letras posteriores a ellos en mayúscula (<code>style.fontFamily</code>).</p> <h2><a class="h_ident" id="h-nTpjcUlubm" href="#h-nTpjcUlubm" tabindex="-1" role="presentation"></a>Estilos en cascada</h2> <p><a class="p_ident" id="p-XC76tolu1R" href="#p-XC76tolu1R" tabindex="-1" role="presentation"></a>El sistema de estilos para HTML se llama CSS, por sus siglas en inglés, <em>Cascading Style Sheets</em>. Una <em>hoja de estilo</em> es un conjunto de reglas sobre cómo dar estilo a los elementos en un documento. Puede ser proporcionada dentro de una etiqueta <code><style></code>.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-hf+0aERbE6" href="#c-hf+0aERbE6" tabindex="-1" role="presentation"></a><<span class="tok-typeName">style</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-sZL3hTPIqC" href="#c-sZL3hTPIqC" tabindex="-1" role="presentation"></a><<span class="tok-typeName">style</span>> <span class="tok-typeName">strong</span> { font-style: <span class="tok-atom">italic</span>; color: <span class="tok-atom">gray</span>; } </<span class="tok-typeName">style</span>> -<<span class="tok-typeName">p</span>>Ahora el <<span class="tok-typeName">strong</span>>texto fuerte</<span class="tok-typeName">strong</span>> es cursiva y gris.</<span class="tok-typeName">p</span>></pre> +<<span class="tok-typeName">p</span>>Ahora el <<span class="tok-typeName">strong</span>>texto fuerte</<span class="tok-typeName">strong</span>> está escrito en cursiva y de color gris.</<span class="tok-typeName">p</span>></pre> <p><a class="p_ident" id="p-c0Y1XlVJ+b" href="#p-c0Y1XlVJ+b" tabindex="-1" role="presentation"></a>El <em>cascada</em> en el nombre se refiere al hecho de que múltiples reglas de este tipo se combinan para producir el estilo final de un elemento. En el ejemplo, el estilo predeterminado de las etiquetas <code><strong></code>, que les da <code>font-weight: bold</code>, se superpone por la regla en la etiqueta <code><style></code>, que agrega <code>font-style</code> y <code>color</code>.</p> -<p><a class="p_ident" id="p-bu7knnS02i" href="#p-bu7knnS02i" tabindex="-1" role="presentation"></a>Cuando múltiples reglas definen un valor para la misma propiedad, la regla más recientemente leída obtiene una precedencia más alta y gana. Por lo tanto, si la regla en la etiqueta <code><style></code> incluyera <code>font-weight: normal</code>, contradiciendo la regla predeterminada de <code>font-weight</code>, el texto sería normal, <em>no</em> negrita. Los estilos en un atributo <code>style</code> aplicado directamente al nodo tienen la mayor precedencia y siempre prevalecen.</p> +<p><a class="p_ident" id="p-ZOo8179oz4" href="#p-ZOo8179oz4" tabindex="-1" role="presentation"></a>Cuando hay varias reglas definiendo un valor para la misma propiedad, la última regla que se lee obtiene una precedencia más alta y gana. Por lo tanto, si la regla en la etiqueta <code><style></code> incluyera <code>font-weight: normal</code>, contradiciendo la regla predeterminada de <code>font-weight</code>, el texto sería normal, <em>no</em> negrita. Los estilos en un atributo <code>style</code> aplicado directamente al nodo tienen la mayor precedencia y siempre prevalecen.</p> <p><a class="p_ident" id="p-pDky2YF6um" href="#p-pDky2YF6um" tabindex="-1" role="presentation"></a>Es posible apuntar a cosas distintas de los nombres de etiqueta en reglas de CSS. Una regla para <code>.abc</code> se aplica a todos los elementos con <code>"abc"</code> en su atributo <code>class</code>. Una regla para <code>#xyz</code> se aplica al elemento con un atributo <code>id</code> de <code>"xyz"</code> (que debería ser único dentro del documento).</p> @@ -350,30 +350,30 @@ <h2><a class="h_ident" id="h-nTpjcUlubm" href="#h-nTpjcUlubm" tabindex="-1" role margin-bottom: <span class="tok-number">20</span><span class="tok-keyword">px</span>; }</pre> -<p><a class="p_ident" id="p-7aLTXAE2Vt" href="#p-7aLTXAE2Vt" tabindex="-1" role="presentation"></a>La regla de precedencia que favorece a la regla más recientemente definida se aplica solo cuando las reglas tienen la misma <em>especificidad</em>. La especificidad de una regla es una medida de qué tan precisamente describe los elementos que coinciden, determinada por el número y tipo (etiqueta, clase o ID) de aspectos de elementos que requiere. Por ejemplo, una regla que apunta a <code>p.a</code> es más específica que las reglas que apuntan a <code>p</code> o simplemente <code>.a</code> y, por lo tanto, tendría precedencia sobre ellas.</p> +<p><a class="p_ident" id="p-7aLTXAE2Vt" href="#p-7aLTXAE2Vt" tabindex="-1" role="presentation"></a>La regla de precedencia que favorece a la regla más recientemente definida se aplica solo cuando las reglas tienen la misma <em>especificidad</em>. La especificidad de una regla es una medida de cómo de precisamente describe los elementos que coinciden, determinada por el número y tipo (etiqueta, clase o ID) de aspectos de elementos que requiere. Por ejemplo, una regla que apunta a <code>p.a</code> es más específica que las reglas que apuntan a <code>p</code> o simplemente <code>.a</code> y, por lo tanto, tendría precedencia sobre ellas.</p> <p><a class="p_ident" id="p-7tkPYtY0mq" href="#p-7tkPYtY0mq" tabindex="-1" role="presentation"></a>La notación <code>p > a {…}</code> aplica los estilos dados a todas las etiquetas <code><a></code> que son hijos directos de etiquetas <code><p></code>. De manera similar, <code>p a {…}</code> se aplica a todas las etiquetas <code><a></code> dentro de las etiquetas <code><p></code>, ya sean hijos directos o indirectos.</p> <h2><a class="h_ident" id="h-2xww2G0Ig3" href="#h-2xww2G0Ig3" tabindex="-1" role="presentation"></a>Selectores de consulta</h2> -<p><a class="p_ident" id="p-0c8DFK/DUS" href="#p-0c8DFK/DUS" tabindex="-1" role="presentation"></a>No vamos a usar hojas de estilo demasiado en este libro. Entenderlas es útil cuando se programa en el navegador, pero son lo suficientemente complicadas como para justificar un libro aparte.</p> +<p><a class="p_ident" id="p-0c8DFK/DUS" href="#p-0c8DFK/DUS" tabindex="-1" role="presentation"></a>No vamos a usar hojas de estilo demasiado en este libro. Entenderlas es útil cuando se programa en el navegador, pero son lo suficientemente complicadas como para necesitar un libro aparte.</p> -<p><a class="p_ident" id="p-vr+TMCFKlE" href="#p-vr+TMCFKlE" tabindex="-1" role="presentation"></a>La razón principal por la que introduje la sintaxis <em>selector</em>—la notación utilizada en las hojas de estilo para determinar a qué elementos se aplican un conjunto de estilos— es que podemos utilizar este mismo mini-lenguaje como una forma efectiva de encontrar elementos del DOM.</p> +<p><a class="p_ident" id="p-vr+TMCFKlE" href="#p-vr+TMCFKlE" tabindex="-1" role="presentation"></a>La razón principal por la que introduje la sintaxis <em>selector</em> —la notación utilizada en las hojas de estilo para determinar a qué elementos se aplican un conjunto de estilos— es que podemos utilizar este mismo mini-lenguaje como una forma efectiva de encontrar elementos del DOM.</p> -<p><a class="p_ident" id="p-OZdBTd/l6p" href="#p-OZdBTd/l6p" tabindex="-1" role="presentation"></a>El método <code>querySelectorAll</code>, que está definido tanto en el objeto <code>document</code> como en los nodos de elementos, toma una cadena de selector y devuelve un <code>NodeList</code> que contiene todos los elementos que encuentra.</p> +<p><a class="p_ident" id="p-OZdBTd/l6p" href="#p-OZdBTd/l6p" tabindex="-1" role="presentation"></a>El método <code>querySelectorAll</code>, que está definido tanto en el objeto <code>document</code> como en los nodos elemento, toma una cadena de selector y devuelve un <code>NodeList</code> que contiene todos los elementos que encuentra.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-ybdXxCLxUN" href="#c-ybdXxCLxUN" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>And if you go chasing +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-lBfL/naWJE" href="#c-lBfL/naWJE" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>And if you go chasing <<span class="tok-typeName">span</span> class=<span class="tok-string">"animal"</span>>rabbits</<span class="tok-typeName">span</span>></<span class="tok-typeName">p</span>> <<span class="tok-typeName">p</span>>And you know you're going to fall</<span class="tok-typeName">p</span>> -<<span class="tok-typeName">p</span>>Tell 'em a <<span class="tok-typeName">span</span> class=<span class="tok-string">"character"</span>>hookah smoking +<<span class="tok-typeName">p</span>>Tell 'em a <<span class="tok-typeName">span</span> class=<span class="tok-string">"personaje"</span>>hookah smoking <<span class="tok-typeName">span</span> class=<span class="tok-string">"animal"</span>>caterpillar</<span class="tok-typeName">span</span>></<span class="tok-typeName">span</span>></<span class="tok-typeName">p</span>> <<span class="tok-typeName">p</span>>Has given you the call</<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">function</span> <span class="tok-definition">count</span>(<span class="tok-definition">selector</span>) { + <span class="tok-keyword">function</span> <span class="tok-definition">contar</span>(<span class="tok-definition">selector</span>) { <span class="tok-keyword">return</span> document.querySelectorAll(selector).length; } - console.log(count(<span class="tok-string">"p"</span>)); <span class="tok-comment">// Todos los elementos <p></span> + console.log(contar(<span class="tok-string">"p"</span>)); <span class="tok-comment">// Todos los elementos <p></span> <span class="tok-comment">// → 4</span> console.log(count(<span class="tok-string">".animal"</span>)); <span class="tok-comment">// Clase animal</span> <span class="tok-comment">// → 2</span> @@ -383,54 +383,60 @@ <h2><a class="h_ident" id="h-2xww2G0Ig3" href="#h-2xww2G0Ig3" tabindex="-1" role <span class="tok-comment">// → 1</span> </<span class="tok-typeName">script</span>></pre> -<p><a class="p_ident" id="p-30dhoV1yIT" href="#p-30dhoV1yIT" tabindex="-1" role="presentation"></a>A diferencia de métodos como <code>getElementsByTagName</code>, el objeto devuelto por <code>querySelectorAll</code> <em>no</em> es dinámico. No cambiará cuando cambies el documento. Aun así, no es un array real, por lo que necesitas llamar a <code>Array.from</code> si deseas tratarlo como tal.</p> +<p><a class="p_ident" id="p-30dhoV1yIT" href="#p-30dhoV1yIT" tabindex="-1" role="presentation"></a>A diferencia de métodos como <code>getElementsByTagName</code>, el objeto devuelto por <code>querySelectorAll</code> <em>no</em> es dinámico. No cambiará cuando cambies el documento. Aun así, no es un array de verdad, por lo que tendrás que llamar a <code>Array.from</code> si quieres tratarlo como tal.</p> <p><a class="p_ident" id="p-ekjGpAGiW6" href="#p-ekjGpAGiW6" tabindex="-1" role="presentation"></a>El método <code>querySelector</code> (sin la parte <code>All</code>) funciona de manera similar. Este es útil si deseas un elemento específico y único. Solo devolverá el primer elemento coincidente o <code>null</code> cuando no haya ningún elemento coincidente.</p> <h2 id="animation"><a class="h_ident" id="h-ja3JZukaEb" href="#h-ja3JZukaEb" tabindex="-1" role="presentation"></a>Posicionamiento y animación</h2> -<p><a class="p_ident" id="p-YmSisoZPOk" href="#p-YmSisoZPOk" tabindex="-1" role="presentation"></a>La propiedad de estilo <code>position</code> influye en el diseño de una manera poderosa. De forma predeterminada, tiene un valor de <code>static</code>, lo que significa que el elemento se sitúa en su lugar normal en el documento. Cuando se establece en <code>relative</code>, el elemento sigue ocupando espacio en el documento, pero ahora las propiedades de estilo <code>top</code> y <code>left</code> se pueden usar para moverlo con respecto a ese lugar normal. Cuando <code>position</code> se establece en <code>absolute</code>, el elemento se elimina del flujo normal del documento, es decir, ya no ocupa espacio y puede superponerse con otros elementos. Además, sus propiedades de <code>top</code> y <code>left</code> se pueden usar para posicionarlo absolutamente con respecto a la esquina superior izquierda del elemento contenedor más cercano cuya propiedad de <code>position</code> no sea <code>static</code>, o con respecto al documento si no existe tal elemento contenedor.</p> +<p><a class="p_ident" id="p-YmSisoZPOk" href="#p-YmSisoZPOk" tabindex="-1" role="presentation"></a>La propiedad de estilo <code>position</code> influye en el diseño de manera importante. De forma predeterminada, tiene un valor de <code>static</code>, lo que significa que el elemento se sitúa en su lugar normal en el documento. Cuando se establece en <code>relative</code>, el elemento sigue ocupando espacio en el documento, pero ahora las propiedades de estilo <code>top</code> y <code>left</code> se pueden usar para moverlo con respecto a ese lugar normal. Cuando <code>position</code> se establece en <code>absolute</code>, el elemento se elimina del flujo normal del documento, es decir, ya no ocupa espacio y puede superponerse con otros elementos. Además, sus propiedades de <code>top</code> y <code>left</code> se pueden usar para posicionarlo absolutamente con respecto a la esquina superior izquierda del elemento contenedor más cercano cuya propiedad de <code>position</code> no sea <code>static</code>, o con respecto al documento si no existe tal elemento contenedor.</p> <p><a class="p_ident" id="p-DNfa4H/xcw" href="#p-DNfa4H/xcw" tabindex="-1" role="presentation"></a>Podemos usar esto para crear una animación. El siguiente documento muestra una imagen de un gato que se mueve en una elipse:</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-SCZYa8azNm" href="#c-SCZYa8azNm" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span> style=<span class="tok-string">"</span>text-align: <span class="tok-atom">center</span><span class="tok-string">"</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-IRH5jC7N4Q" href="#c-IRH5jC7N4Q" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span> style=<span class="tok-string">"</span>text-align: <span class="tok-atom">center</span><span class="tok-string">"</span>> <<span class="tok-typeName">img</span> src=<span class="tok-string">"img/cat.png"</span> style=<span class="tok-string">"</span>position: <span class="tok-atom">relative</span><span class="tok-string">"</span>> </<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">cat</span> = document.querySelector(<span class="tok-string">"img"</span>); - <span class="tok-keyword">let</span> <span class="tok-definition">angle</span> = Math.PI / <span class="tok-number">2</span>; - <span class="tok-keyword">function</span> <span class="tok-definition">animate</span>(<span class="tok-definition">time</span>, <span class="tok-definition">lastTime</span>) { - <span class="tok-keyword">if</span> (lastTime != <span class="tok-keyword">null</span>) { - angle += (time - lastTime) * <span class="tok-number">0.001</span>; + <span class="tok-keyword">let</span> <span class="tok-definition">gato</span> = document.querySelector(<span class="tok-string">"img"</span>); + <span class="tok-keyword">let</span> <span class="tok-definition">ángulo</span> = Math.PI / <span class="tok-number">2</span>; + <span class="tok-keyword">function</span> <span class="tok-definition">animar</span>(<span class="tok-definition">esteMomento</span>, <span class="tok-definition">últimaVez</span>) { + <span class="tok-keyword">if</span> (últimaVez != <span class="tok-keyword">null</span>) { + ángulo += (esteMomento - últimaVez) * <span class="tok-number">0.001</span>; } - cat.style.top = (Math.sin(angle) * <span class="tok-number">20</span>) + <span class="tok-string">"px"</span>; - cat.style.left = (Math.cos(angle) * <span class="tok-number">200</span>) + <span class="tok-string">"px"</span>; - requestAnimationFrame(<span class="tok-definition">newTime</span> => animate(newTime, time)); + gato.style.top = (Math.sin(ángulo) * <span class="tok-number">20</span>) + <span class="tok-string">"px"</span>; + gato.style.left = (Math.cos(ángulo) * <span class="tok-number">200</span>) + <span class="tok-string">"px"</span>; + requestAnimationFrame(<span class="tok-definition">nuevoMomento</span> => animar(nuevoMomento, esteMomento)); } - requestAnimationFrame(animate); + requestAnimationFrame(animar); </<span class="tok-typeName">script</span>></pre> <p><a class="p_ident" id="p-fEXgVZgjYG" href="#p-fEXgVZgjYG" tabindex="-1" role="presentation"></a>Nuestra imagen está centrada en la página y tiene una <code>posición</code> de <code>relative</code>. Actualizaremos repetidamente los estilos <code>top</code> e <code>left</code> de esa imagen para moverla.</p> -<p id="animationFrame"><a class="p_ident" id="p-RqVeyWHVNp" href="#p-RqVeyWHVNp" tabindex="-1" role="presentation"></a>El script utiliza <code>requestAnimationFrame</code> para programar la ejecución de la función <code>animar</code> siempre que el navegador esté listo para repintar la pantalla. La función <code>animar</code> a su vez vuelve a llamar a <code>requestAnimationFrame</code> para programar la siguiente actualización. Cuando la ventana del navegador (o pestaña) está activa, esto provocará que las actualizaciones ocurran a una velocidad de aproximadamente 60 por segundo, lo que suele producir una animación atractiva.</p> +<p id="animationFrame"><a class="p_ident" id="p-eZlNNa3Nt+" href="#p-eZlNNa3Nt+" tabindex="-1" role="presentation"></a>El script utiliza <code>requestAnimationFrame</code> para programar la ejecución de la función <code>animar</code> siempre que el navegador esté listo para repintar la pantalla. La función <code>animar</code> a su vez vuelve a llamar a <code>requestAnimationFrame</code> para programar la siguiente actualización. Cuando la ventana del navegador (o pestaña) está activa, esto provocará que las actualizaciones ocurran a una velocidad de aproximadamente 60 actualizaciones por segundo, lo que suele producir una animación bastante vistosa.</p> + +<div class="translator-note"><p><strong>N. del T.:</strong> La función <code>requestAnimationFrame()</code> es una función estándar en JavaScript que forma parte de la especificación de la Web API, diseñada para facilitar la creación de animaciones en el navegador de manera eficiente. Esta solicita al navegador que ejecute una función antes del próximo repintado de pantalla, pasando como argumento a dicha función la marca de tiempo actual.</p> +</div> + +<p><a class="p_ident" id="p-pmgnq2fRkz" href="#p-pmgnq2fRkz" tabindex="-1" role="presentation"></a>Si simplemente actualizáramos el DOM en un bucle, la página se congelaría y no aparecería nada en la pantalla. Los navegadores no actualizan su pantalla mientras se ejecuta un programa JavaScript, ni permiten ninguna interacción con la página. Por eso necesitamos <code>requestAnimationFrame</code> —le indica al navegador que hemos terminado por ahora, y puede continuar haciendo las cosas que hacen los navegadores, como actualizar la pantalla y responder a las acciones del usuario.</p> -<p><a class="p_ident" id="p-pmgnq2fRkz" href="#p-pmgnq2fRkz" tabindex="-1" role="presentation"></a>Si simplemente actualizáramos el DOM en un bucle, la página se congelaría y nada aparecería en la pantalla. Los navegadores no actualizan su pantalla mientras se ejecuta un programa JavaScript, ni permiten ninguna interacción con la página. Por eso necesitamos <code>requestAnimationFrame</code> — le indica al navegador que hemos terminado por ahora, y puede continuar haciendo las cosas que hacen los navegadores, como actualizar la pantalla y responder a las acciones del usuario.</p> +<p><a class="p_ident" id="p-jsGXLKizvY" href="#p-jsGXLKizvY" tabindex="-1" role="presentation"></a>La función de animación recibe el tiempo actual como argumento. Para asegurar que el movimiento del gato por milisegundo sea estable, basa la velocidad a la que cambia el ángulo en la diferencia entre el momento actual y el último momento en que se ejecutó la función. Si simplemente modificara el ángulo en una cantidad fija por paso, el movimiento se interrumpiría si, por ejemplo, otra tarea pesada que se está ejecutando en la misma computadora impidiera que la función se ejecutara durante una fracción de segundo.</p> -<p><a class="p_ident" id="p-jsGXLKizvY" href="#p-jsGXLKizvY" tabindex="-1" role="presentation"></a>La función de animación recibe el tiempo actual como argumento. Para asegurar que el movimiento del gato por milisegundo sea estable, basa la velocidad a la que cambia el ángulo en la diferencia entre el tiempo actual y el último tiempo en que se ejecutó la función. Si simplemente moviera el ángulo por una cantidad fija por paso, el movimiento se interrumpiría si, por ejemplo, otra tarea pesada que se está ejecutando en la misma computadora impidiera que la función se ejecutara durante una fracción de segundo.</p> +<p id="sin_cos"><a class="p_ident" id="p-kst9P8Te8N" href="#p-kst9P8Te8N" tabindex="-1" role="presentation"></a>Moverse en círculos es algo que puede hacerse utilizando las funciones trigonométricas <code>Math.cos</code> y <code>Math.sin</code>. Para aquellos que no estén familiarizados con ellas, las presentaré brevemente ya que ocasionalmente las utilizaremos en este libro.</p> -<p id="sin_cos"><a class="p_ident" id="p-kst9P8Te8N" href="#p-kst9P8Te8N" tabindex="-1" role="presentation"></a>Moverse en círculos se hace utilizando las funciones trigonométricas <code>Math.cos</code> y <code>Math.sin</code>. Para aquellos que no estén familiarizados con ellas, las presentaré brevemente ya que ocasionalmente las utilizaremos en este libro.</p> +<p><a class="p_ident" id="p-vthchjpwSJ" href="#p-vthchjpwSJ" tabindex="-1" role="presentation"></a><code>Math.cos</code> y <code>Math.sin</code> son útiles para encontrar puntos que se encuentran en un círculo alrededor del punto (0,0) con un radio de longitud uno. Ambas funciones interpretan su argumento como la posición en este círculo, correspondiendo el 0 con el punto en el extremo derecho del círculo, avanzando en el sentido de las agujas del reloj hasta llegamos a 2π (aproximadamente 6,28) habiendo recorrido así todo el círculo. <code>Math.cos</code> te indica la coordenada x del punto que corresponde a la posición dada, y <code>Math.sin</code> devuelve la coordenada y. Las posiciones (o ángulos) mayores que 2π o menores que 0 son válidos, la rotación se repite de manera que <em>a</em>+2π se refiere al mismo ángulo que <em>a</em>.</p> -<p><a class="p_ident" id="p-vthchjpwSJ" href="#p-vthchjpwSJ" tabindex="-1" role="presentation"></a><code>Math.cos</code> y <code>Math.sin</code> son útiles para encontrar puntos que se encuentran en un círculo alrededor del punto (0,0) con un radio de uno. Ambas funciones interpretan su argumento como la posición en este círculo, con cero denotando el punto en el extremo derecho del círculo, avanzando en el sentido de las agujas del reloj hasta que 2π (aproximadamente 6,28) nos ha llevado alrededor de todo el círculo. <code>Math.cos</code> te indica la coordenada x del punto que corresponde a la posición dada, y <code>Math.sin</code> devuelve la coordenada y. Las posiciones (o ángulos) mayores que 2π o menores que 0 son válidos, la rotación se repite de manera que <em>a</em>+2π se refiere al mismo ángulo que <em>a</em>.</p> +<div class="translator-note"><p><strong>N. del T.:</strong> normalmente, las funciones <code>cos</code> y <code>sin</code> en matemáticas, al aumentar su argumento desde 0 hasta 2π, describen una circunferencia en el sentido <strong>opuesto</strong> al de las agujas del reloj. No obstante, en tal caso, las coordenadas están expresadas de modo que el eje vertical apunta hacia arriba. En el caso de las funciones <code>cos</code> y <code>sin</code> que estamos usando en este código en JavaScript, el eje vertical apunta hacia abajo (pues se mide respecto del <code>top</code>), por lo que el sentido de rotación es el opuesto al usual que encontramos en matemáticas.</p> +</div> -<p><a class="p_ident" id="p-sMFMQb4ytS" href="#p-sMFMQb4ytS" tabindex="-1" role="presentation"></a>Esta unidad para medir ángulos se llama radianes — un círculo completo son 2π radianes, similar a cómo son 360 grados al medir en grados. La constante π está disponible como <code>Math.PI</code> en JavaScript.</p><figure><img src="img/cos_sin.svg" alt="Diagrama que muestra el uso del coseno y el seno para calcular coordenadas. Se muestra un círculo con radio 1 con dos puntos en él. El ángulo desde el lado derecho del círculo hasta el punto, en radianes, se utiliza para calcular la posición de cada punto usando 'cos(ángulo)' para la distancia horizontal desde el centro del círculo y sin(ángulo) para la distancia vertical."></figure> +<p><a class="p_ident" id="p-sMFMQb4ytS" href="#p-sMFMQb4ytS" tabindex="-1" role="presentation"></a>Esta unidad para medir ángulos se llama radián —un círculo completo comprende 2π radianes, igual que con los 360 grados al medir en grados. La constante π está disponible como <code>Math.PI</code> en JavaScript.</p><figure><img src="img/cos_sin.svg" alt="Diagrama que muestra el uso del coseno y el seno para calcular coordenadas. Se muestra un círculo con radio 1 con dos puntos en él. El ángulo desde el lado derecho del círculo hasta el punto, en radianes, se utiliza para calcular la posición de cada punto usando 'cos(ángulo)' para la distancia horizontal desde el centro del círculo y sin(ángulo) para la distancia vertical."></figure> -<p><a class="p_ident" id="p-ocGDBG/Yqx" href="#p-ocGDBG/Yqx" tabindex="-1" role="presentation"></a>El código de animación del gato mantiene un contador, <code>angle</code>, para el ángulo actual de la animación e incrementa el mismo cada vez que se llama la función <code>animate</code>. Luego puede usar este ángulo para calcular la posición actual del elemento de imagen. El estilo <code>top</code> es calculado con <code>Math.sin</code> y multiplicado por 20, que es el radio vertical de nuestra elipse. El estilo <code>left</code> se basa en <code>Math.cos</code> y multiplicado por 200 para que la elipse sea mucho más ancha que alta.</p> +<p><a class="p_ident" id="p-ocGDBG/Yqx" href="#p-ocGDBG/Yqx" tabindex="-1" role="presentation"></a>El código de animación del gato mantiene un contador, <code>ángulo</code>, para el ángulo actual de la animación e incrementa el mismo cada vez que se llama la función <code>animar</code>. Luego puede usar este ángulo para calcular la posición actual del elemento de imagen. El estilo <code>top</code> es calculado con <code>Math.sin</code> y multiplicado por 20, que es el radio vertical de nuestra elipse. El estilo <code>left</code> se basa en <code>Math.cos</code> y multiplicado por 200 para que la elipse sea mucho más ancha que alta.</p> -<p><a class="p_ident" id="p-Xqf+1pzb5Y" href="#p-Xqf+1pzb5Y" tabindex="-1" role="presentation"></a>Ten en cuenta que los estilos usualmente necesitan <em>unidades</em>. En este caso, tenemos que añadir <code>"px"</code> al número para indicarle al navegador que estamos contando en píxeles (en lugar de centímetros, “ems” u otras unidades). Esto es fácil de olvidar. Usar números sin unidades resultará en que tu estilo sea ignorado — a menos que el número sea 0, lo cual siempre significa lo mismo, independientemente de su unidad.</p> +<p><a class="p_ident" id="p-Xqf+1pzb5Y" href="#p-Xqf+1pzb5Y" tabindex="-1" role="presentation"></a>Ten en cuenta que los estilos normalmente necesitan <em>unidades</em>. En este caso, tenemos que añadir <code>"px"</code> al número para indicarle al navegador que estamos contando en píxeles (en lugar de centímetros, “ems” u otras unidades). Esto es fácil de olvidar. Usar números sin unidades resultará en que tu estilo sea ignorado — a menos que el número sea 0, lo cual siempre representa lo mismo, independientemente de su unidad.</p> <h2><a class="h_ident" id="h-NUFOUyK+lw" href="#h-NUFOUyK+lw" tabindex="-1" role="presentation"></a>Resumen</h2> -<p><a class="p_ident" id="p-rZ35pctbjh" href="#p-rZ35pctbjh" tabindex="-1" role="presentation"></a>Los programas de JavaScript pueden inspeccionar e interferir con el documento que el navegador está mostrando a través de una estructura de datos llamada el DOM. Esta estructura de datos representa el modelo del documento del navegador, y un programa de JavaScript puede modificarlo para cambiar el documento visible.</p> +<p><a class="p_ident" id="p-rZ35pctbjh" href="#p-rZ35pctbjh" tabindex="-1" role="presentation"></a>Los programas de JavaScript pueden inspeccionar e interferir con el documento que el navegador está mostrando a través de una estructura de datos conocida como el DOM. Esta estructura de datos representa el modelo del documento del navegador, y un programa de JavaScript puede modificarlo para cambiar el documento visible.</p> <p><a class="p_ident" id="p-tjM39Si6eN" href="#p-tjM39Si6eN" tabindex="-1" role="presentation"></a>El DOM está organizado como un árbol, en el cual los elementos están dispuestos jerárquicamente de acuerdo a la estructura del documento. Los objetos que representan elementos tienen propiedades como <code>parentNode</code> y <code>childNodes</code>, las cuales pueden ser usadas para navegar a través de este árbol.</p> @@ -442,11 +448,11 @@ <h3 id="exercise_table"><a class="i_ident" id="i-z5OvB5hZU/" href="#i-z5OvB5hZU/ <p><a class="p_ident" id="p-pufpiqkCqY" href="#p-pufpiqkCqY" tabindex="-1" role="presentation"></a>Una tabla HTML se construye con la siguiente estructura de etiquetas:</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-np7yTOJmzm" href="#c-np7yTOJmzm" tabindex="-1" role="presentation"></a><<span class="tok-typeName">table</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-MudCG4cYiG" href="#c-MudCG4cYiG" tabindex="-1" role="presentation"></a><<span class="tok-typeName">table</span>> <<span class="tok-typeName">tr</span>> - <<span class="tok-typeName">th</span>>nombre</<span class="tok-typeName">th</span>> - <<span class="tok-typeName">th</span>>altura</<span class="tok-typeName">th</span>> - <<span class="tok-typeName">th</span>>lugar</<span class="tok-typeName">th</span>> + <<span class="tok-typeName">th</span>>name</<span class="tok-typeName">th</span>> + <<span class="tok-typeName">th</span>>height</<span class="tok-typeName">th</span>> + <<span class="tok-typeName">th</span>>place</<span class="tok-typeName">th</span>> </<span class="tok-typeName">tr</span>> <<span class="tok-typeName">tr</span>> <<span class="tok-typeName">td</span>>Kilimanjaro</<span class="tok-typeName">td</span>> @@ -457,7 +463,7 @@ <h3 id="exercise_table"><a class="i_ident" id="i-z5OvB5hZU/" href="#i-z5OvB5hZU/ <p><a class="p_ident" id="p-XmKxZkmv87" href="#p-XmKxZkmv87" tabindex="-1" role="presentation"></a>Dado un conjunto de datos de montañas, un array de objetos con propiedades <code>name</code>, <code>height</code>, y <code>place</code>, genera la estructura DOM para una tabla que enumera los objetos. Debería haber una columna por clave y una fila por objeto, además de una fila de encabezado con elementos <code><th></code> en la parte superior, enumerando los nombres de las columnas.</p> -<p><a class="p_ident" id="p-jM6gk2QtmI" href="#p-jM6gk2QtmI" tabindex="-1" role="presentation"></a>Escribe esto de manera que las columnas se deriven automáticamente de los objetos, tomando los nombres de las propiedades del primer objeto en los datos.</p> +<p><a class="p_ident" id="p-jM6gk2QtmI" href="#p-jM6gk2QtmI" tabindex="-1" role="presentation"></a>Escribe esto de manera que las columnas se objengan automáticamente de los objetos, tomando los nombres de las propiedades del primer objeto en los datos.</p> <p><a class="p_ident" id="p-JSYCxXRw8Y" href="#p-JSYCxXRw8Y" tabindex="-1" role="presentation"></a>Muestra la tabla resultante en el documento agregándola al elemento que tenga un atributo <code>id</code> de <code>"mountains"</code>.</p> @@ -485,7 +491,7 @@ <h3 id="exercise_table"><a class="i_ident" id="i-z5OvB5hZU/" href="#i-z5OvB5hZU/ <p><a class="p_ident" id="p-XNvmBiuS1c" href="#p-XNvmBiuS1c" tabindex="-1" role="presentation"></a>Puedes usar <code>document.<wbr>createElement</code> para crear nuevos nodos de elementos, <code>document.<wbr>createTextNode</code> para crear nodos de texto y el método <code>appendChild</code> para poner nodos en otros nodos.</p> -<p><a class="p_ident" id="p-Lcrj8SZVc5" href="#p-Lcrj8SZVc5" tabindex="-1" role="presentation"></a>Querrás iterar sobre los nombres de las claves una vez para completar la fila superior y luego nuevamente para cada objeto en el array para construir las filas de datos. Para obtener un array de nombres de claves del primer objeto, <code>Object.keys</code> será útil.</p> +<p><a class="p_ident" id="p-HRHhdmEcpl" href="#p-HRHhdmEcpl" tabindex="-1" role="presentation"></a>Querrás iterar sobre los nombres de las claves una vez para completar la fila superior y luego nuevamente para cada objeto en el array para construir las filas de datos. Para obtener un array de nombres de claves del primer objeto, te será útil el método <code>Object.keys</code>.</p> <p><a class="p_ident" id="p-ny4p+IvE+e" href="#p-ny4p+IvE+e" tabindex="-1" role="presentation"></a>Para agregar la tabla al nodo padre correcto, puedes usar <code>document.<wbr>getElementById</code> o <code>document.<wbr>querySelector</code> con <code>"#mountains"</code> para encontrar el nodo.</p> @@ -517,9 +523,9 @@ <h3><a class="i_ident" id="i-2EZh9/DLl/" href="#i-2EZh9/DLl/" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-Ei5S1OWx7h" href="#p-Ei5S1OWx7h" tabindex="-1" role="presentation"></a>La solución es más fácil de expresar con una función recursiva, similar a la <a href="14_dom.html#talksAbout">función <code>talksAbout</code></a> definida anteriormente en este capítulo.</p> +<p><a class="p_ident" id="p-Ei5S1OWx7h" href="#p-Ei5S1OWx7h" tabindex="-1" role="presentation"></a>La solución es más fácil de expresar con una función recursiva, similar a la <a href="14_dom.html#talksAbout">función <code>hablaSobre</code></a> definida anteriormente en este capítulo.</p> -<p><a class="p_ident" id="p-ElU7W0NdPy" href="#p-ElU7W0NdPy" tabindex="-1" role="presentation"></a>Puedes llamar a <code>byTagname</code> a sí misma de manera recursiva, concatenando los arrays resultantes para producir la salida. O puedes crear una función interna que se llame a sí misma de manera recursiva y que tenga acceso a un enlace de array definido en la función externa, al cual puede agregar los elementos coincidentes que encuentre. No olvides llamar a la función interna una vez desde la función externa para iniciar el proceso.</p> +<p><a class="p_ident" id="p-ElU7W0NdPy" href="#p-ElU7W0NdPy" tabindex="-1" role="presentation"></a>Puedes llamar a la misma <code>byTagname</code> de manera recursiva, concatenando los arrays resultantes para producir la salida. O puedes crear una función interna que se llame a sí misma de manera recursiva y que tenga acceso a una asociación de array definida en la función externa, a la cual puede agregar los elementos coincidentes que encuentre. No olvides llamar a la función interna una vez desde la función externa para iniciar el proceso.</p> <p><a class="p_ident" id="p-uSFxM3zQ/0" href="#p-uSFxM3zQ/0" tabindex="-1" role="presentation"></a>La función recursiva debe verificar el tipo de nodo. Aquí estamos interesados solo en el tipo de nodo 1 (<code>Node.<wbr>ELEMENT_NODE</code>). Para estos nodos, debemos recorrer sus hijos y, para cada hijo, ver si el hijo coincide con la consulta mientras también hacemos una llamada recursiva en él para inspeccionar sus propios hijos.</p> @@ -558,7 +564,7 @@ <h3><a class="i_ident" id="i-75abwvO+MO" href="#i-75abwvO+MO" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-jfbbeH+fXF" href="#p-jfbbeH+fXF" tabindex="-1" role="presentation"></a><code>Math.cos</code> y <code>Math.sin</code> miden los ángulos en radianes, donde un círculo completo es 2π. Para un ángulo dado, puedes obtener el ángulo opuesto sumando la mitad de este, que es <code>Math.PI</code>. Esto puede ser útil para poner el sombrero en el lado opuesto de la órbita.</p> +<p><a class="p_ident" id="p-jfbbeH+fXF" href="#p-jfbbeH+fXF" tabindex="-1" role="presentation"></a><code>Math.cos</code> y <code>Math.sin</code> miden los ángulos en radianes, donde una circunferencia completa consta de 2π. Para un ángulo dado, puedes obtener el ángulo opuesto sumando <code>Math.PI</code>, que es la mitad de los radianes de los que consta una circunferencia. Esto puede ser útil para poner el sombrero en el lado opuesto de la órbita.</p> </div></details><nav><a href="13_browser.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="15_event.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button> </nav> From 6ec828cf55c47bbaf13d66102800c7a07e002de3 Mon Sep 17 00:00:00 2001 From: ckdvk <javicemarpe@gmail.com> Date: Sat, 22 Feb 2025 14:48:35 +0800 Subject: [PATCH 26/36] =?UTF-8?q?revisado=20cap=C3=ADtulo=2015?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 15_event.md | 254 ++++++++++++++++++++-------------------- html/15_event.html | 280 ++++++++++++++++++++++----------------------- 2 files changed, 267 insertions(+), 267 deletions(-) diff --git a/15_event.md b/15_event.md index bb24743a..e7c49826 100644 --- a/15_event.md +++ b/15_event.md @@ -2,7 +2,7 @@ {{quote {author: "Marco Aurelio", title: Meditaciones, chapter: true} -Tienes poder sobre tu mente, no sobre los eventos externos. Date cuenta de esto y encontrarás fuerza. +Tienes poder sobre tu mente, no sobre los eventos externos. Comprende esto y hallarás la fuerza. quote}} @@ -10,19 +10,19 @@ quote}} {{figure {url: "img/chapter_picture_15.jpg", alt: "Ilustración que muestra una máquina de Rube Goldberg que involucra una pelota, una balanza, un par de tijeras y un martillo, los cuales se afectan en una reacción en cadena que enciende una bombilla.", chapter: "framed"}}} -Algunos programas trabajan con la entrada directa del usuario, como acciones del ratón y del teclado. Ese tipo de entrada no está disponible de antemano, como una estructura de datos bien organizada, llega pieza por pieza, en tiempo real, y el programa debe responder a medida que sucede. +Algunos programas trabajan directamente con la interacción del usuario, como acciones del ratón o del teclado. Ese tipo de entrada no está disponible de antemano como una estructura de datos bien organizada —llega pieza poco a poco, en tiempo real, y el programa debe responder a medida que sucede. ## Controladores de Eventos {{index sondeo, "botón", "tiempo real"}} -Imagina una interfaz donde la única forma de saber si una tecla en el ((teclado)) está siendo presionada es leyendo el estado actual de esa tecla. Para poder reaccionar a las pulsaciones de teclas, tendrías que leer constantemente el estado de la tecla para capturarla antes de que se libere nuevamente. Sería peligroso realizar otras computaciones intensivas en tiempo, ya que podrías perder una pulsación de tecla. +Imagina una interfaz donde la única forma de saber si una tecla en el ((teclado)) está siendo presionada es leyendo el estado actual de esa tecla. Para poder reaccionar a las pulsaciones de teclas, tendrías que leer constantemente el estado de la tecla para capturarla antes de que se libere nuevamente. Sería peligroso realizar otros procedimientos intensivos en cuanto a tiempo, ya que podrías perder una pulsación de tecla por el camino. -Algunas máquinas primitivas manejan la entrada de esa manera. Un paso adelante sería que el hardware o el sistema operativo noten la pulsación de tecla y la pongan en una cola. Un programa puede luego verificar periódicamente la cola en busca de nuevos eventos y reaccionar a lo que encuentre allí. +Algunas máquinas primitivas manejan este tipo de entrada de esa manera. Un paso adelante sería que el hardware o el sistema operativo noten la pulsación de tecla y la pongan en una cola. Un programa puede luego verificar periódicamente la cola en busca de nuevos eventos y reaccionar a lo que encuentre allí. {{index capacidad de respuesta, "experiencia de usuario"}} -Por supuesto, tiene que recordar mirar la cola y hacerlo a menudo, porque cualquier tiempo transcurrido entre la presión de la tecla y la notificación del evento por parte del programa hará que el software se sienta sin respuesta. Este enfoque se llama _((sondeo))_. La mayoría de los programadores prefieren evitarlo. +Por supuesto, tiene que recordar mirar la cola y hacerlo a menudo, porque cualquier tiempo transcurrido entre la presión de la tecla y la notificación del evento por parte del programa hará que el software se sienta como sin respuesta. Este enfoque se llama _((sondeo))_. La mayoría de los programadores prefieren evitarlo. {{index "función de devolución de llamada", "manejo de eventos"}} @@ -32,7 +32,7 @@ Un mecanismo mejor es que el sistema notifique activamente a nuestro código cua <p>Haz clic en este documento para activar el manejador.</p> <script> window.addEventListener("click", () => { - console.log("¿Llamaste?"); + console.log("¿Quién es?"); }); </script> ``` @@ -41,26 +41,26 @@ Un mecanismo mejor es que el sistema notifique activamente a nuestro código cua La asignación `window` se refiere a un objeto integrado proporcionado por el navegador. Representa la ventana del navegador que contiene el documento. Llamar a su método `addEventListener` registra el segundo argumento para que se llame cada vez que ocurra el evento descrito por su primer argumento. -## Eventos y nodos DOM +## Eventos y nodos del DOM {{index "método addEventListener", "manejo de eventos", "objeto window", navegador, [DOM, eventos]}} -Cada controlador de eventos del navegador se registra en un contexto. En el ejemplo anterior llamamos a `addEventListener` en el objeto `window` para registrar un controlador para toda la ventana. Un método similar también se encuentra en elementos del DOM y algunos otros tipos de objetos. Los escuchas de eventos solo se llaman cuando el evento ocurre en el contexto del objeto en el que están registrados. +Cada controlador de eventos del navegador se registra en un contexto. En el ejemplo anterior llamamos a `addEventListener` en el objeto `window` para registrar un controlador para toda la ventana. También podemos encontrar un método similar en elementos del DOM y algunos otros tipos de objetos. Los escuchas de eventos solo se llaman cuando el evento ocurre en el contexto del objeto en el que están registrados. ```{lang: html} <button>Haz clic</button> <p>No hay manejador aquí.</p> <script> - let button = document.querySelector("button"); - button.addEventListener("click", () => { - console.log("Botón clickeado."); + let botón = document.querySelector("button"); + botón.addEventListener("click", () => { + console.log("Botón cliqueado."); }); </script> ``` {{index "evento de clic", "botón (etiqueta HTML)"}} -Ese ejemplo adjunta un manejador al nodo del botón. Los clics en el botón hacen que se ejecute ese manejador, pero los clics en el resto del documento no lo hacen. +En este ejemplo se adjunta un manejador al nodo del botón. Los clics en el botón hacen que se ejecute ese manejador, pero los clics en el resto del documento no lo hacen. {{index "atributo onclick", encapsulamiento}} @@ -70,35 +70,35 @@ Pero un nodo solo puede tener un atributo `onclick`, por lo que solo puedes regi {{index "método removeEventListener"}} -El método `removeEventListener`, llamado con argumentos similares a `addEventListener`, remueve un manejador. +El método `removeEventListener`, llamado con argumentos similares a `addEventListener`, elimina un manejador. ```{lang: html} <button>Botón de acción única</button> <script> - let button = document.querySelector("button"); + let botón = document.querySelector("button"); function unaVez() { console.log("¡Hecho!"); - button.removeEventListener("click", unaVez); + botón.removeEventListener("click", unaVez); } - button.addEventListener("click", unaVez); + botón.addEventListener("click", unaVez); </script> ``` {{index ["función", "como valor"]}} -La función proporcionada a `removeEventListener` debe ser el mismo valor de función que se proporcionó a `addEventListener`. Por lo tanto, para anular el registro de un manejador, querrás darle un nombre a la función (`unaVez`, en el ejemplo) para poder pasar el mismo valor de función a ambos métodos. +La función proporcionada a `removeEventListener` debe ser el mismo valor de función que se proporcionó a `addEventListener`. Por lo tanto, para anular el registro de un manejador, tendrás que darle un nombre a la función (`unaVez`, en el ejemplo) para poder pasar el mismo valor de función a ambos métodos. ## Objetos de eventos {{index "propiedad de botón", "manejo de eventos"}} -Aunque lo hemos ignorado hasta ahora, las funciones de manejadores de eventos reciben un argumento: el _((objeto de evento))_. Este objeto contiene información adicional sobre el evento. Por ejemplo, si queremos saber _cuál_ ((botón del mouse)) se presionó, podemos mirar la propiedad `button` del objeto de evento. +Aunque lo hemos ignorado hasta ahora, las funciones de manejadores de eventos reciben un argumento: el _((objeto de evento))_. Este objeto contiene información adicional sobre el evento. Por ejemplo, si queremos saber _qué_ ((botón del ratón)) se presionó, podemos mirar la propiedad `button` del objeto de evento. ```{lang: html} <button>Haz clic como quieras</button> <script> - let button = document.querySelector("button"); - button.addEventListener("mousedown", event => { + let botón = document.querySelector("button"); + botón.addEventListener("mousedown", event => { if (event.button == 0) { console.log("Botón izquierdo"); } else if (event.button == 1) { @@ -126,7 +126,7 @@ Para la mayoría de tipos de evento, los manejadores registrados en nodos con hi {{index "manejo de eventos"}} -Pero si tanto el párrafo como el botón tienen un controlador, el controlador más específico —el del botón— tiene prioridad para ejecutarse primero. Se dice que el evento *se propaga* hacia afuera, desde el nodo donde ocurrió hacia el nodo padre de ese nodo y hasta la raíz del documento. Finalmente, después de que todos los controladores registrados en un nodo específico hayan tenido su turno, los controladores registrados en toda la ((ventana)) tienen la oportunidad de responder al evento. +Pero si tanto el párrafo como el botón tienen un controlador, el controlador más específico —el del botón— tiene prioridad para ejecutarse primero. Se dice que el evento *se propaga* hacia afuera, desde el nodo donde ocurrió hacia el nodo padre de ese nodo y hasta la raíz del documento. Finalmente, después de que todos los manejadores registrados en un nodo específico hayan tenido su turno, los manejadores registrados en toda la ((ventana)) tienen la oportunidad de responder al evento. {{index "método stopPropagation", "evento click"}} @@ -134,17 +134,17 @@ En cualquier momento, un controlador de eventos puede llamar al método `stopPro {{index "evento mousedown", "evento de puntero"}} -El siguiente ejemplo registra controladores de `"mousedown"` tanto en un botón como en el párrafo que lo rodea. Cuando se hace clic con el botón derecho del ratón, el controlador del botón llama a `stopPropagation`, lo que evitará que se ejecute el controlador en el párrafo. Cuando el botón se hace clic con otro ((botón del ratón)), ambos controladores se ejecutarán. +El siguiente ejemplo registra manejadores de `"mousedown"` tanto en un botón como en el párrafo que lo rodea. Cuando se hace clic con el botón derecho del ratón, el manejador del botón llama a `stopPropagation`, lo que evitará que se ejecute el manejador en el párrafo. Cuando se hace clic en el botón con otro ((botón del ratón)), ambos manejadores se ejecutarán. ```{lang: html} <p>Un párrafo con un <button>botón</button>.</p> <script> - let para = document.querySelector("p"); - let button = document.querySelector("button"); - para.addEventListener("mousedown", () => { + let parr = document.querySelector("p"); + let botón = document.querySelector("button"); + parr.addEventListener("mousedown", () => { console.log("Controlador para el párrafo."); }); - button.addEventListener("mousedown", event => { + botón.addEventListener("mousedown", event => { console.log("Controlador para el botón."); if (event.button == 2) event.stopPropagation(); }); @@ -174,11 +174,11 @@ También es posible usar la propiedad `target` para abarcar un amplio rango para {{index scrolling, "comportamiento predeterminado", "manejo de eventos"}} -Muchos eventos tienen una acción predeterminada asociada a ellos. Si haces clic en un ((enlace)), serás llevado al destino del enlace. Si presionas la flecha hacia abajo, el navegador desplazará la página hacia abajo. Si haces clic derecho, obtendrás un menú contextual. Y así sucesivamente. +Muchos eventos tienen una acción predeterminada asociada a ellos. Si haces clic en un ((enlace)), serás llevado al destino del enlace. Si presionas la flecha hacia abajo, el navegador desplazará la página hacia abajo. Si haces clic derecho, obtendrás un menú contextual. Y así con todo. {{index "método preventDefault"}} -Para la mayoría de los tipos de eventos, los controladores de eventos de JavaScript se ejecutan _antes_ de que ocurra el comportamiento predeterminado. Si el controlador no desea que este comportamiento normal ocurra, típicamente porque ya se encargó de manejar el evento, puede llamar al método `preventDefault` en el objeto de evento. +Para la mayoría de los tipos de eventos, los manejadores de eventos de JavaScript se ejecutan _antes_ de que ocurra el comportamiento predeterminado. Si el manejador no desea que este comportamiento normal ocurra, usualmente porque ya se ha encargado de manejar el evento, puede llamar al método `preventDefault` en el objeto de evento. {{index expectativas}} @@ -187,8 +187,8 @@ Esto se puede utilizar para implementar tus propios atajos de teclado o menús c ```{lang: html} <a href="https://developer.mozilla.org/">MDN</a> <script> - let link = document.querySelector("a"); - link.addEventListener("click", event => { + let enlace = document.querySelector("a"); + enlace.addEventListener("click", event => { console.log("¡Incorrecto!"); event.preventDefault(); }); @@ -197,9 +197,9 @@ Esto se puede utilizar para implementar tus propios atajos de teclado o menús c {{index usabilidad}} -Trata de no hacer este tipo de cosas a menos que tengas una razón realmente válida. Será desagradable para las personas que utilicen tu página cuando se rompa el comportamiento esperado. +Trata de no hacer este tipo de cosas a menos que tengas una buena razón para hacerlo. Será desagradable para las personas que utilicen tu página cuando se rompa el comportamiento esperado. -Dependiendo del navegador, algunos eventos no se pueden interceptar en absoluto. En Chrome, por ejemplo, el atajo de teclado para cerrar la pestaña actual (control-W o command-W) no se puede manejar con JavaScript. +Dependiendo del navegador, algunos eventos no se pueden interceptar. En Chrome, por ejemplo, el atajo de teclado para cerrar la pestaña actual ([control]{keyname}-[W]{keyname} o [command]{keyname}-[W]{keyname}) no se puede manejar con JavaScript. ## Eventos de teclado @@ -225,15 +225,15 @@ Cuando se presiona una tecla en el teclado, tu navegador dispara un evento `"key {{index "tecla repetitiva"}} -A pesar de su nombre, `"keydown"` se dispara no solo cuando la tecla se presiona físicamente hacia abajo. Cuando se presiona y se mantiene una tecla, el evento se vuelve a disparar cada vez que la tecla _se repite_. A veces tienes que tener cuidado con esto. Por ejemplo, si agregas un botón al DOM cuando se presiona una tecla y lo eliminas de nuevo cuando se suelta la tecla, podrías agregar accidentalmente cientos de botones cuando se mantiene presionada la tecla durante más tiempo. +A pesar de su nombre, `"keydown"` se dispara no solo cuando la tecla se presiona físicamente hacia abajo. Cuando se presiona y se mantiene una tecla, el evento se vuelve a disparar cada vez que la tecla _se repite_. A veces tienes que tener cuidado con esto. Por ejemplo, si agregas un botón al DOM cuando se presiona una tecla y lo eliminas de nuevo cuando se suelta la tecla, podrías agregar sin querer cientos de botones al mantener presionada la tecla durante más tiempo. {{index "propiedad key"}} -El ejemplo observó la propiedad `key` del objeto evento para ver sobre qué tecla es el evento. Esta propiedad contiene una cadena que, para la mayoría de las teclas, corresponde a lo que escribirías al presionar esa tecla. Para teclas especiales como [enter]{keyname}, contiene una cadena que nombra la tecla (`"Enter"`, en este caso). Si mantienes presionado [shift]{keyname} mientras presionas una tecla, eso también puede influir en el nombre de la tecla: `"v"` se convierte en `"V"`, y `"1"` puede convertirse en `"!"`, si eso es lo que produce al presionar [shift]{keyname}-1 en tu teclado. +El ejemplo observó la propiedad `key` del objeto evento para ver sobre qué tecla es el evento. Esta propiedad contiene una cadena que, para la mayoría de las teclas, corresponde a lo que escribirías al presionar esa tecla. Para teclas especiales como [enter]{keyname}, contiene una cadena que nombra la tecla (`"Enter"`, en este caso). Si mantienes presionado [shift]{keyname} mientras presionas una tecla, eso también puede influir en el nombre de la tecla: `"v"` se convierte en `"V"`, y `"1"` puede convertirse en `"!"`, si eso es lo que se produce al presionar [shift]{keyname}-1 en tu teclado. {{index "tecla modificadora", "tecla shift", "tecla control", "tecla alt", "tecla meta", "tecla command", "propiedad ctrlKey", "propiedad shiftKey", "propiedad altKey", "propiedad metaKey"}} -Las teclas modificadoras como [shift]{keyname}, [control]{keyname}, [alt]{keyname} y [meta]{keyname} (command en Mac) generan eventos de tecla igual que las teclas normales. Pero al buscar combinaciones de teclas, también puedes averiguar si estas teclas se mantienen presionadas mirando las propiedades `shiftKey`, `ctrlKey`, `altKey` y `metaKey` de los eventos de teclado y ratón. +Las teclas modificadoras como [shift]{keyname}, [control]{keyname}, [alt]{keyname} y [meta]{keyname} ([command]{keyname} en Mac) generan eventos de tecla igual que las teclas normales. Pero al buscar combinaciones de teclas, también puedes averiguar si estas teclas se mantienen presionadas mirando las propiedades `shiftKey`, `ctrlKey`, `altKey` y `metaKey` de los eventos de teclado y ratón. ```{lang: html, focus: true} <p>Pulsa Control-Espacio para continuar.</p> @@ -248,15 +248,15 @@ Las teclas modificadoras como [shift]{keyname}, [control]{keyname}, [alt]{keynam {{index "button (etiqueta HTML)", "atributo tabindex", [DOM, eventos]}} -El nodo del DOM donde se origina un evento de teclado depende del elemento que tiene ((foco)) cuando se presiona la tecla. La mayoría de los nodos no pueden tener foco a menos que les des un atributo `tabindex`, pero cosas como los ((enlace))s, botones y campos de formulario pueden. Volveremos a los campos de formulario en el [Capítulo ?](http#forms). Cuando nada en particular tiene foco, `document.body` actúa como el nodo objetivo de los eventos de teclado. +El nodo del DOM donde se origina un evento de teclado depende del elemento que tiene ((foco)) cuando se presiona la tecla. La mayoría de los nodos no pueden tener foco a menos que les des un atributo `tabindex`, pero cosas como los ((enlace))s, botones y campos de formulario sí pueden. Volveremos a los campos de formulario en el [Capítulo ?](http#forms). Cuando no hay nada en particular con foco, `document.body` actúa como el nodo objetivo de los eventos de teclado. -Cuando el usuario está escribiendo texto, utilizar eventos de teclado para averiguar qué se está escribiendo es problemático. Algunas plataformas, especialmente el ((teclado virtual)) en teléfonos ((Android)), no disparan eventos de teclado. Pero incluso cuando se tiene un teclado tradicional, algunos tipos de entrada de texto no coinciden con las pulsaciones de teclas de manera directa, como el software de _editor de método de entrada_ (((IME))) utilizado por personas cuyos guiones no caben en un teclado, donde múltiples pulsaciones de teclas se combinan para crear caracteres. +Cuando el usuario está escribiendo texto, utilizar eventos de teclado para averiguar qué se está escribiendo es problemático. Algunas plataformas, especialmente el ((teclado virtual)) en teléfonos ((Android)), no disparan eventos de teclado. Pero incluso cuando se tiene un teclado tradicional, algunos tipos de entrada de texto no coinciden con las pulsaciones de teclas de manera directa, como el software de _editor de método de entrada_ (((IME))) utilizado por personas cuyos sistemas de escritura no caben en un teclado, donde múltiples pulsaciones de teclas se combinan para crear caracteres. -Para detectar cuando se ha escrito algo, los elementos en los que se puede escribir, como las etiquetas `<input>` y `<textarea>`, activan eventos `"input"` cada vez que el usuario cambia su contenido. Para obtener el contenido real que se ha escrito, lo mejor es leerlo directamente del campo enfocado. [Capítulo ?](http#forms) mostrará cómo hacerlo. +Para detectar cuando se ha escrito algo, los elementos en los que se puede escribir, como las etiquetas `<input>` y `<textarea>`, activan eventos `"input"` cada vez que el usuario cambia su contenido. Para obtener el contenido real que se ha escrito, lo mejor es leerlo directamente del campo enfocado. El [Capítulo ?](http#forms) mostrará cómo hacerlo. ## Eventos de puntero -Actualmente existen dos formas ampliamente utilizadas de señalar cosas en una pantalla: los ratones (incluyendo dispositivos que actúan como ratones, como touchpads y trackballs) y las pantallas táctiles. Estas producen diferentes tipos de eventos. +Actualmente existen dos formas ampliamente utilizadas de señalar cosas en una pantalla: los ratones (incluyendo dispositivos que actúan como ratones, como touchpads y trackballs) y las pantallas táctiles. Ambas producen diferentes tipos de eventos. ### Clics de ratón @@ -274,11 +274,11 @@ Si dos clics ocurren cerca uno del otro, también se dispara un evento `"dblclic {{index "píxel", "propiedad clientX", "propiedad clientY", "propiedad pageX", "propiedad pageY", "objeto evento"}} -Para obtener información precisa sobre el lugar donde ocurrió un evento de ratón, puedes mirar sus propiedades `clientX` y `clientY`, que contienen las ((coordenadas)) del evento (en píxeles) relativas a la esquina superior izquierda de la ventana, o `pageX` y `pageY`, que son relativas a la esquina superior izquierda de todo el documento (lo cual puede ser diferente cuando la ventana ha sido desplazada). +Para obtener información precisa sobre el lugar donde ocurrió un evento de ratón, puedes mirar sus propiedades `clientX` y `clientY`, que contienen las ((coordenadas)) del evento (en píxeles) relativas a la esquina superior izquierda de la ventana, o `pageX` y `pageY`, que son relativas a la esquina superior izquierda de todo el documento (estas pueden ser diferentes cuando la ventana ha sido desplazada). {{index "border-radius (CSS)", "posicionamiento absoluto", "ejemplo de programa de dibujo"}} -{{id "dibujo con ratón"}} +{{id "dibujo_con_ratón"}} El siguiente programa implementa una aplicación de dibujo primitiva. Cada vez que haces clic en el documento, agrega un punto bajo el puntero de tu ratón. Ver [Capítulo ?](paint) para una aplicación de dibujo menos primitiva. @@ -288,7 +288,7 @@ El siguiente programa implementa una aplicación de dibujo primitiva. Cada vez q height: 200px; background: beige; } - .dot { + .punto { height: 8px; width: 8px; border-radius: 4px; /* redondea las esquinas */ background: teal; @@ -297,11 +297,11 @@ El siguiente programa implementa una aplicación de dibujo primitiva. Cada vez q </style> <script> window.addEventListener("click", event => { - let dot = document.createElement("div"); - dot.className = "dot"; - dot.style.left = (event.pageX - 4) + "px"; - dot.style.top = (event.pageY - 4) + "px"; - document.body.appendChild(dot); + let punto = document.createElement("div"); + punto.className = "punto"; + punto.style.left = (event.pageX - 4) + "px"; + punto.style.top = (event.pageY - 4) + "px"; + document.body.appendChild(punto); }); </script> ``` @@ -314,31 +314,31 @@ Cada vez que el puntero del ratón se mueve, se dispara un evento `"mousemove"`. {{index "draggable bar example"}} -Como ejemplo, el siguiente programa muestra una barra y configura controladores de eventos para que al arrastrar hacia la izquierda o hacia la derecha en esta barra, se haga más estrecha o más ancha: +Como ejemplo, el siguiente programa muestra una barra y configura manejadores de eventos para que al arrastrar hacia la izquierda o hacia la derecha en esta barra, se haga más estrecha o más ancha: ```{lang: html, startCode: true} <p>Arrastra la barra para cambiar su anchura:</p> <div style="background: orange; width: 60px; height: 20px"> </div> <script> - let lastX; // Rastrea la última posición X del ratón observada - let bar = document.querySelector("div"); - bar.addEventListener("mousedown", event => { + let últimaX; // Rastrea la última posición X del ratón observada + let barra = document.querySelector("div"); + barra.addEventListener("mousedown", event => { if (event.button == 0) { - lastX = event.clientX; - window.addEventListener("mousemove", moved); - event.preventDefault(); // Prevenir selección + últimaX = event.clientX; + window.addEventListener("mousemove", movido); + event.preventDefault(); // Evitar selección } }); - function moved(event) { - if (event.buttons == 0) { - window.removeEventListener("mousemove", moved); + function movido(evento) { + if (evento.buttons == 0) { + window.removeEventListener("mousemove", movido); } else { - let dist = event.clientX - lastX; - let newWidth = Math.max(10, bar.offsetWidth + dist); - bar.style.width = newWidth + "px"; - lastX = event.clientX; + let dist = event.clientX - últimaX; + let nuevoAncho = Math.max(10, barra.offsetWidth + dist); + barra.style.width = nuevoAncho + "px"; + últimaX = event.clientX; } } </script> @@ -354,11 +354,11 @@ if}} {{index "mouseup event", "mousemove event"}} -Ten en cuenta que el controlador `"mousemove"` está registrado en toda la ((window)). Incluso si el ratón sale de la barra durante el cambio de tamaño, mientras el botón se mantenga presionado todavía queremos actualizar su tamaño. +Ten en cuenta que el controlador `"mousemove"` está registrado en toda la ((ventana)). Incluso si el ratón sale de la barra durante el cambio de tamaño, mientras el botón se mantenga presionado todavía queremos actualizar su tamaño. {{index "buttons property", "button property", "bitfield"}} -Debemos detener el cambio de tamaño de la barra cuando se libere el botón del ratón. Para eso, podemos usar la propiedad `buttons` (notar el plural), que nos indica qué botones están actualmente presionados. Cuando este valor es cero, ningún botón está presionado. Cuando se mantienen presionados botones, su valor es la suma de los códigos de esos botones—el botón izquierdo tiene el código 1, el derecho 2 y el central 4. Con el botón izquierdo y el derecho presionados, por ejemplo, el valor de `buttons` será 3. +Debemos detener el cambio de tamaño de la barra cuando se libere el botón del ratón. Para eso, podemos usar la propiedad `buttons` (atención al plural), que nos indica qué botones están actualmente presionados. Cuando este valor es cero, ningún botón está presionado. Cuando se mantienen presionados botones, su valor es la suma de los códigos de esos botones—el botón izquierdo tiene el código 1, el derecho 2 y el central 4. Con el botón izquierdo y el derecho presionados, por ejemplo, el valor de `buttons` será 3. Es importante destacar que el orden de estos códigos es diferente al utilizado por `button`, donde el botón central venía antes que el derecho. Como se mencionó, la consistencia no es realmente un punto fuerte de la interfaz de programación del navegador. @@ -366,7 +366,7 @@ Es importante destacar que el orden de estos códigos es diferente al utilizado {{index touch, "evento mousedown", "evento mouseup", "evento click"}} -El estilo de navegador gráfico que usamos fue diseñado pensando en interfaces de ratón, en una época donde las pantallas táctiles eran raras. Para hacer que la web "funcione" en los primeros teléfonos con pantalla táctil, los navegadores de esos dispositivos fingían, hasta cierto punto, que los eventos táctiles eran eventos de ratón. Si tocas la pantalla, recibirás eventos de `"mousedown"`, `"mouseup"` y `"click"`. +El estilo de navegador gráfico que usamos fue diseñado pensando en interfaces de ratón, en una época donde las pantallas táctiles no eran muy comunes. Para hacer que la web "funcione" en los primeros teléfonos con pantalla táctil, los navegadores de esos dispositivos fingían, hasta cierto punto, que los eventos táctiles eran eventos de ratón. Si tocas la pantalla, recibirás eventos de `"mousedown"`, `"mouseup"` y `"click"`. Pero esta ilusión no es muy robusta. Una pantalla táctil funciona de manera diferente a un ratón: no tiene múltiples botones, no se puede rastrear el dedo cuando no está en la pantalla (para simular `"mousemove"`), y permite que varios dedos estén en la pantalla al mismo tiempo. @@ -374,37 +374,37 @@ Los eventos de ratón solo cubren la interacción táctil en casos sencillos: si {{index "evento touchstart", "evento touchmove", "evento touchend"}} -Existen tipos específicos de eventos disparados por la interacción táctil. Cuando un dedo comienza a tocar la pantalla, se genera un evento `"touchstart"`. Cuando se mueve mientras toca, se generan eventos `"touchmove"`. Finalmente, cuando deja de tocar la pantalla, verás un evento `"touchend"`. +Existen tipos específicos de eventos que se disparan por la interacción táctil. Cuando un dedo comienza a tocar la pantalla, se genera un evento `"touchstart"`. Cuando se mueve mientras toca, se generan eventos `"touchmove"`. Finalmente, cuando deja de tocar la pantalla, verás un evento `"touchend"`. {{index "propiedad touches", "propiedad clientX", "propiedad clientY", "propiedad pageX", "propiedad pageY"}} -Debido a que muchas pantallas táctiles pueden detectar varios dedos al mismo tiempo, estos eventos no tienen un único conjunto de coordenadas asociadas. Más bien, sus ((objetos de eventos)) tienen una propiedad `touches`, que contiene un ((objeto similar a un array)) de puntos, cada uno con sus propias propiedades `clientX`, `clientY`, `pageX` y `pageY`. +Debido a que muchas pantallas táctiles pueden detectar varios dedos al mismo tiempo, estos eventos no tienen un único conjunto de coordenadas asociadas. Más bien, sus ((objetos de eventos)) tienen una propiedad `touches`, que contiene un ((objeto parecido a un array)) de puntos, cada uno con sus propias propiedades `clientX`, `clientY`, `pageX` y `pageY`. Podrías hacer algo como esto para mostrar círculos rojos alrededor de cada dedo que toca: ```{lang: html} <style> - dot { position: absolute; display: block; + punto { position: absolute; display: block; border: 2px solid red; border-radius: 50px; height: 100px; width: 100px; } </style> <p>Toca esta página</p> <script> - function update(event) { - for (let dot; dot = document.querySelector("dot");) { - dot.remove(); + function actualizar(evento) { + for (let punto; punto = document.querySelector("punto");) { + punto.remove(); } - for (let i = 0; i < event.touches.length; i++) { - let {pageX, pageY} = event.touches[i]; - let dot = document.createElement("dot"); - dot.style.left = (pageX - 50) + "px"; - dot.style.top = (pageY - 50) + "px"; - document.body.appendChild(dot); + for (let i = 0; i < evento.touches.length; i++) { + let {pageX, pageY} = evento.touches[i]; + let punto = document.createElement("punto"); + punto.style.left = (pageX - 50) + "px"; + punto.style.top = (pageY - 50) + "px"; + document.body.appendChild(punto); } } - window.addEventListener("touchstart", update); - window.addEventListener("touchmove", update); - window.addEventListener("touchend", update); + window.addEventListener("touchstart", actualizar); + window.addEventListener("touchmove", actualizar); + window.addEventListener("touchend", actualizar); </script> ``` @@ -416,27 +416,27 @@ A menudo querrás llamar a `preventDefault` en los controladores de eventos tác {{index scrolling, "evento scroll", "manejo de eventos"}} -Cada vez que un elemento se desplaza, se dispara un evento `"scroll"`. Esto tiene varios usos, como saber qué está viendo actualmente el usuario (para desactivar animaciones fuera de la pantalla o enviar informes de vigilancia a tu malvada sede) o mostrar alguna indicación de progreso (resaltando parte de una tabla de contenidos o mostrando un número de página).El siguiente ejemplo dibuja una barra de progreso sobre el documento y la actualiza para llenarla a medida que se desplaza hacia abajo: +Cada vez que un elemento se desplaza, se dispara un evento `"scroll"`. Esto tiene varios usos, como saber qué está viendo actualmente el usuario (para desactivar animaciones fuera de la pantalla o enviar informes de vigilancia a tu malvado cuartel general) o mostrar alguna indicación de progreso (resaltando parte de una tabla de contenidos o mostrando un número de página).El siguiente ejemplo dibuja una barra de progreso sobre el documento y la actualiza para llenarla a medida que se desplaza hacia abajo: ```{lang: html} <style> - #progress { + #progreso { border-bottom: 2px solid blue; width: 0; position: fixed; top: 0; left: 0; } </style> -<div id="progress"></div> +<div id="progreso"></div> <script> // Create some content document.body.appendChild(document.createTextNode( - "supercalifragilisticexpialidocious ".repeat(1000))); + "supercalifragilisticoespialidoso ".repeat(1000))); - let bar = document.querySelector("#progress"); + let barra = document.querySelector("#progreso"); window.addEventListener("scroll", () => { let max = document.body.scrollHeight - innerHeight; - bar.style.width = `${(pageYOffset / max) * 100}%`; + barra.style.width = `${(pageYOffset / max) * 100}%`; }); </script> ``` @@ -447,11 +447,11 @@ Darle a un elemento una `position` de `fixed` actúa de manera similar a una pos {{index "innerHeight property", "innerWidth property", "pageYOffset property"}} -El enlace global `innerHeight` nos da la altura de la ventana, que debemos restar de la altura total desplazable, ya que no se puede seguir desplazando cuando se llega al final del documento. También existe un `innerWidth` para el ancho de la ventana. Al dividir `pageYOffset`, la posición actual de desplazamiento, por la posición máxima de desplazamiento y multiplicar por 100, obtenemos el porcentaje para la barra de progreso. +La variable global `innerHeight` nos da la altura de la ventana, que debemos restar de la altura total desplazable, ya que no se puede seguir desplazando cuando se llega al final del documento. También existe un `innerWidth` para el ancho de la ventana. Al dividir `pageYOffset`, la posición actual de desplazamiento, por la posición máxima de desplazamiento y multiplicar por 100, obtenemos el porcentaje para la barra de progreso. {{index "preventDefault method"}} -Llamar a `preventDefault` en un evento de desplazamiento no impide que ocurra el desplazamiento. De hecho, el controlador de eventos se llama solo _después_ de que ocurre el desplazamiento. +Llamar a `preventDefault` en un evento de desplazamiento no impide que ocurra el desplazamiento. De hecho, el controlador de eventos se llama justo _después_ de que ocurra el desplazamiento. ## Eventos de enfoque @@ -461,27 +461,27 @@ Cuando un elemento recibe el ((enfoque)), el navegador dispara un evento `"focus {{index "event propagation"}} -A diferencia de los eventos discutidos anteriormente, estos dos eventos no se propagan. Un controlador en un elemento padre no recibe notificaciones cuando un elemento hijo recibe o pierde el enfoque. +A diferencia de los eventos discutidos anteriormente, estos dos eventos no se propagan. Un manejador en un elemento padre no recibe notificaciones cuando un elemento hijo recibe o pierde el enfoque. {{index "input (HTML tag)", "help text example"}} El siguiente ejemplo muestra texto de ayuda para el ((campo de texto)) que actualmente tiene el foco: ```{lang: html} -<p>Nombre: <input type="text" data-help="Tu nombre completo"></p> -<p>Edad: <input type="text" data-help="Tu edad en años"></p> -<p id="help"></p> +<p>Nombre: <input type="text" data-ayuda="Tu nombre completo"></p> +<p>Edad: <input type="text" data-ayuda="Tu edad en años"></p> +<p id="ayuda"></p> <script> - let help = document.querySelector("#help"); - let fields = document.querySelectorAll("input"); - for (let field of Array.from(fields)) { - field.addEventListener("focus", event => { - let text = event.target.getAttribute("data-help"); - help.textContent = text; + let ayuda = document.querySelector("#ayuda"); + let campos = document.querySelectorAll("input"); + for (let campo of Array.from(campos)) { + campo.addEventListener("focus", event => { + let texto = event.target.getAttribute("data-ayuda"); + ayuda.textContent = texto; }); - field.addEventListener("blur", event => { - help.textContent = ""; + campo.addEventListener("blur", evento => { + ayuda.textContent = ""; }); } </script> @@ -511,7 +511,7 @@ Elementos como ((imágenes)) y etiquetas de script que cargan un archivo externo {{index "evento beforeunload", "recarga de página", "método preventDefault"}} -Cuando se cierra una página o se navega lejos de ella (por ejemplo, al seguir un enlace), se dispara un evento `"beforeunload"`. El uso principal de este evento es evitar que el usuario pierda accidentalmente su trabajo al cerrar un documento. Si previenes el comportamiento predeterminado en este evento _y_ estableces la propiedad `returnValue` en el objeto de evento a una cadena, el navegador mostrará al usuario un cuadro de diálogo preguntando si realmente desea abandonar la página. Ese cuadro de diálogo podría incluir tu cadena, pero debido a que algunos sitios maliciosos intentan usar estos cuadros de diálogo para confundir a las personas y hacer que se queden en su página para ver anuncios de pérdida de peso dudosos, la mayoría de los navegadores ya no los muestran. +Cuando se cierra una página o se navega lejos de ella (por ejemplo, al seguir un enlace), se dispara un evento `"beforeunload"`. El uso principal de este evento es evitar que el usuario pierda accidentalmente su trabajo al cerrar un documento. Si evitas el comportamiento predeterminado en este evento _y_ estableces la propiedad `returnValue` en el objeto de evento a una cadena, el navegador mostrará al usuario un cuadro de diálogo preguntando si realmente desea abandonar la página. Ese cuadro de diálogo podría incluir tu cadena, pero debido a que algunos sitios maliciosos intentan usar estos cuadros de diálogo para confundir a las personas y hacer que se queden en su página para ver dudosos anuncios de pérdida de peso, la mayoría de los navegadores ya no los muestran. {{id timeline}} @@ -519,7 +519,7 @@ Cuando se cierra una página o se navega lejos de ella (por ejemplo, al seguir u {{index "función requestAnimationFrame", "manejo de eventos", timeline, "script (etiqueta HTML)"}} -En el contexto del bucle de eventos, como se discutió en el [Capítulo ?](async), los controladores de eventos del navegador se comportan como otras notificaciones asíncronas. Se programan cuando ocurre el evento pero deben esperar a que otros scripts que se estén ejecutando terminen antes de tener la oportunidad de ejecutarse. +En el contexto del bucle de eventos, como se discutió en el [Capítulo ?](async), los manejadores de eventos del navegador se comportan como cualquier otra notificación asíncrona. Se programan cuando ocurre el evento pero antes de tener la oportunidad de ejecutarse deben esperar a que otros scripts que se estén ejecutando terminen. El hecho de que los eventos solo se puedan procesar cuando no hay nada más en ejecución significa que, si el bucle de eventos está ocupado con otro trabajo, cualquier interacción con la página (que ocurre a través de eventos) se retrasará hasta que haya tiempo para procesarla. Entonces, si programas demasiado trabajo, ya sea con controladores de eventos de larga duración o con muchos que se ejecutan rápidamente, la página se volverá lenta y pesada de usar. @@ -528,19 +528,19 @@ Para casos en los que _realmente_ quieres hacer algo que consume mucho tiempo en Imagina que elevar al cuadrado un número es una computación pesada y de larga duración que queremos realizar en un ((hilo)) separado. Podríamos escribir un archivo llamado `code/squareworker.js` que responda a mensajes calculando un cuadrado y enviando un mensaje de vuelta. ``` -addEventListener("message", event => { - postMessage(event.data * event.data); +addEventListener("message", evento => { + postMessage(evento.data * evento.data); }); ``` -Para evitar los problemas de tener múltiples hilos tocando los mismos datos, los workers no comparten su alcance global ni ningún otro dato con el entorno del script principal. En cambio, debes comunicarte con ellos enviando mensajes de ida y vuelta. +Para evitar los problemas de tener múltiples hilos tocando los mismos datos, los workers no comparten su alcance global ni ningún otro dato con el entorno del script principal. En vez de eso, debes comunicarte con ellos enviando mensajes de ida y vuelta. Este código genera un worker que ejecuta ese script, le envía algunos mensajes y muestra las respuestas. ```{test: no} let squareWorker = new Worker("code/squareworker.js"); -squareWorker.addEventListener("message", event => { - console.log("El worker respondió:", event.data); +squareWorker.addEventListener("message", evento => { + console.log("El worker respondió:", evento.data); }); squareWorker.postMessage(10); squareWorker.postMessage(24); @@ -554,20 +554,20 @@ La función `postMessage` envía un mensaje, lo que causará que se dispare un e {{index timeout, "función setTimeout"}} -Vimos la función `setTimeout` en el [Capítulo ?](async). Programa otra función para que se llame más tarde, después de un cierto número de milisegundos. +La función `setTimeout` que vimos en el [Capítulo ?](async) programa otra función para que se llame más tarde, después de un cierto número de milisegundos. {{index "función clearTimeout"}} A veces necesitas cancelar una función que has programado. Esto se hace almacenando el valor devuelto por `setTimeout` y llamando a `clearTimeout` sobre él. ``` -let bombTimer = setTimeout(() => { +let temporizadorBomba = setTimeout(() => { console.log("¡BOOM!"); }, 500); if (Math.random() < 0.5) { // 50% de probabilidad console.log("Desactivado."); - clearTimeout(bombTimer); + clearTimeout(temporizadorBomba); } ``` @@ -594,11 +594,11 @@ let reloj = setInterval(() => { {{index "optimización", "evento mousemove", "evento scroll", bloqueo}} -Algunos tipos de eventos pueden activarse rápidamente, muchas veces seguidas (como los eventos `"mousemove"` y `"scroll"`, por ejemplo). Al manejar tales eventos, debes tener cuidado de no hacer nada que consuma demasiado tiempo, ya que tu controlador tomará tanto tiempo que la interacción con el documento comenzará a sentirse lenta. +Algunos tipos de eventos pueden activarse rápidamente, muchas veces seguidas (como los eventos `"mousemove"` y `"scroll"`, por ejemplo). Al manejar tales eventos, debes tener cuidado de no hacer nada que consuma demasiado tiempo, ya que tu manejador tomará tanto tiempo que la interacción con el documento comenzará a percibirse como lenta. {{index "función setTimeout"}} -Si necesitas hacer algo importante en un controlador de este tipo, puedes usar `setTimeout` para asegurarte de que no lo estás haciendo con demasiada frecuencia. Esto suele llamarse _((debouncing))_ el evento. Hay varios enfoques ligeramente diferentes para esto. +Si necesitas hacer algo importante en un manejador de este tipo, puedes usar `setTimeout` para asegurarte de que no lo estás haciendo con demasiada frecuencia. Esto suele llamarse limitación (o _((debouncing))_, en inglés) del evento. Hay varios enfoques ligeramente diferentes para esto. {{index "textarea (etiqueta HTML)", "función clearTimeout", "evento keydown"}} @@ -608,10 +608,10 @@ En el primer ejemplo, queremos reaccionar cuando el usuario ha escrito algo, per <textarea>Escribe algo aquí...</textarea> <script> let textarea = document.querySelector("textarea"); - let timeout; + let espera; textarea.addEventListener("input", () => { - clearTimeout(timeout); - timeout = setTimeout(() => console.log("¡Escrito!"), 500); + clearTimeout(espera); + espera = setTimeout(() => console.log("¡Escrito!"), 500); }); </script> ``` @@ -627,7 +627,7 @@ Podemos usar un patrón ligeramente diferente si queremos espaciar las respuesta ```{lang: html} <script> let programado = null; - window.addEventListener("mousemove", event => { + window.addEventListener("mousemove", evento => { if (!programado) { setTimeout(() => { document.body.textContent = @@ -635,18 +635,18 @@ Podemos usar un patrón ligeramente diferente si queremos espaciar las respuesta programado = null; }, 250); } - programado = event; + programado = evento; }); </script> ``` ## Resumen -Los controladores de eventos hacen posible detectar y reaccionar a eventos que ocurren en nuestra página web. El método `addEventListener` se utiliza para registrar dicho controlador. +Los manejadores de eventos hacen posible detectar y reaccionar a eventos que ocurren en nuestra página web. El método `addEventListener` se utiliza para registrar dicho manejador. -Cada evento tiene un tipo (`"keydown"`, `"focus"`, y así sucesivamente) que lo identifica. La mayoría de los eventos se activan en un elemento DOM específico y luego se _propagan_ a los ancestros de ese elemento, lo que permite que los controladores asociados a esos elementos los manejen. +Cada evento tiene un tipo (`"keydown"`, `"focus"`, etc) que lo identifica. La mayoría de los eventos se activan en un elemento DOM específico y luego se _propagan_ a los ancestros de ese elemento, lo que permite que los manejadores asociados a esos elementos los manejen. -Cuando se llama a un controlador de eventos, se le pasa un objeto de evento con información adicional sobre el evento. Este objeto también tiene métodos que nos permiten detener una mayor propagación (`stopPropagation`) y evitar el manejo predeterminado del evento por parte del navegador (`preventDefault`). +Cuando se llama a un manejador de eventos, se le pasa un objeto de evento con información adicional sobre el evento. Este objeto también tiene métodos que nos permiten detener una mayor propagación (`stopPropagation`) y evitar el manejo predeterminado del evento por parte del navegador (`preventDefault`). Presionar una tecla dispara eventos `"keydown"` y `"keyup"`. Presionar un botón del mouse dispara eventos `"mousedown"`, `"mouseup"` y `"click"`. Mover el mouse dispara eventos `"mousemove"`. La interacción con pantallas táctiles dará lugar a eventos `"touchstart"`, `"touchmove"` y `"touchend"`. @@ -662,7 +662,7 @@ Escribe una página que muestre un ((globo)) (usando el ((emoji)) de globo, 🎈 {{index "font-size (CSS)"}} -Puedes controlar el tamaño del texto (los emoji son texto) estableciendo la propiedad CSS `font-size` (`style.fontSize`) en su elemento padre. Recuerda incluir una unidad en el valor, por ejemplo, píxeles (`10px`). +Puedes controlar el tamaño del texto (los emoji son texto) estableciendo la propiedad CSS `font-size` (`style.fontSize`) en su elemento padre. Recuerda incluir las unidades en el valor, por ejemplo, píxeles (`10px`). Los nombres de las teclas de flecha son `"ArrowUp"` y `"ArrowDown"`. Asegúrate de que las teclas cambien solo el globo, sin hacer scroll en la página. @@ -684,9 +684,9 @@ if}} {{index "evento keydown", "propiedad key", "globo (ejercicio)"}} -Querrás registrar un manejador para el evento `"keydown"` y mirar `event.key` para saber si se presionó la tecla de flecha hacia arriba o hacia abajo. +Tendrás que registrar un manejador para el evento `"keydown"` y mirar `event.key` para saber si se presionó la tecla de flecha hacia arriba o hacia abajo. -El tamaño actual se puede mantener en un enlace para que puedas basarte en él para el nuevo tamaño. Será útil definir una función que actualice el tamaño, tanto el enlace como el estilo del globo en el DOM, para que puedas llamarla desde tu manejador de eventos, y posiblemente también una vez al inicio, para establecer el tamaño inicial. +El tamaño actual se puede mantener en una variable para que puedas basarte en ella para el nuevo tamaño. Será útil definir una función que actualice el tamaño —tanto la variable como el estilo del globo en el DOM— para que puedas llamarla desde tu manejador de eventos, y posiblemente también una vez al inicio, para establecer el tamaño inicial. {{index "método replaceChild", "propiedad textContent"}} @@ -704,7 +704,7 @@ Una de estas era la _estela del ratón_ —una serie de elementos que seguirían {{index "posicionamiento absoluto", "background (CSS)"}} -En este ejercicio, quiero que implementes una estela del ratón. Utiliza elementos `<div>` con posición absoluta y un tamaño fijo y color de fondo (consulta el [código](event#mouse_drawing) en la sección de "Clics de ratón" para un ejemplo). Crea un montón de estos elementos y, al mover el ratón, muéstralos en la estela del puntero del ratón. +En este ejercicio, quiero que implementes una estela del ratón. Utiliza elementos `<div>` con posición absoluta y un tamaño fijo y color de fondo (consulta el [código](event#dibujo_con_ratón) en la sección de "Clics de ratón" para un ejemplo). Crea un montón de estos elementos y, al mover el ratón, muéstralos en la estela del puntero del ratón. {{index "mousemove event"}} @@ -737,15 +737,15 @@ if}} {{index "mouse trail (exercise)"}} -Crear los elementos es mejor hacerlo con un bucle. Adjúntalos al documento para que aparezcan. Para poder acceder a ellos más tarde y cambiar su posición, querrás almacenar los elementos en un array. +Para crear los elementos lo mejor es hacerlo con un bucle. Adjúntalos al documento para que aparezcan. Para poder acceder a ellos más tarde y cambiar su posición, tendrás que almacenar los elementos en un array. {{index "mousemove event", [array, indexing], "remainder operator", "% operator"}} -Recorrerlos se puede hacer manteniendo una variable de contador y sumándole 1 cada vez que se dispare el evento `"mousemove"`. Luego se puede usar el operador de resto (`% elementos.length`) para obtener un índice de array válido para elegir el elemento que deseas posicionar durante un evento dado. +Puedes recorrerlos manteniendo una variable de contador y sumándole 1 cada vez que se dispare el evento `"mousemove"`. Luego se puede usar el operador de resto (`% elementos.length`) para obtener un índice de array válido para elegir el elemento que deseas posicionar durante un evento dado. {{index "simulación", "requestAnimationFrame function"}} -Otro efecto interesante se puede lograr modelando un simple sistema de ((física)). Usa el evento `"mousemove"` solo para actualizar un par de enlaces que siguen la posición del ratón. Luego utiliza `requestAnimationFrame` para simular que los elementos rastreadores son atraídos a la posición del puntero del ratón. En cada paso de animación, actualiza su posición basándote en su posición relativa al puntero (y, opcionalmente, una velocidad que está almacenada para cada elemento). Descubrir una buena forma de hacer esto queda a tu cargo. +Otro efecto interesante se puede lograr modelando un simple sistema de ((física)). Usa el evento `"mousemove"` solo para actualizar un par de enlaces que siguen la posición del ratón. Luego utiliza `requestAnimationFrame` para simular que los elementos rastreadores son atraídos a la posición del puntero del ratón. En cada paso de animación, actualiza su posición basándote en su posición relativa al puntero (y, opcionalmente, una velocidad que está almacenada para cada elemento). En tu mano está el descubrir una buena forma de hacer esto. hint}} @@ -759,7 +759,7 @@ Los paneles con pestañas son ampliamente utilizados en interfaces de usuario. T En este ejercicio debes implementar una interfaz de pestañas simple. Escribe una función, `asTabs`, que tome un nodo DOM y cree una interfaz de pestañas que muestre los elementos secundarios de ese nodo. Debería insertar una lista de elementos `<button>` en la parte superior del nodo, uno por cada elemento secundario, conteniendo el texto recuperado del atributo `data-tabname` del hijo. Todos los hijos originales excepto uno deben estar ocultos (con un estilo `display` de `none`). El nodo actualmente visible se puede seleccionar haciendo clic en los botones. -Cuando funcione, extiéndelo para dar estilo al botón de la pestaña actualmente seleccionada de manera diferente para que sea obvio cuál pestaña está seleccionada. +Cuando funcione, extiéndelo para dar estilo al botón de la pestaña actualmente seleccionada de manera diferente para que sea obvio qué pestaña está seleccionada. {{if interactive diff --git a/html/15_event.html b/html/15_event.html index 3e25c9a8..cc7bb87b 100644 --- a/html/15_event.html +++ b/html/15_event.html @@ -14,74 +14,74 @@ <h1>Manejo de Eventos</h1> <blockquote> -<p><a class="p_ident" id="p-VUZeGAxBS+" href="#p-VUZeGAxBS+" tabindex="-1" role="presentation"></a>Tienes poder sobre tu mente, no sobre los eventos externos. Date cuenta de esto y encontrarás fuerza.</p> +<p><a class="p_ident" id="p-bIbmS/SOgJ" href="#p-bIbmS/SOgJ" tabindex="-1" role="presentation"></a>Tienes poder sobre tu mente, no sobre los eventos externos. Comprende esto y hallarás la fuerza.</p> <footer>Marco Aurelio, <cite>Meditaciones</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_15.jpg" alt="Ilustración que muestra una máquina de Rube Goldberg que involucra una pelota, una balanza, un par de tijeras y un martillo, los cuales se afectan en una reacción en cadena que enciende una bombilla."></figure> -<p><a class="p_ident" id="p-7buioHYYih" href="#p-7buioHYYih" tabindex="-1" role="presentation"></a>Algunos programas trabajan con la entrada directa del usuario, como acciones del ratón y del teclado. Ese tipo de entrada no está disponible de antemano, como una estructura de datos bien organizada, llega pieza por pieza, en tiempo real, y el programa debe responder a medida que sucede.</p> +<p><a class="p_ident" id="p-7buioHYYih" href="#p-7buioHYYih" tabindex="-1" role="presentation"></a>Algunos programas trabajan directamente con la interacción del usuario, como acciones del ratón o del teclado. Ese tipo de entrada no está disponible de antemano como una estructura de datos bien organizada —llega pieza poco a poco, en tiempo real, y el programa debe responder a medida que sucede.</p> <h2><a class="h_ident" id="h-FRSr58jqPG" href="#h-FRSr58jqPG" tabindex="-1" role="presentation"></a>Controladores de Eventos</h2> -<p><a class="p_ident" id="p-8Rhq4iuO+f" href="#p-8Rhq4iuO+f" tabindex="-1" role="presentation"></a>Imagina una interfaz donde la única forma de saber si una tecla en el teclado está siendo presionada es leyendo el estado actual de esa tecla. Para poder reaccionar a las pulsaciones de teclas, tendrías que leer constantemente el estado de la tecla para capturarla antes de que se libere nuevamente. Sería peligroso realizar otras computaciones intensivas en tiempo, ya que podrías perder una pulsación de tecla.</p> +<p><a class="p_ident" id="p-lkuLsIRCh+" href="#p-lkuLsIRCh+" tabindex="-1" role="presentation"></a>Imagina una interfaz donde la única forma de saber si una tecla en el teclado está siendo presionada es leyendo el estado actual de esa tecla. Para poder reaccionar a las pulsaciones de teclas, tendrías que leer constantemente el estado de la tecla para capturarla antes de que se libere nuevamente. Sería peligroso realizar otros procedimientos intensivos en cuanto a tiempo, ya que podrías perder una pulsación de tecla por el camino.</p> -<p><a class="p_ident" id="p-VP41tPOvsN" href="#p-VP41tPOvsN" tabindex="-1" role="presentation"></a>Algunas máquinas primitivas manejan la entrada de esa manera. Un paso adelante sería que el hardware o el sistema operativo noten la pulsación de tecla y la pongan en una cola. Un programa puede luego verificar periódicamente la cola en busca de nuevos eventos y reaccionar a lo que encuentre allí.</p> +<p><a class="p_ident" id="p-VP41tPOvsN" href="#p-VP41tPOvsN" tabindex="-1" role="presentation"></a>Algunas máquinas primitivas manejan este tipo de entrada de esa manera. Un paso adelante sería que el hardware o el sistema operativo noten la pulsación de tecla y la pongan en una cola. Un programa puede luego verificar periódicamente la cola en busca de nuevos eventos y reaccionar a lo que encuentre allí.</p> -<p><a class="p_ident" id="p-OA4r/1sP4t" href="#p-OA4r/1sP4t" tabindex="-1" role="presentation"></a>Por supuesto, tiene que recordar mirar la cola y hacerlo a menudo, porque cualquier tiempo transcurrido entre la presión de la tecla y la notificación del evento por parte del programa hará que el software se sienta sin respuesta. Este enfoque se llama <em>sondeo</em>. La mayoría de los programadores prefieren evitarlo.</p> +<p><a class="p_ident" id="p-OA4r/1sP4t" href="#p-OA4r/1sP4t" tabindex="-1" role="presentation"></a>Por supuesto, tiene que recordar mirar la cola y hacerlo a menudo, porque cualquier tiempo transcurrido entre la presión de la tecla y la notificación del evento por parte del programa hará que el software se sienta como sin respuesta. Este enfoque se llama <em>sondeo</em>. La mayoría de los programadores prefieren evitarlo.</p> <p><a class="p_ident" id="p-2XkwGPIiX5" href="#p-2XkwGPIiX5" tabindex="-1" role="presentation"></a>Un mecanismo mejor es que el sistema notifique activamente a nuestro código cuando ocurre un evento. Los navegadores hacen esto al permitirnos registrar funciones como <em>manejadores</em> para eventos específicos.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-oVekYvwKVN" href="#c-oVekYvwKVN" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Haz clic en este documento para activar el manejador.</<span class="tok-typeName">p</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-MmG75mNTKc" href="#c-MmG75mNTKc" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Haz clic en este documento para activar el manejador.</<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> window.addEventListener(<span class="tok-string">"click"</span>, () => { - console.log(<span class="tok-string">"¿Llamaste?"</span>); + console.log(<span class="tok-string">"¿Quién es?"</span>); }); </<span class="tok-typeName">script</span>></pre> <p><a class="p_ident" id="p-sMSFJq6kop" href="#p-sMSFJq6kop" tabindex="-1" role="presentation"></a>La asignación <code>window</code> se refiere a un objeto integrado proporcionado por el navegador. Representa la ventana del navegador que contiene el documento. Llamar a su método <code>addEventListener</code> registra el segundo argumento para que se llame cada vez que ocurra el evento descrito por su primer argumento.</p> -<h2><a class="h_ident" id="h-i1TmEKGPGX" href="#h-i1TmEKGPGX" tabindex="-1" role="presentation"></a>Eventos y nodos DOM</h2> +<h2><a class="h_ident" id="h-DVNLj0NT/B" href="#h-DVNLj0NT/B" tabindex="-1" role="presentation"></a>Eventos y nodos del DOM</h2> -<p><a class="p_ident" id="p-KozqhnfqX8" href="#p-KozqhnfqX8" tabindex="-1" role="presentation"></a>Cada controlador de eventos del navegador se registra en un contexto. En el ejemplo anterior llamamos a <code>addEventListener</code> en el objeto <code>window</code> para registrar un controlador para toda la ventana. Un método similar también se encuentra en elementos del DOM y algunos otros tipos de objetos. Los escuchas de eventos solo se llaman cuando el evento ocurre en el contexto del objeto en el que están registrados.</p> +<p><a class="p_ident" id="p-KozqhnfqX8" href="#p-KozqhnfqX8" tabindex="-1" role="presentation"></a>Cada controlador de eventos del navegador se registra en un contexto. En el ejemplo anterior llamamos a <code>addEventListener</code> en el objeto <code>window</code> para registrar un controlador para toda la ventana. También podemos encontrar un método similar en elementos del DOM y algunos otros tipos de objetos. Los escuchas de eventos solo se llaman cuando el evento ocurre en el contexto del objeto en el que están registrados.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-cgGEoZtMBf" href="#c-cgGEoZtMBf" tabindex="-1" role="presentation"></a><<span class="tok-typeName">button</span>>Haz clic</<span class="tok-typeName">button</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-Ap1y9/gxo1" href="#c-Ap1y9/gxo1" tabindex="-1" role="presentation"></a><<span class="tok-typeName">button</span>>Haz clic</<span class="tok-typeName">button</span>> <<span class="tok-typeName">p</span>>No hay manejador aquí.</<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">button</span> = document.querySelector(<span class="tok-string">"button"</span>); - button.addEventListener(<span class="tok-string">"click"</span>, () => { - console.log(<span class="tok-string">"Botón clickeado."</span>); + <span class="tok-keyword">let</span> <span class="tok-definition">botón</span> = document.querySelector(<span class="tok-string">"button"</span>); + botón.addEventListener(<span class="tok-string">"click"</span>, () => { + console.log(<span class="tok-string">"Botón cliqueado."</span>); }); </<span class="tok-typeName">script</span>></pre> -<p><a class="p_ident" id="p-qcV8LJi52o" href="#p-qcV8LJi52o" tabindex="-1" role="presentation"></a>Ese ejemplo adjunta un manejador al nodo del botón. Los clics en el botón hacen que se ejecute ese manejador, pero los clics en el resto del documento no lo hacen.</p> +<p><a class="p_ident" id="p-n9SHftwR5M" href="#p-n9SHftwR5M" tabindex="-1" role="presentation"></a>En este ejemplo se adjunta un manejador al nodo del botón. Los clics en el botón hacen que se ejecute ese manejador, pero los clics en el resto del documento no lo hacen.</p> <p><a class="p_ident" id="p-ULEZ9TcmUH" href="#p-ULEZ9TcmUH" tabindex="-1" role="presentation"></a>Darle a un nodo un atributo <code>onclick</code> tiene un efecto similar. Esto funciona para la mayoría de tipos de eventos: puedes adjuntar un manejador a través del atributo cuyo nombre es el nombre del evento con <code>on</code> al inicio.</p> <p><a class="p_ident" id="p-jwX/IrUZx/" href="#p-jwX/IrUZx/" tabindex="-1" role="presentation"></a>Pero un nodo solo puede tener un atributo <code>onclick</code>, por lo que solo puedes registrar un manejador por nodo de esa manera. El método <code>addEventListener</code> te permite agregar cualquier cantidad de manejadores, por lo que es seguro agregar manejadores incluso si ya hay otro manejador en el elemento.</p> -<p><a class="p_ident" id="p-/4LnTySpGj" href="#p-/4LnTySpGj" tabindex="-1" role="presentation"></a>El método <code>removeEventListener</code>, llamado con argumentos similares a <code>addEventListener</code>, remueve un manejador.</p> +<p><a class="p_ident" id="p-l/iOEDcKAk" href="#p-l/iOEDcKAk" tabindex="-1" role="presentation"></a>El método <code>removeEventListener</code>, llamado con argumentos similares a <code>addEventListener</code>, elimina un manejador.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-BYy+V/MPJt" href="#c-BYy+V/MPJt" tabindex="-1" role="presentation"></a><<span class="tok-typeName">button</span>>Botón de acción única</<span class="tok-typeName">button</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-FX0B07eIDd" href="#c-FX0B07eIDd" tabindex="-1" role="presentation"></a><<span class="tok-typeName">button</span>>Botón de acción única</<span class="tok-typeName">button</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">button</span> = document.querySelector(<span class="tok-string">"button"</span>); + <span class="tok-keyword">let</span> <span class="tok-definition">botón</span> = document.querySelector(<span class="tok-string">"button"</span>); <span class="tok-keyword">function</span> <span class="tok-definition">unaVez</span>() { console.log(<span class="tok-string">"¡Hecho!"</span>); - button.removeEventListener(<span class="tok-string">"click"</span>, unaVez); + botón.removeEventListener(<span class="tok-string">"click"</span>, unaVez); } - button.addEventListener(<span class="tok-string">"click"</span>, unaVez); + botón.addEventListener(<span class="tok-string">"click"</span>, unaVez); </<span class="tok-typeName">script</span>></pre> -<p><a class="p_ident" id="p-8jkibmkt7W" href="#p-8jkibmkt7W" tabindex="-1" role="presentation"></a>La función proporcionada a <code>removeEventListener</code> debe ser el mismo valor de función que se proporcionó a <code>addEventListener</code>. Por lo tanto, para anular el registro de un manejador, querrás darle un nombre a la función (<code>unaVez</code>, en el ejemplo) para poder pasar el mismo valor de función a ambos métodos.</p> +<p><a class="p_ident" id="p-8jkibmkt7W" href="#p-8jkibmkt7W" tabindex="-1" role="presentation"></a>La función proporcionada a <code>removeEventListener</code> debe ser el mismo valor de función que se proporcionó a <code>addEventListener</code>. Por lo tanto, para anular el registro de un manejador, tendrás que darle un nombre a la función (<code>unaVez</code>, en el ejemplo) para poder pasar el mismo valor de función a ambos métodos.</p> <h2><a class="h_ident" id="h-KlpBrbTtf/" href="#h-KlpBrbTtf/" tabindex="-1" role="presentation"></a>Objetos de eventos</h2> -<p><a class="p_ident" id="p-EfRG8R9jxW" href="#p-EfRG8R9jxW" tabindex="-1" role="presentation"></a>Aunque lo hemos ignorado hasta ahora, las funciones de manejadores de eventos reciben un argumento: el <em>objeto de evento</em>. Este objeto contiene información adicional sobre el evento. Por ejemplo, si queremos saber <em>cuál</em> botón del mouse se presionó, podemos mirar la propiedad <code>button</code> del objeto de evento.</p> +<p><a class="p_ident" id="p-EfRG8R9jxW" href="#p-EfRG8R9jxW" tabindex="-1" role="presentation"></a>Aunque lo hemos ignorado hasta ahora, las funciones de manejadores de eventos reciben un argumento: el <em>objeto de evento</em>. Este objeto contiene información adicional sobre el evento. Por ejemplo, si queremos saber <em>qué</em> botón del ratón se presionó, podemos mirar la propiedad <code>button</code> del objeto de evento.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-1XU8X2K94Z" href="#c-1XU8X2K94Z" tabindex="-1" role="presentation"></a><<span class="tok-typeName">button</span>>Haz clic como quieras</<span class="tok-typeName">button</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-L8AjbiBWiF" href="#c-L8AjbiBWiF" tabindex="-1" role="presentation"></a><<span class="tok-typeName">button</span>>Haz clic como quieras</<span class="tok-typeName">button</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">button</span> = document.querySelector(<span class="tok-string">"button"</span>); - button.addEventListener(<span class="tok-string">"mousedown"</span>, <span class="tok-definition">event</span> => { + <span class="tok-keyword">let</span> <span class="tok-definition">botón</span> = document.querySelector(<span class="tok-string">"button"</span>); + botón.addEventListener(<span class="tok-string">"mousedown"</span>, <span class="tok-definition">event</span> => { <span class="tok-keyword">if</span> (event.button == <span class="tok-number">0</span>) { console.log(<span class="tok-string">"Botón izquierdo"</span>); } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (event.button == <span class="tok-number">1</span>) { @@ -98,20 +98,20 @@ <h2><a class="h_ident" id="h-4NLRTYLmHH" href="#h-4NLRTYLmHH" tabindex="-1" role <p><a class="p_ident" id="p-eiMQVYI0Vr" href="#p-eiMQVYI0Vr" tabindex="-1" role="presentation"></a>Para la mayoría de tipos de evento, los manejadores registrados en nodos con hijos también recibirán eventos que ocurran en los hijos. Si se hace clic en un botón dentro de un párrafo, los manejadores de eventos en el párrafo también verán el evento de clic.</p> -<p><a class="p_ident" id="p-LrSHxLbszG" href="#p-LrSHxLbszG" tabindex="-1" role="presentation"></a>Pero si tanto el párrafo como el botón tienen un controlador, el controlador más específico —el del botón— tiene prioridad para ejecutarse primero. Se dice que el evento <em>se propaga</em> hacia afuera, desde el nodo donde ocurrió hacia el nodo padre de ese nodo y hasta la raíz del documento. Finalmente, después de que todos los controladores registrados en un nodo específico hayan tenido su turno, los controladores registrados en toda la ventana tienen la oportunidad de responder al evento.</p> +<p><a class="p_ident" id="p-LrSHxLbszG" href="#p-LrSHxLbszG" tabindex="-1" role="presentation"></a>Pero si tanto el párrafo como el botón tienen un controlador, el controlador más específico —el del botón— tiene prioridad para ejecutarse primero. Se dice que el evento <em>se propaga</em> hacia afuera, desde el nodo donde ocurrió hacia el nodo padre de ese nodo y hasta la raíz del documento. Finalmente, después de que todos los manejadores registrados en un nodo específico hayan tenido su turno, los manejadores registrados en toda la ventana tienen la oportunidad de responder al evento.</p> <p><a class="p_ident" id="p-bLFObMVe5V" href="#p-bLFObMVe5V" tabindex="-1" role="presentation"></a>En cualquier momento, un controlador de eventos puede llamar al método <code>stopPropagation</code> en el objeto de evento para evitar que los controladores superiores reciban el evento. Esto puede ser útil cuando, por ejemplo, tienes un botón dentro de otro elemento clickeable y no quieres que los clics en el botón activen el comportamiento de click del elemento externo.</p> -<p><a class="p_ident" id="p-VouwaRKuxI" href="#p-VouwaRKuxI" tabindex="-1" role="presentation"></a>El siguiente ejemplo registra controladores de <code>"mousedown"</code> tanto en un botón como en el párrafo que lo rodea. Cuando se hace clic con el botón derecho del ratón, el controlador del botón llama a <code>stopPropagation</code>, lo que evitará que se ejecute el controlador en el párrafo. Cuando el botón se hace clic con otro botón del ratón, ambos controladores se ejecutarán.</p> +<p><a class="p_ident" id="p-VouwaRKuxI" href="#p-VouwaRKuxI" tabindex="-1" role="presentation"></a>El siguiente ejemplo registra manejadores de <code>"mousedown"</code> tanto en un botón como en el párrafo que lo rodea. Cuando se hace clic con el botón derecho del ratón, el manejador del botón llama a <code>stopPropagation</code>, lo que evitará que se ejecute el manejador en el párrafo. Cuando se hace clic en el botón con otro botón del ratón, ambos manejadores se ejecutarán.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-VU+8jIYijA" href="#c-VU+8jIYijA" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Un párrafo con un <<span class="tok-typeName">button</span>>botón</<span class="tok-typeName">button</span>>.</<span class="tok-typeName">p</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-w+JAotaejk" href="#c-w+JAotaejk" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Un párrafo con un <<span class="tok-typeName">button</span>>botón</<span class="tok-typeName">button</span>>.</<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">para</span> = document.querySelector(<span class="tok-string">"p"</span>); - <span class="tok-keyword">let</span> <span class="tok-definition">button</span> = document.querySelector(<span class="tok-string">"button"</span>); - para.addEventListener(<span class="tok-string">"mousedown"</span>, () => { + <span class="tok-keyword">let</span> <span class="tok-definition">parr</span> = document.querySelector(<span class="tok-string">"p"</span>); + <span class="tok-keyword">let</span> <span class="tok-definition">botón</span> = document.querySelector(<span class="tok-string">"button"</span>); + parr.addEventListener(<span class="tok-string">"mousedown"</span>, () => { console.log(<span class="tok-string">"Controlador para el párrafo."</span>); }); - button.addEventListener(<span class="tok-string">"mousedown"</span>, <span class="tok-definition">event</span> => { + botón.addEventListener(<span class="tok-string">"mousedown"</span>, <span class="tok-definition">event</span> => { console.log(<span class="tok-string">"Controlador para el botón."</span>); <span class="tok-keyword">if</span> (event.button == <span class="tok-number">2</span>) event.stopPropagation(); }); @@ -134,24 +134,24 @@ <h2><a class="h_ident" id="h-4NLRTYLmHH" href="#h-4NLRTYLmHH" tabindex="-1" role <h2><a class="h_ident" id="h-6R7Ptf/aVU" href="#h-6R7Ptf/aVU" tabindex="-1" role="presentation"></a>Acciones predeterminadas</h2> -<p><a class="p_ident" id="p-R2pr2N9dn+" href="#p-R2pr2N9dn+" tabindex="-1" role="presentation"></a>Muchos eventos tienen una acción predeterminada asociada a ellos. Si haces clic en un enlace, serás llevado al destino del enlace. Si presionas la flecha hacia abajo, el navegador desplazará la página hacia abajo. Si haces clic derecho, obtendrás un menú contextual. Y así sucesivamente.</p> +<p><a class="p_ident" id="p-0YxDwtCaUT" href="#p-0YxDwtCaUT" tabindex="-1" role="presentation"></a>Muchos eventos tienen una acción predeterminada asociada a ellos. Si haces clic en un enlace, serás llevado al destino del enlace. Si presionas la flecha hacia abajo, el navegador desplazará la página hacia abajo. Si haces clic derecho, obtendrás un menú contextual. Y así con todo.</p> -<p><a class="p_ident" id="p-eCArhCpoYq" href="#p-eCArhCpoYq" tabindex="-1" role="presentation"></a>Para la mayoría de los tipos de eventos, los controladores de eventos de JavaScript se ejecutan <em>antes</em> de que ocurra el comportamiento predeterminado. Si el controlador no desea que este comportamiento normal ocurra, típicamente porque ya se encargó de manejar el evento, puede llamar al método <code>preventDefault</code> en el objeto de evento.</p> +<p><a class="p_ident" id="p-eCArhCpoYq" href="#p-eCArhCpoYq" tabindex="-1" role="presentation"></a>Para la mayoría de los tipos de eventos, los manejadores de eventos de JavaScript se ejecutan <em>antes</em> de que ocurra el comportamiento predeterminado. Si el manejador no desea que este comportamiento normal ocurra, usualmente porque ya se ha encargado de manejar el evento, puede llamar al método <code>preventDefault</code> en el objeto de evento.</p> <p><a class="p_ident" id="p-RY97vdby+n" href="#p-RY97vdby+n" tabindex="-1" role="presentation"></a>Esto se puede utilizar para implementar tus propios atajos de teclado o menús contextuales. También se puede usar para interferir de manera molesta con el comportamiento que los usuarios esperan. Por ejemplo, aquí hay un enlace que no se puede seguir:</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-sGqQCQuHVQ" href="#c-sGqQCQuHVQ" tabindex="-1" role="presentation"></a><<span class="tok-typeName">a</span> href=<span class="tok-string">"https://developer.mozilla.org/"</span>>MDN</<span class="tok-typeName">a</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-Gj9esZ/lnR" href="#c-Gj9esZ/lnR" tabindex="-1" role="presentation"></a><<span class="tok-typeName">a</span> href=<span class="tok-string">"https://developer.mozilla.org/"</span>>MDN</<span class="tok-typeName">a</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">link</span> = document.querySelector(<span class="tok-string">"a"</span>); - link.addEventListener(<span class="tok-string">"click"</span>, <span class="tok-definition">event</span> => { + <span class="tok-keyword">let</span> <span class="tok-definition">enlace</span> = document.querySelector(<span class="tok-string">"a"</span>); + enlace.addEventListener(<span class="tok-string">"click"</span>, <span class="tok-definition">event</span> => { console.log(<span class="tok-string">"¡Incorrecto!"</span>); event.preventDefault(); }); </<span class="tok-typeName">script</span>></pre> -<p><a class="p_ident" id="p-H9L0J5B5xg" href="#p-H9L0J5B5xg" tabindex="-1" role="presentation"></a>Trata de no hacer este tipo de cosas a menos que tengas una razón realmente válida. Será desagradable para las personas que utilicen tu página cuando se rompa el comportamiento esperado.</p> +<p><a class="p_ident" id="p-H9L0J5B5xg" href="#p-H9L0J5B5xg" tabindex="-1" role="presentation"></a>Trata de no hacer este tipo de cosas a menos que tengas una buena razón para hacerlo. Será desagradable para las personas que utilicen tu página cuando se rompa el comportamiento esperado.</p> -<p><a class="p_ident" id="p-eCmiVPHlxu" href="#p-eCmiVPHlxu" tabindex="-1" role="presentation"></a>Dependiendo del navegador, algunos eventos no se pueden interceptar en absoluto. En Chrome, por ejemplo, el atajo de teclado para cerrar la pestaña actual (control-W o command-W) no se puede manejar con JavaScript.</p> +<p><a class="p_ident" id="p-eCmiVPHlxu" href="#p-eCmiVPHlxu" tabindex="-1" role="presentation"></a>Dependiendo del navegador, algunos eventos no se pueden interceptar. En Chrome, por ejemplo, el atajo de teclado para cerrar la pestaña actual (<span class="keyname">control</span>-<span class="keyname">W</span> o <span class="keyname">command</span>-<span class="keyname">W</span>) no se puede manejar con JavaScript.</p> <h2><a class="h_ident" id="h-eLNw0KdiHA" href="#h-eLNw0KdiHA" tabindex="-1" role="presentation"></a>Eventos de teclado</h2> @@ -171,11 +171,11 @@ <h2><a class="h_ident" id="h-eLNw0KdiHA" href="#h-eLNw0KdiHA" tabindex="-1" role }); </<span class="tok-typeName">script</span>></pre> -<p><a class="p_ident" id="p-/uBKiqvFy0" href="#p-/uBKiqvFy0" tabindex="-1" role="presentation"></a>A pesar de su nombre, <code>"keydown"</code> se dispara no solo cuando la tecla se presiona físicamente hacia abajo. Cuando se presiona y se mantiene una tecla, el evento se vuelve a disparar cada vez que la tecla <em>se repite</em>. A veces tienes que tener cuidado con esto. Por ejemplo, si agregas un botón al DOM cuando se presiona una tecla y lo eliminas de nuevo cuando se suelta la tecla, podrías agregar accidentalmente cientos de botones cuando se mantiene presionada la tecla durante más tiempo.</p> +<p><a class="p_ident" id="p-/uBKiqvFy0" href="#p-/uBKiqvFy0" tabindex="-1" role="presentation"></a>A pesar de su nombre, <code>"keydown"</code> se dispara no solo cuando la tecla se presiona físicamente hacia abajo. Cuando se presiona y se mantiene una tecla, el evento se vuelve a disparar cada vez que la tecla <em>se repite</em>. A veces tienes que tener cuidado con esto. Por ejemplo, si agregas un botón al DOM cuando se presiona una tecla y lo eliminas de nuevo cuando se suelta la tecla, podrías agregar sin querer cientos de botones al mantener presionada la tecla durante más tiempo.</p> -<p><a class="p_ident" id="p-I4C4KO6VjA" href="#p-I4C4KO6VjA" tabindex="-1" role="presentation"></a>El ejemplo observó la propiedad <code>key</code> del objeto evento para ver sobre qué tecla es el evento. Esta propiedad contiene una cadena que, para la mayoría de las teclas, corresponde a lo que escribirías al presionar esa tecla. Para teclas especiales como <span class="keyname">enter</span>, contiene una cadena que nombra la tecla (<code>"Enter"</code>, en este caso). Si mantienes presionado <span class="keyname">shift</span> mientras presionas una tecla, eso también puede influir en el nombre de la tecla: <code>"v"</code> se convierte en <code>"V"</code>, y <code>"1"</code> puede convertirse en <code>"!"</code>, si eso es lo que produce al presionar <span class="keyname">shift</span>-1 en tu teclado.</p> +<p><a class="p_ident" id="p-I4C4KO6VjA" href="#p-I4C4KO6VjA" tabindex="-1" role="presentation"></a>El ejemplo observó la propiedad <code>key</code> del objeto evento para ver sobre qué tecla es el evento. Esta propiedad contiene una cadena que, para la mayoría de las teclas, corresponde a lo que escribirías al presionar esa tecla. Para teclas especiales como <span class="keyname">enter</span>, contiene una cadena que nombra la tecla (<code>"Enter"</code>, en este caso). Si mantienes presionado <span class="keyname">shift</span> mientras presionas una tecla, eso también puede influir en el nombre de la tecla: <code>"v"</code> se convierte en <code>"V"</code>, y <code>"1"</code> puede convertirse en <code>"!"</code>, si eso es lo que se produce al presionar <span class="keyname">shift</span>-1 en tu teclado.</p> -<p><a class="p_ident" id="p-GTiYNx18vJ" href="#p-GTiYNx18vJ" tabindex="-1" role="presentation"></a>Las teclas modificadoras como <span class="keyname">shift</span>, <span class="keyname">control</span>, <span class="keyname">alt</span> y <span class="keyname">meta</span> (command en Mac) generan eventos de tecla igual que las teclas normales. Pero al buscar combinaciones de teclas, también puedes averiguar si estas teclas se mantienen presionadas mirando las propiedades <code>shiftKey</code>, <code>ctrlKey</code>, <code>altKey</code> y <code>metaKey</code> de los eventos de teclado y ratón.</p> +<p><a class="p_ident" id="p-GTiYNx18vJ" href="#p-GTiYNx18vJ" tabindex="-1" role="presentation"></a>Las teclas modificadoras como <span class="keyname">shift</span>, <span class="keyname">control</span>, <span class="keyname">alt</span> y <span class="keyname">meta</span> (<span class="keyname">command</span> en Mac) generan eventos de tecla igual que las teclas normales. Pero al buscar combinaciones de teclas, también puedes averiguar si estas teclas se mantienen presionadas mirando las propiedades <code>shiftKey</code>, <code>ctrlKey</code>, <code>altKey</code> y <code>metaKey</code> de los eventos de teclado y ratón.</p> <pre tabindex="0" class="snippet" data-language="html" data-focus="true"><a class="c_ident" id="c-8SxxAoGtUO" href="#c-8SxxAoGtUO" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Pulsa Control-Espacio para continuar.</<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> @@ -186,15 +186,15 @@ <h2><a class="h_ident" id="h-eLNw0KdiHA" href="#h-eLNw0KdiHA" tabindex="-1" role }); </<span class="tok-typeName">script</span>></pre> -<p><a class="p_ident" id="p-2tFLOxDicw" href="#p-2tFLOxDicw" tabindex="-1" role="presentation"></a>El nodo del DOM donde se origina un evento de teclado depende del elemento que tiene foco cuando se presiona la tecla. La mayoría de los nodos no pueden tener foco a menos que les des un atributo <code>tabindex</code>, pero cosas como los enlaces, botones y campos de formulario pueden. Volveremos a los campos de formulario en el <a href="18_http.html#forms">Capítulo 18</a>. Cuando nada en particular tiene foco, <code>document.body</code> actúa como el nodo objetivo de los eventos de teclado.</p> +<p><a class="p_ident" id="p-2tFLOxDicw" href="#p-2tFLOxDicw" tabindex="-1" role="presentation"></a>El nodo del DOM donde se origina un evento de teclado depende del elemento que tiene foco cuando se presiona la tecla. La mayoría de los nodos no pueden tener foco a menos que les des un atributo <code>tabindex</code>, pero cosas como los enlaces, botones y campos de formulario sí pueden. Volveremos a los campos de formulario en el <a href="18_http.html#forms">Capítulo 18</a>. Cuando no hay nada en particular con foco, <code>document.body</code> actúa como el nodo objetivo de los eventos de teclado.</p> -<p><a class="p_ident" id="p-24lfblPn/F" href="#p-24lfblPn/F" tabindex="-1" role="presentation"></a>Cuando el usuario está escribiendo texto, utilizar eventos de teclado para averiguar qué se está escribiendo es problemático. Algunas plataformas, especialmente el teclado virtual en teléfonos Android, no disparan eventos de teclado. Pero incluso cuando se tiene un teclado tradicional, algunos tipos de entrada de texto no coinciden con las pulsaciones de teclas de manera directa, como el software de <em>editor de método de entrada</em> (IME) utilizado por personas cuyos guiones no caben en un teclado, donde múltiples pulsaciones de teclas se combinan para crear caracteres.</p> +<p><a class="p_ident" id="p-24lfblPn/F" href="#p-24lfblPn/F" tabindex="-1" role="presentation"></a>Cuando el usuario está escribiendo texto, utilizar eventos de teclado para averiguar qué se está escribiendo es problemático. Algunas plataformas, especialmente el teclado virtual en teléfonos Android, no disparan eventos de teclado. Pero incluso cuando se tiene un teclado tradicional, algunos tipos de entrada de texto no coinciden con las pulsaciones de teclas de manera directa, como el software de <em>editor de método de entrada</em> (IME) utilizado por personas cuyos sistemas de escritura no caben en un teclado, donde múltiples pulsaciones de teclas se combinan para crear caracteres.</p> -<p><a class="p_ident" id="p-PQJVGZpm4G" href="#p-PQJVGZpm4G" tabindex="-1" role="presentation"></a>Para detectar cuando se ha escrito algo, los elementos en los que se puede escribir, como las etiquetas <code><input></code> y <code><textarea></code>, activan eventos <code>"input"</code> cada vez que el usuario cambia su contenido. Para obtener el contenido real que se ha escrito, lo mejor es leerlo directamente del campo enfocado. <a href="18_http.html#forms">Capítulo 18</a> mostrará cómo hacerlo.</p> +<p><a class="p_ident" id="p-PQJVGZpm4G" href="#p-PQJVGZpm4G" tabindex="-1" role="presentation"></a>Para detectar cuando se ha escrito algo, los elementos en los que se puede escribir, como las etiquetas <code><input></code> y <code><textarea></code>, activan eventos <code>"input"</code> cada vez que el usuario cambia su contenido. Para obtener el contenido real que se ha escrito, lo mejor es leerlo directamente del campo enfocado. El <a href="18_http.html#forms">Capítulo 18</a> mostrará cómo hacerlo.</p> <h2><a class="h_ident" id="h-AYhl2kjrOW" href="#h-AYhl2kjrOW" tabindex="-1" role="presentation"></a>Eventos de puntero</h2> -<p><a class="p_ident" id="p-6Y0Y9AhTlQ" href="#p-6Y0Y9AhTlQ" tabindex="-1" role="presentation"></a>Actualmente existen dos formas ampliamente utilizadas de señalar cosas en una pantalla: los ratones (incluyendo dispositivos que actúan como ratones, como touchpads y trackballs) y las pantallas táctiles. Estas producen diferentes tipos de eventos.</p> +<p><a class="p_ident" id="p-6Y0Y9AhTlQ" href="#p-6Y0Y9AhTlQ" tabindex="-1" role="presentation"></a>Actualmente existen dos formas ampliamente utilizadas de señalar cosas en una pantalla: los ratones (incluyendo dispositivos que actúan como ratones, como touchpads y trackballs) y las pantallas táctiles. Ambas producen diferentes tipos de eventos.</p> <h3><a class="i_ident" id="i-ld4kHyEZCM" href="#i-ld4kHyEZCM" tabindex="-1" role="presentation"></a>Clics de ratón</h3> @@ -204,16 +204,16 @@ <h3><a class="i_ident" id="i-ld4kHyEZCM" href="#i-ld4kHyEZCM" tabindex="-1" role <p><a class="p_ident" id="p-VTiFjJPIru" href="#p-VTiFjJPIru" tabindex="-1" role="presentation"></a>Si dos clics ocurren cerca uno del otro, también se dispara un evento <code>"dblclick"</code> (doble clic), después del segundo evento de clic.</p> -<p><a class="p_ident" id="p-bCo6gVY4HL" href="#p-bCo6gVY4HL" tabindex="-1" role="presentation"></a>Para obtener información precisa sobre el lugar donde ocurrió un evento de ratón, puedes mirar sus propiedades <code>clientX</code> y <code>clientY</code>, que contienen las coordenadas del evento (en píxeles) relativas a la esquina superior izquierda de la ventana, o <code>pageX</code> y <code>pageY</code>, que son relativas a la esquina superior izquierda de todo el documento (lo cual puede ser diferente cuando la ventana ha sido desplazada).</p> +<p><a class="p_ident" id="p-bCo6gVY4HL" href="#p-bCo6gVY4HL" tabindex="-1" role="presentation"></a>Para obtener información precisa sobre el lugar donde ocurrió un evento de ratón, puedes mirar sus propiedades <code>clientX</code> y <code>clientY</code>, que contienen las coordenadas del evento (en píxeles) relativas a la esquina superior izquierda de la ventana, o <code>pageX</code> y <code>pageY</code>, que son relativas a la esquina superior izquierda de todo el documento (estas pueden ser diferentes cuando la ventana ha sido desplazada).</p> -<p id="dibujo con ratón"><a class="p_ident" id="p-u6ObYcInxf" href="#p-u6ObYcInxf" tabindex="-1" role="presentation"></a>El siguiente programa implementa una aplicación de dibujo primitiva. Cada vez que haces clic en el documento, agrega un punto bajo el puntero de tu ratón. Ver <a href="19_paint.html">Capítulo 19</a> para una aplicación de dibujo menos primitiva.</p> +<p id="dibujo_con_ratón"><a class="p_ident" id="p-u6ObYcInxf" href="#p-u6ObYcInxf" tabindex="-1" role="presentation"></a>El siguiente programa implementa una aplicación de dibujo primitiva. Cada vez que haces clic en el documento, agrega un punto bajo el puntero de tu ratón. Ver <a href="19_paint.html">Capítulo 19</a> para una aplicación de dibujo menos primitiva.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-vLkN71+z+e" href="#c-vLkN71+z+e" tabindex="-1" role="presentation"></a><<span class="tok-typeName">style</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-TjedoBBIL0" href="#c-TjedoBBIL0" tabindex="-1" role="presentation"></a><<span class="tok-typeName">style</span>> <span class="tok-typeName">body</span> { height: <span class="tok-number">200</span><span class="tok-keyword">px</span>; background: <span class="tok-atom">beige</span>; } - .dot { + .punto { height: <span class="tok-number">8</span><span class="tok-keyword">px</span>; width: <span class="tok-number">8</span><span class="tok-keyword">px</span>; border-radius: <span class="tok-number">4</span><span class="tok-keyword">px</span>; <span class="tok-comment">/* redondea las esquinas */</span> background: <span class="tok-atom">teal</span>; @@ -222,11 +222,11 @@ <h3><a class="i_ident" id="i-ld4kHyEZCM" href="#i-ld4kHyEZCM" tabindex="-1" role </<span class="tok-typeName">style</span>> <<span class="tok-typeName">script</span>> window.addEventListener(<span class="tok-string">"click"</span>, <span class="tok-definition">event</span> => { - <span class="tok-keyword">let</span> <span class="tok-definition">dot</span> = document.createElement(<span class="tok-string">"div"</span>); - dot.className = <span class="tok-string">"dot"</span>; - dot.style.left = (event.pageX - <span class="tok-number">4</span>) + <span class="tok-string">"px"</span>; - dot.style.top = (event.pageY - <span class="tok-number">4</span>) + <span class="tok-string">"px"</span>; - document.body.appendChild(dot); + <span class="tok-keyword">let</span> <span class="tok-definition">punto</span> = document.createElement(<span class="tok-string">"div"</span>); + punto.className = <span class="tok-string">"punto"</span>; + punto.style.left = (event.pageX - <span class="tok-number">4</span>) + <span class="tok-string">"px"</span>; + punto.style.top = (event.pageY - <span class="tok-number">4</span>) + <span class="tok-string">"px"</span>; + document.body.appendChild(punto); }); </<span class="tok-typeName">script</span>></pre> @@ -234,133 +234,133 @@ <h3><a class="i_ident" id="i-uAoy9QjKsj" href="#i-uAoy9QjKsj" tabindex="-1" role <p><a class="p_ident" id="p-wmFbB/1fGJ" href="#p-wmFbB/1fGJ" tabindex="-1" role="presentation"></a>Cada vez que el puntero del ratón se mueve, se dispara un evento <code>"mousemove"</code>. Este evento se puede usar para rastrear la posición del ratón. Una situación común en la que esto es útil es al implementar algún tipo de funcionalidad de arrastrar y soltar con el ratón.</p> -<p><a class="p_ident" id="p-2QDCAMGt0z" href="#p-2QDCAMGt0z" tabindex="-1" role="presentation"></a>Como ejemplo, el siguiente programa muestra una barra y configura controladores de eventos para que al arrastrar hacia la izquierda o hacia la derecha en esta barra, se haga más estrecha o más ancha:</p> +<p><a class="p_ident" id="p-2QDCAMGt0z" href="#p-2QDCAMGt0z" tabindex="-1" role="presentation"></a>Como ejemplo, el siguiente programa muestra una barra y configura manejadores de eventos para que al arrastrar hacia la izquierda o hacia la derecha en esta barra, se haga más estrecha o más ancha:</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-UTKlG/Tbre" href="#c-UTKlG/Tbre" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Arrastra la barra para cambiar su anchura:</<span class="tok-typeName">p</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-7ahXgKwwQV" href="#c-7ahXgKwwQV" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Arrastra la barra para cambiar su anchura:</<span class="tok-typeName">p</span>> <<span class="tok-typeName">div</span> style=<span class="tok-string">"</span>background: <span class="tok-atom">orange</span>; width: <span class="tok-number">60</span><span class="tok-keyword">px</span>; height: <span class="tok-number">20</span><span class="tok-keyword">px</span><span class="tok-string">"</span>> </<span class="tok-typeName">div</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">lastX</span>; <span class="tok-comment">// Rastrea la última posición X del ratón observada</span> - <span class="tok-keyword">let</span> <span class="tok-definition">bar</span> = document.querySelector(<span class="tok-string">"div"</span>); - bar.addEventListener(<span class="tok-string">"mousedown"</span>, <span class="tok-definition">event</span> => { + <span class="tok-keyword">let</span> <span class="tok-definition">últimaX</span>; <span class="tok-comment">// Rastrea la última posición X del ratón observada</span> + <span class="tok-keyword">let</span> <span class="tok-definition">barra</span> = document.querySelector(<span class="tok-string">"div"</span>); + barra.addEventListener(<span class="tok-string">"mousedown"</span>, <span class="tok-definition">event</span> => { <span class="tok-keyword">if</span> (event.button == <span class="tok-number">0</span>) { - lastX = event.clientX; - window.addEventListener(<span class="tok-string">"mousemove"</span>, moved); - event.preventDefault(); <span class="tok-comment">// Prevenir selección</span> + últimaX = event.clientX; + window.addEventListener(<span class="tok-string">"mousemove"</span>, movido); + event.preventDefault(); <span class="tok-comment">// Evitar selección</span> } }); - <span class="tok-keyword">function</span> <span class="tok-definition">moved</span>(<span class="tok-definition">event</span>) { - <span class="tok-keyword">if</span> (event.buttons == <span class="tok-number">0</span>) { - window.removeEventListener(<span class="tok-string">"mousemove"</span>, moved); + <span class="tok-keyword">function</span> <span class="tok-definition">movido</span>(<span class="tok-definition">evento</span>) { + <span class="tok-keyword">if</span> (evento.buttons == <span class="tok-number">0</span>) { + window.removeEventListener(<span class="tok-string">"mousemove"</span>, movido); } <span class="tok-keyword">else</span> { - <span class="tok-keyword">let</span> <span class="tok-definition">dist</span> = event.clientX - lastX; - <span class="tok-keyword">let</span> <span class="tok-definition">newWidth</span> = Math.max(<span class="tok-number">10</span>, bar.offsetWidth + dist); - bar.style.width = newWidth + <span class="tok-string">"px"</span>; - lastX = event.clientX; + <span class="tok-keyword">let</span> <span class="tok-definition">dist</span> = event.clientX - últimaX; + <span class="tok-keyword">let</span> <span class="tok-definition">nuevoAncho</span> = Math.max(<span class="tok-number">10</span>, barra.offsetWidth + dist); + barra.style.width = nuevoAncho + <span class="tok-string">"px"</span>; + últimaX = event.clientX; } } </<span class="tok-typeName">script</span>></pre> -<p><a class="p_ident" id="p-fI+m6ASEUA" href="#p-fI+m6ASEUA" tabindex="-1" role="presentation"></a>Ten en cuenta que el controlador <code>"mousemove"</code> está registrado en toda la window. Incluso si el ratón sale de la barra durante el cambio de tamaño, mientras el botón se mantenga presionado todavía queremos actualizar su tamaño.</p> +<p><a class="p_ident" id="p-fI+m6ASEUA" href="#p-fI+m6ASEUA" tabindex="-1" role="presentation"></a>Ten en cuenta que el controlador <code>"mousemove"</code> está registrado en toda la ventana. Incluso si el ratón sale de la barra durante el cambio de tamaño, mientras el botón se mantenga presionado todavía queremos actualizar su tamaño.</p> -<p><a class="p_ident" id="p-khR2v5y/R0" href="#p-khR2v5y/R0" tabindex="-1" role="presentation"></a>Debemos detener el cambio de tamaño de la barra cuando se libere el botón del ratón. Para eso, podemos usar la propiedad <code>buttons</code> (notar el plural), que nos indica qué botones están actualmente presionados. Cuando este valor es cero, ningún botón está presionado. Cuando se mantienen presionados botones, su valor es la suma de los códigos de esos botones—el botón izquierdo tiene el código 1, el derecho 2 y el central 4. Con el botón izquierdo y el derecho presionados, por ejemplo, el valor de <code>buttons</code> será 3.</p> +<p><a class="p_ident" id="p-khR2v5y/R0" href="#p-khR2v5y/R0" tabindex="-1" role="presentation"></a>Debemos detener el cambio de tamaño de la barra cuando se libere el botón del ratón. Para eso, podemos usar la propiedad <code>buttons</code> (atención al plural), que nos indica qué botones están actualmente presionados. Cuando este valor es cero, ningún botón está presionado. Cuando se mantienen presionados botones, su valor es la suma de los códigos de esos botones—el botón izquierdo tiene el código 1, el derecho 2 y el central 4. Con el botón izquierdo y el derecho presionados, por ejemplo, el valor de <code>buttons</code> será 3.</p> <p><a class="p_ident" id="p-lvY87c8VFr" href="#p-lvY87c8VFr" tabindex="-1" role="presentation"></a>Es importante destacar que el orden de estos códigos es diferente al utilizado por <code>button</code>, donde el botón central venía antes que el derecho. Como se mencionó, la consistencia no es realmente un punto fuerte de la interfaz de programación del navegador.</p> <h3><a class="i_ident" id="i-QH29N+27Jo" href="#i-QH29N+27Jo" tabindex="-1" role="presentation"></a>Eventos táctiles</h3> -<p><a class="p_ident" id="p-3qJEJXxC/X" href="#p-3qJEJXxC/X" tabindex="-1" role="presentation"></a>El estilo de navegador gráfico que usamos fue diseñado pensando en interfaces de ratón, en una época donde las pantallas táctiles eran raras. Para hacer que la web “funcione” en los primeros teléfonos con pantalla táctil, los navegadores de esos dispositivos fingían, hasta cierto punto, que los eventos táctiles eran eventos de ratón. Si tocas la pantalla, recibirás eventos de <code>"mousedown"</code>, <code>"mouseup"</code> y <code>"click"</code>.</p> +<p><a class="p_ident" id="p-3qJEJXxC/X" href="#p-3qJEJXxC/X" tabindex="-1" role="presentation"></a>El estilo de navegador gráfico que usamos fue diseñado pensando en interfaces de ratón, en una época donde las pantallas táctiles no eran muy comunes. Para hacer que la web “funcione” en los primeros teléfonos con pantalla táctil, los navegadores de esos dispositivos fingían, hasta cierto punto, que los eventos táctiles eran eventos de ratón. Si tocas la pantalla, recibirás eventos de <code>"mousedown"</code>, <code>"mouseup"</code> y <code>"click"</code>.</p> <p><a class="p_ident" id="p-FMkG1NPMsu" href="#p-FMkG1NPMsu" tabindex="-1" role="presentation"></a>Pero esta ilusión no es muy robusta. Una pantalla táctil funciona de manera diferente a un ratón: no tiene múltiples botones, no se puede rastrear el dedo cuando no está en la pantalla (para simular <code>"mousemove"</code>), y permite que varios dedos estén en la pantalla al mismo tiempo.</p> <p><a class="p_ident" id="p-h68paN5HEy" href="#p-h68paN5HEy" tabindex="-1" role="presentation"></a>Los eventos de ratón solo cubren la interacción táctil en casos sencillos: si agregas un controlador de <code>"click"</code> a un botón, los usuarios táctiles aún podrán usarlo. Pero algo como la barra redimensionable del ejemplo anterior no funciona en una pantalla táctil.</p> -<p><a class="p_ident" id="p-n2OW5CUnrz" href="#p-n2OW5CUnrz" tabindex="-1" role="presentation"></a>Existen tipos específicos de eventos disparados por la interacción táctil. Cuando un dedo comienza a tocar la pantalla, se genera un evento <code>"touchstart"</code>. Cuando se mueve mientras toca, se generan eventos <code>"touchmove"</code>. Finalmente, cuando deja de tocar la pantalla, verás un evento <code>"touchend"</code>.</p> +<p><a class="p_ident" id="p-n2OW5CUnrz" href="#p-n2OW5CUnrz" tabindex="-1" role="presentation"></a>Existen tipos específicos de eventos que se disparan por la interacción táctil. Cuando un dedo comienza a tocar la pantalla, se genera un evento <code>"touchstart"</code>. Cuando se mueve mientras toca, se generan eventos <code>"touchmove"</code>. Finalmente, cuando deja de tocar la pantalla, verás un evento <code>"touchend"</code>.</p> -<p><a class="p_ident" id="p-RgqmyZVECT" href="#p-RgqmyZVECT" tabindex="-1" role="presentation"></a>Debido a que muchas pantallas táctiles pueden detectar varios dedos al mismo tiempo, estos eventos no tienen un único conjunto de coordenadas asociadas. Más bien, sus objetos de eventos tienen una propiedad <code>touches</code>, que contiene un objeto similar a un array de puntos, cada uno con sus propias propiedades <code>clientX</code>, <code>clientY</code>, <code>pageX</code> y <code>pageY</code>.</p> +<p><a class="p_ident" id="p-RgqmyZVECT" href="#p-RgqmyZVECT" tabindex="-1" role="presentation"></a>Debido a que muchas pantallas táctiles pueden detectar varios dedos al mismo tiempo, estos eventos no tienen un único conjunto de coordenadas asociadas. Más bien, sus objetos de eventos tienen una propiedad <code>touches</code>, que contiene un objeto parecido a un array de puntos, cada uno con sus propias propiedades <code>clientX</code>, <code>clientY</code>, <code>pageX</code> y <code>pageY</code>.</p> <p><a class="p_ident" id="p-ye0cXsCzXJ" href="#p-ye0cXsCzXJ" tabindex="-1" role="presentation"></a>Podrías hacer algo como esto para mostrar círculos rojos alrededor de cada dedo que toca:</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-o5U4LM42M9" href="#c-o5U4LM42M9" tabindex="-1" role="presentation"></a><<span class="tok-typeName">style</span>> - <span class="tok-typeName">dot</span> { position: <span class="tok-atom">absolute</span>; display: <span class="tok-atom">block</span>; +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-ftkqLabATg" href="#c-ftkqLabATg" tabindex="-1" role="presentation"></a><<span class="tok-typeName">style</span>> + <span class="tok-typeName">punto</span> { position: <span class="tok-atom">absolute</span>; display: <span class="tok-atom">block</span>; border: <span class="tok-number">2</span><span class="tok-keyword">px</span> <span class="tok-atom">solid</span> <span class="tok-atom">red</span>; border-radius: <span class="tok-number">50</span><span class="tok-keyword">px</span>; height: <span class="tok-number">100</span><span class="tok-keyword">px</span>; width: <span class="tok-number">100</span><span class="tok-keyword">px</span>; } </<span class="tok-typeName">style</span>> <<span class="tok-typeName">p</span>>Toca esta página</<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">function</span> <span class="tok-definition">update</span>(<span class="tok-definition">event</span>) { - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">dot</span>; dot = document.querySelector(<span class="tok-string">"dot"</span>);) { - dot.remove(); + <span class="tok-keyword">function</span> <span class="tok-definition">actualizar</span>(<span class="tok-definition">evento</span>) { + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">punto</span>; punto = document.querySelector(<span class="tok-string">"punto"</span>);) { + punto.remove(); } - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = <span class="tok-number">0</span>; i < event.touches.length; i++) { - <span class="tok-keyword">let</span> {pageX, pageY} = event.touches[i]; - <span class="tok-keyword">let</span> <span class="tok-definition">dot</span> = document.createElement(<span class="tok-string">"dot"</span>); - dot.style.left = (pageX - <span class="tok-number">50</span>) + <span class="tok-string">"px"</span>; - dot.style.top = (pageY - <span class="tok-number">50</span>) + <span class="tok-string">"px"</span>; - document.body.appendChild(dot); + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = <span class="tok-number">0</span>; i < evento.touches.length; i++) { + <span class="tok-keyword">let</span> {pageX, pageY} = evento.touches[i]; + <span class="tok-keyword">let</span> <span class="tok-definition">punto</span> = document.createElement(<span class="tok-string">"punto"</span>); + punto.style.left = (pageX - <span class="tok-number">50</span>) + <span class="tok-string">"px"</span>; + punto.style.top = (pageY - <span class="tok-number">50</span>) + <span class="tok-string">"px"</span>; + document.body.appendChild(punto); } } - window.addEventListener(<span class="tok-string">"touchstart"</span>, update); - window.addEventListener(<span class="tok-string">"touchmove"</span>, update); - window.addEventListener(<span class="tok-string">"touchend"</span>, update); + window.addEventListener(<span class="tok-string">"touchstart"</span>, actualizar); + window.addEventListener(<span class="tok-string">"touchmove"</span>, actualizar); + window.addEventListener(<span class="tok-string">"touchend"</span>, actualizar); </<span class="tok-typeName">script</span>></pre> <p><a class="p_ident" id="p-3HLdUVc1+r" href="#p-3HLdUVc1+r" tabindex="-1" role="presentation"></a>A menudo querrás llamar a <code>preventDefault</code> en los controladores de eventos táctiles para anular el comportamiento predeterminado del navegador (que puede incluir desplazar la página al deslizar) y evitar que se generen eventos de ratón, para los cuales también puedes tener un controlador.</p> <h2><a class="h_ident" id="h-GirMfi5LMB" href="#h-GirMfi5LMB" tabindex="-1" role="presentation"></a>Eventos de desplazamiento</h2> -<p><a class="p_ident" id="p-53ahV1ZLJz" href="#p-53ahV1ZLJz" tabindex="-1" role="presentation"></a>Cada vez que un elemento se desplaza, se dispara un evento <code>"scroll"</code>. Esto tiene varios usos, como saber qué está viendo actualmente el usuario (para desactivar animaciones fuera de la pantalla o enviar informes de vigilancia a tu malvada sede) o mostrar alguna indicación de progreso (resaltando parte de una tabla de contenidos o mostrando un número de página).El siguiente ejemplo dibuja una barra de progreso sobre el documento y la actualiza para llenarla a medida que se desplaza hacia abajo:</p> +<p><a class="p_ident" id="p-53ahV1ZLJz" href="#p-53ahV1ZLJz" tabindex="-1" role="presentation"></a>Cada vez que un elemento se desplaza, se dispara un evento <code>"scroll"</code>. Esto tiene varios usos, como saber qué está viendo actualmente el usuario (para desactivar animaciones fuera de la pantalla o enviar informes de vigilancia a tu malvado cuartel general) o mostrar alguna indicación de progreso (resaltando parte de una tabla de contenidos o mostrando un número de página).El siguiente ejemplo dibuja una barra de progreso sobre el documento y la actualiza para llenarla a medida que se desplaza hacia abajo:</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-7tyBZD/B1O" href="#c-7tyBZD/B1O" tabindex="-1" role="presentation"></a><<span class="tok-typeName">style</span>> - #progress { +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-UPP+elrWis" href="#c-UPP+elrWis" tabindex="-1" role="presentation"></a><<span class="tok-typeName">style</span>> + #progreso { border-bottom: <span class="tok-number">2</span><span class="tok-keyword">px</span> <span class="tok-atom">solid</span> <span class="tok-atom">blue</span>; width: <span class="tok-number">0</span>; position: <span class="tok-atom">fixed</span>; top: <span class="tok-number">0</span>; left: <span class="tok-number">0</span>; } </<span class="tok-typeName">style</span>> -<<span class="tok-typeName">div</span> id=<span class="tok-string">"progress"</span>></<span class="tok-typeName">div</span>> +<<span class="tok-typeName">div</span> id=<span class="tok-string">"progreso"</span>></<span class="tok-typeName">div</span>> <<span class="tok-typeName">script</span>> <span class="tok-comment">// Create some content</span> document.body.appendChild(document.createTextNode( - <span class="tok-string">"supercalifragilisticexpialidocious "</span>.repeat(<span class="tok-number">1000</span>))); + <span class="tok-string">"supercalifragilisticoespialidoso "</span>.repeat(<span class="tok-number">1000</span>))); - <span class="tok-keyword">let</span> <span class="tok-definition">bar</span> = document.querySelector(<span class="tok-string">"#progress"</span>); + <span class="tok-keyword">let</span> <span class="tok-definition">barra</span> = document.querySelector(<span class="tok-string">"#progreso"</span>); window.addEventListener(<span class="tok-string">"scroll"</span>, () => { <span class="tok-keyword">let</span> <span class="tok-definition">max</span> = document.body.scrollHeight - innerHeight; - bar.style.width = <span class="tok-string2">`</span>${(pageYOffset / max) * <span class="tok-number">100</span>}<span class="tok-string2">%`</span>; + barra.style.width = <span class="tok-string2">`</span>${(pageYOffset / max) * <span class="tok-number">100</span>}<span class="tok-string2">%`</span>; }); </<span class="tok-typeName">script</span>></pre> <p><a class="p_ident" id="p-wyaK3qXoPQ" href="#p-wyaK3qXoPQ" tabindex="-1" role="presentation"></a>Darle a un elemento una <code>position</code> de <code>fixed</code> actúa de manera similar a una posición <code>absolute</code>, pero también evita que se desplace junto con el resto del documento. El efecto es hacer que nuestra barra de progreso permanezca en la parte superior. Su ancho se cambia para indicar el progreso actual. Usamos <code>%</code>, en lugar de <code>px</code>, como unidad al establecer el ancho para que el elemento tenga un tamaño relativo al ancho de la página.</p> -<p><a class="p_ident" id="p-ZC4hff5qkY" href="#p-ZC4hff5qkY" tabindex="-1" role="presentation"></a>El enlace global <code>innerHeight</code> nos da la altura de la ventana, que debemos restar de la altura total desplazable, ya que no se puede seguir desplazando cuando se llega al final del documento. También existe un <code>innerWidth</code> para el ancho de la ventana. Al dividir <code>pageYOffset</code>, la posición actual de desplazamiento, por la posición máxima de desplazamiento y multiplicar por 100, obtenemos el porcentaje para la barra de progreso.</p> +<p><a class="p_ident" id="p-b6nSYl2fJh" href="#p-b6nSYl2fJh" tabindex="-1" role="presentation"></a>La variable global <code>innerHeight</code> nos da la altura de la ventana, que debemos restar de la altura total desplazable, ya que no se puede seguir desplazando cuando se llega al final del documento. También existe un <code>innerWidth</code> para el ancho de la ventana. Al dividir <code>pageYOffset</code>, la posición actual de desplazamiento, por la posición máxima de desplazamiento y multiplicar por 100, obtenemos el porcentaje para la barra de progreso.</p> -<p><a class="p_ident" id="p-+d2fsgi/S8" href="#p-+d2fsgi/S8" tabindex="-1" role="presentation"></a>Llamar a <code>preventDefault</code> en un evento de desplazamiento no impide que ocurra el desplazamiento. De hecho, el controlador de eventos se llama solo <em>después</em> de que ocurre el desplazamiento.</p> +<p><a class="p_ident" id="p-eZ9Mlz5NRI" href="#p-eZ9Mlz5NRI" tabindex="-1" role="presentation"></a>Llamar a <code>preventDefault</code> en un evento de desplazamiento no impide que ocurra el desplazamiento. De hecho, el controlador de eventos se llama justo <em>después</em> de que ocurra el desplazamiento.</p> <h2><a class="h_ident" id="h-Hax528+TkG" href="#h-Hax528+TkG" tabindex="-1" role="presentation"></a>Eventos de enfoque</h2> <p><a class="p_ident" id="p-PRA9P1NtZm" href="#p-PRA9P1NtZm" tabindex="-1" role="presentation"></a>Cuando un elemento recibe el enfoque, el navegador dispara un evento <code>"focus"</code> en él. Cuando pierde el enfoque, el elemento recibe un evento <code>"blur"</code>.</p> -<p><a class="p_ident" id="p-8kzN7NKw/2" href="#p-8kzN7NKw/2" tabindex="-1" role="presentation"></a>A diferencia de los eventos discutidos anteriormente, estos dos eventos no se propagan. Un controlador en un elemento padre no recibe notificaciones cuando un elemento hijo recibe o pierde el enfoque.</p> +<p><a class="p_ident" id="p-8kzN7NKw/2" href="#p-8kzN7NKw/2" tabindex="-1" role="presentation"></a>A diferencia de los eventos discutidos anteriormente, estos dos eventos no se propagan. Un manejador en un elemento padre no recibe notificaciones cuando un elemento hijo recibe o pierde el enfoque.</p> <p><a class="p_ident" id="p-D8wyq1oJ5u" href="#p-D8wyq1oJ5u" tabindex="-1" role="presentation"></a>El siguiente ejemplo muestra texto de ayuda para el campo de texto que actualmente tiene el foco:</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-WCzW7hyh0Y" href="#c-WCzW7hyh0Y" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Nombre: <<span class="tok-typeName">input</span> type=<span class="tok-string">"text"</span> data-help=<span class="tok-string">"Tu nombre completo"</span>></<span class="tok-typeName">p</span>> -<<span class="tok-typeName">p</span>>Edad: <<span class="tok-typeName">input</span> type=<span class="tok-string">"text"</span> data-help=<span class="tok-string">"Tu edad en años"</span>></<span class="tok-typeName">p</span>> -<<span class="tok-typeName">p</span> id=<span class="tok-string">"help"</span>></<span class="tok-typeName">p</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-xiEiHXW4DO" href="#c-xiEiHXW4DO" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>>Nombre: <<span class="tok-typeName">input</span> type=<span class="tok-string">"text"</span> data-ayuda=<span class="tok-string">"Tu nombre completo"</span>></<span class="tok-typeName">p</span>> +<<span class="tok-typeName">p</span>>Edad: <<span class="tok-typeName">input</span> type=<span class="tok-string">"text"</span> data-ayuda=<span class="tok-string">"Tu edad en años"</span>></<span class="tok-typeName">p</span>> +<<span class="tok-typeName">p</span> id=<span class="tok-string">"ayuda"</span>></<span class="tok-typeName">p</span>> <<span class="tok-typeName">script</span>> - <span class="tok-keyword">let</span> <span class="tok-definition">help</span> = document.querySelector(<span class="tok-string">"#help"</span>); - <span class="tok-keyword">let</span> <span class="tok-definition">fields</span> = document.querySelectorAll(<span class="tok-string">"input"</span>); - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">field</span> <span class="tok-keyword">of</span> Array.from(fields)) { - field.addEventListener(<span class="tok-string">"focus"</span>, <span class="tok-definition">event</span> => { - <span class="tok-keyword">let</span> <span class="tok-definition">text</span> = event.target.getAttribute(<span class="tok-string">"data-help"</span>); - help.textContent = text; + <span class="tok-keyword">let</span> <span class="tok-definition">ayuda</span> = document.querySelector(<span class="tok-string">"#ayuda"</span>); + <span class="tok-keyword">let</span> <span class="tok-definition">campos</span> = document.querySelectorAll(<span class="tok-string">"input"</span>); + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">campo</span> <span class="tok-keyword">of</span> Array.from(campos)) { + campo.addEventListener(<span class="tok-string">"focus"</span>, <span class="tok-definition">event</span> => { + <span class="tok-keyword">let</span> <span class="tok-definition">texto</span> = event.target.getAttribute(<span class="tok-string">"data-ayuda"</span>); + ayuda.textContent = texto; }); - field.addEventListener(<span class="tok-string">"blur"</span>, <span class="tok-definition">event</span> => { - help.textContent = <span class="tok-string">""</span>; + campo.addEventListener(<span class="tok-string">"blur"</span>, <span class="tok-definition">evento</span> => { + ayuda.textContent = <span class="tok-string">""</span>; }); } </<span class="tok-typeName">script</span>></pre> @@ -371,11 +371,11 @@ <h2><a class="h_ident" id="h-FDJa9FAIT3" href="#h-FDJa9FAIT3" tabindex="-1" role <p><a class="p_ident" id="p-p/L0xopzQf" href="#p-p/L0xopzQf" tabindex="-1" role="presentation"></a>Elementos como imágenes y etiquetas de script que cargan un archivo externo también tienen un evento <code>"load"</code> que indica que se cargaron los archivos a los que hacen referencia. Al igual que los eventos relacionados con el enfoque, los eventos de carga no se propagan.</p> -<p><a class="p_ident" id="p-UoAZklI9NR" href="#p-UoAZklI9NR" tabindex="-1" role="presentation"></a>Cuando se cierra una página o se navega lejos de ella (por ejemplo, al seguir un enlace), se dispara un evento <code>"beforeunload"</code>. El uso principal de este evento es evitar que el usuario pierda accidentalmente su trabajo al cerrar un documento. Si previenes el comportamiento predeterminado en este evento <em>y</em> estableces la propiedad <code>returnValue</code> en el objeto de evento a una cadena, el navegador mostrará al usuario un cuadro de diálogo preguntando si realmente desea abandonar la página. Ese cuadro de diálogo podría incluir tu cadena, pero debido a que algunos sitios maliciosos intentan usar estos cuadros de diálogo para confundir a las personas y hacer que se queden en su página para ver anuncios de pérdida de peso dudosos, la mayoría de los navegadores ya no los muestran.</p> +<p><a class="p_ident" id="p-UoAZklI9NR" href="#p-UoAZklI9NR" tabindex="-1" role="presentation"></a>Cuando se cierra una página o se navega lejos de ella (por ejemplo, al seguir un enlace), se dispara un evento <code>"beforeunload"</code>. El uso principal de este evento es evitar que el usuario pierda accidentalmente su trabajo al cerrar un documento. Si evitas el comportamiento predeterminado en este evento <em>y</em> estableces la propiedad <code>returnValue</code> en el objeto de evento a una cadena, el navegador mostrará al usuario un cuadro de diálogo preguntando si realmente desea abandonar la página. Ese cuadro de diálogo podría incluir tu cadena, pero debido a que algunos sitios maliciosos intentan usar estos cuadros de diálogo para confundir a las personas y hacer que se queden en su página para ver dudosos anuncios de pérdida de peso, la mayoría de los navegadores ya no los muestran.</p> <h2 id="timeline"><a class="h_ident" id="h-h3a3U37DpR" href="#h-h3a3U37DpR" tabindex="-1" role="presentation"></a>Eventos y el bucle de eventos</h2> -<p><a class="p_ident" id="p-XL+MT1/Um5" href="#p-XL+MT1/Um5" tabindex="-1" role="presentation"></a>En el contexto del bucle de eventos, como se discutió en el <a href="11_async.html">Capítulo 11</a>, los controladores de eventos del navegador se comportan como otras notificaciones asíncronas. Se programan cuando ocurre el evento pero deben esperar a que otros scripts que se estén ejecutando terminen antes de tener la oportunidad de ejecutarse.</p> +<p><a class="p_ident" id="p-ZLVJyKWam2" href="#p-ZLVJyKWam2" tabindex="-1" role="presentation"></a>En el contexto del bucle de eventos, como se discutió en el <a href="11_async.html">Capítulo 11</a>, los manejadores de eventos del navegador se comportan como cualquier otra notificación asíncrona. Se programan cuando ocurre el evento pero antes de tener la oportunidad de ejecutarse deben esperar a que otros scripts que se estén ejecutando terminen.</p> <p><a class="p_ident" id="p-5QVtRn61a1" href="#p-5QVtRn61a1" tabindex="-1" role="presentation"></a>El hecho de que los eventos solo se puedan procesar cuando no hay nada más en ejecución significa que, si el bucle de eventos está ocupado con otro trabajo, cualquier interacción con la página (que ocurre a través de eventos) se retrasará hasta que haya tiempo para procesarla. Entonces, si programas demasiado trabajo, ya sea con controladores de eventos de larga duración o con muchos que se ejecutan rápidamente, la página se volverá lenta y pesada de usar.</p> @@ -383,17 +383,17 @@ <h2 id="timeline"><a class="h_ident" id="h-h3a3U37DpR" href="#h-h3a3U37DpR" tabi <p><a class="p_ident" id="p-ES+9mz3Jrf" href="#p-ES+9mz3Jrf" tabindex="-1" role="presentation"></a>Imagina que elevar al cuadrado un número es una computación pesada y de larga duración que queremos realizar en un hilo separado. Podríamos escribir un archivo llamado <code>code/<wbr>squareworker.<wbr>js</code> que responda a mensajes calculando un cuadrado y enviando un mensaje de vuelta.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-FgmodjGwd9" href="#c-FgmodjGwd9" tabindex="-1" role="presentation"></a>addEventListener(<span class="tok-string">"message"</span>, <span class="tok-definition">event</span> => { - postMessage(event.data * event.data); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-VjpRQ7Nm5N" href="#c-VjpRQ7Nm5N" tabindex="-1" role="presentation"></a>addEventListener(<span class="tok-string">"message"</span>, <span class="tok-definition">evento</span> => { + postMessage(evento.data * evento.data); });</pre> -<p><a class="p_ident" id="p-Ir+TqGr7+e" href="#p-Ir+TqGr7+e" tabindex="-1" role="presentation"></a>Para evitar los problemas de tener múltiples hilos tocando los mismos datos, los workers no comparten su alcance global ni ningún otro dato con el entorno del script principal. En cambio, debes comunicarte con ellos enviando mensajes de ida y vuelta.</p> +<p><a class="p_ident" id="p-Ir+TqGr7+e" href="#p-Ir+TqGr7+e" tabindex="-1" role="presentation"></a>Para evitar los problemas de tener múltiples hilos tocando los mismos datos, los workers no comparten su alcance global ni ningún otro dato con el entorno del script principal. En vez de eso, debes comunicarte con ellos enviando mensajes de ida y vuelta.</p> <p><a class="p_ident" id="p-N2CbRaMg8i" href="#p-N2CbRaMg8i" tabindex="-1" role="presentation"></a>Este código genera un worker que ejecuta ese script, le envía algunos mensajes y muestra las respuestas.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-KE8jyaemjH" href="#c-KE8jyaemjH" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">squareWorker</span> = <span class="tok-keyword">new</span> Worker(<span class="tok-string">"code/squareworker.js"</span>); -squareWorker.addEventListener(<span class="tok-string">"message"</span>, <span class="tok-definition">event</span> => { - console.log(<span class="tok-string">"El worker respondió:"</span>, event.data); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-yYWAWANAa1" href="#c-yYWAWANAa1" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">squareWorker</span> = <span class="tok-keyword">new</span> Worker(<span class="tok-string">"code/squareworker.js"</span>); +squareWorker.addEventListener(<span class="tok-string">"message"</span>, <span class="tok-definition">evento</span> => { + console.log(<span class="tok-string">"El worker respondió:"</span>, evento.data); }); squareWorker.postMessage(<span class="tok-number">10</span>); squareWorker.postMessage(<span class="tok-number">24</span>);</pre> @@ -402,17 +402,17 @@ <h2 id="timeline"><a class="h_ident" id="h-h3a3U37DpR" href="#h-h3a3U37DpR" tabi <h2><a class="h_ident" id="h-5Aaz8MIkVH" href="#h-5Aaz8MIkVH" tabindex="-1" role="presentation"></a>Temporizadores</h2> -<p><a class="p_ident" id="p-WV11KfB182" href="#p-WV11KfB182" tabindex="-1" role="presentation"></a>Vimos la función <code>setTimeout</code> en el <a href="11_async.html">Capítulo 11</a>. Programa otra función para que se llame más tarde, después de un cierto número de milisegundos.</p> +<p><a class="p_ident" id="p-o4RsdFMCro" href="#p-o4RsdFMCro" tabindex="-1" role="presentation"></a>La función <code>setTimeout</code> que vimos en el <a href="11_async.html">Capítulo 11</a> programa otra función para que se llame más tarde, después de un cierto número de milisegundos.</p> <p><a class="p_ident" id="p-s28yPKVQrw" href="#p-s28yPKVQrw" tabindex="-1" role="presentation"></a>A veces necesitas cancelar una función que has programado. Esto se hace almacenando el valor devuelto por <code>setTimeout</code> y llamando a <code>clearTimeout</code> sobre él.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-KtFpcnxPz3" href="#c-KtFpcnxPz3" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">bombTimer</span> = setTimeout(() => { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ixzmDqjymJ" href="#c-ixzmDqjymJ" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">temporizadorBomba</span> = setTimeout(() => { console.log(<span class="tok-string">"¡BOOM!"</span>); }, <span class="tok-number">500</span>); <span class="tok-keyword">if</span> (Math.random() < <span class="tok-number">0.5</span>) { <span class="tok-comment">// 50% de probabilidad</span> console.log(<span class="tok-string">"Desactivado."</span>); - clearTimeout(bombTimer); + clearTimeout(temporizadorBomba); }</pre> <p><a class="p_ident" id="p-FuxjnPqka7" href="#p-FuxjnPqka7" tabindex="-1" role="presentation"></a>La función <code>cancelAnimationFrame</code> funciona de la misma manera que <code>clearTimeout</code>; llamarla en un valor devuelto por <code>requestAnimationFrame</code> cancelará ese fotograma (si no se ha llamado ya).</p> @@ -430,19 +430,19 @@ <h2><a class="h_ident" id="h-5Aaz8MIkVH" href="#h-5Aaz8MIkVH" tabindex="-1" role <h2><a class="h_ident" id="h-AOVmaqj10I" href="#h-AOVmaqj10I" tabindex="-1" role="presentation"></a>Debouncing</h2> -<p><a class="p_ident" id="p-5gp2VkgPZ9" href="#p-5gp2VkgPZ9" tabindex="-1" role="presentation"></a>Algunos tipos de eventos pueden activarse rápidamente, muchas veces seguidas (como los eventos <code>"mousemove"</code> y <code>"scroll"</code>, por ejemplo). Al manejar tales eventos, debes tener cuidado de no hacer nada que consuma demasiado tiempo, ya que tu controlador tomará tanto tiempo que la interacción con el documento comenzará a sentirse lenta.</p> +<p><a class="p_ident" id="p-YafxB42U8H" href="#p-YafxB42U8H" tabindex="-1" role="presentation"></a>Algunos tipos de eventos pueden activarse rápidamente, muchas veces seguidas (como los eventos <code>"mousemove"</code> y <code>"scroll"</code>, por ejemplo). Al manejar tales eventos, debes tener cuidado de no hacer nada que consuma demasiado tiempo, ya que tu manejador tomará tanto tiempo que la interacción con el documento comenzará a percibirse como lenta.</p> -<p><a class="p_ident" id="p-7uuS+8eiFw" href="#p-7uuS+8eiFw" tabindex="-1" role="presentation"></a>Si necesitas hacer algo importante en un controlador de este tipo, puedes usar <code>setTimeout</code> para asegurarte de que no lo estás haciendo con demasiada frecuencia. Esto suele llamarse <em>debouncing</em> el evento. Hay varios enfoques ligeramente diferentes para esto.</p> +<p><a class="p_ident" id="p-7uuS+8eiFw" href="#p-7uuS+8eiFw" tabindex="-1" role="presentation"></a>Si necesitas hacer algo importante en un manejador de este tipo, puedes usar <code>setTimeout</code> para asegurarte de que no lo estás haciendo con demasiada frecuencia. Esto suele llamarse limitación (o <em>debouncing</em>, en inglés) del evento. Hay varios enfoques ligeramente diferentes para esto.</p> <p><a class="p_ident" id="p-+/dVlFxtVJ" href="#p-+/dVlFxtVJ" tabindex="-1" role="presentation"></a>En el primer ejemplo, queremos reaccionar cuando el usuario ha escrito algo, pero no queremos hacerlo inmediatamente para cada evento de entrada. Cuando están escribiendo rápidamente, solo queremos esperar hasta que ocurra una pausa. En lugar de realizar inmediatamente una acción en el controlador de eventos, establecemos un tiempo de espera. También limpiamos el tiempo de espera anterior (si existe) para que cuando los eventos ocurran cerca uno del otro (más cerca de nuestro retraso de tiempo de espera), el tiempo de espera del evento anterior se cancele.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-3gKPMnlx9a" href="#c-3gKPMnlx9a" tabindex="-1" role="presentation"></a><<span class="tok-typeName">textarea</span>>Escribe algo aquí...</<span class="tok-typeName">textarea</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-TRSiPoBEGD" href="#c-TRSiPoBEGD" tabindex="-1" role="presentation"></a><<span class="tok-typeName">textarea</span>>Escribe algo aquí...</<span class="tok-typeName">textarea</span>> <<span class="tok-typeName">script</span>> <span class="tok-keyword">let</span> <span class="tok-definition">textarea</span> = document.querySelector(<span class="tok-string">"textarea"</span>); - <span class="tok-keyword">let</span> <span class="tok-definition">timeout</span>; + <span class="tok-keyword">let</span> <span class="tok-definition">espera</span>; textarea.addEventListener(<span class="tok-string">"input"</span>, () => { - clearTimeout(timeout); - timeout = setTimeout(() => console.log(<span class="tok-string">"¡Escrito!"</span>), <span class="tok-number">500</span>); + clearTimeout(espera); + espera = setTimeout(() => console.log(<span class="tok-string">"¡Escrito!"</span>), <span class="tok-number">500</span>); }); </<span class="tok-typeName">script</span>></pre> @@ -450,9 +450,9 @@ <h2><a class="h_ident" id="h-AOVmaqj10I" href="#h-AOVmaqj10I" tabindex="-1" role <p><a class="p_ident" id="p-XG0GpvZAFO" href="#p-XG0GpvZAFO" tabindex="-1" role="presentation"></a>Podemos usar un patrón ligeramente diferente si queremos espaciar las respuestas para que estén separadas por al menos una cierta longitud de tiempo, pero queremos activarlas <em>durante</em> una serie de eventos, no solo después. Por ejemplo, podríamos querer responder a eventos <code>"mousemove"</code> mostrando las coordenadas actuales del mouse pero solo cada 250 milisegundos.</p> -<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-iNs9SdbBE1" href="#c-iNs9SdbBE1" tabindex="-1" role="presentation"></a><<span class="tok-typeName">script</span>> +<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-RIoFhPnId3" href="#c-RIoFhPnId3" tabindex="-1" role="presentation"></a><<span class="tok-typeName">script</span>> <span class="tok-keyword">let</span> <span class="tok-definition">programado</span> = <span class="tok-keyword">null</span>; - window.addEventListener(<span class="tok-string">"mousemove"</span>, <span class="tok-definition">event</span> => { + window.addEventListener(<span class="tok-string">"mousemove"</span>, <span class="tok-definition">evento</span> => { <span class="tok-keyword">if</span> (!programado) { setTimeout(() => { document.body.textContent = @@ -460,17 +460,17 @@ <h2><a class="h_ident" id="h-AOVmaqj10I" href="#h-AOVmaqj10I" tabindex="-1" role programado = <span class="tok-keyword">null</span>; }, <span class="tok-number">250</span>); } - programado = event; + programado = evento; }); </<span class="tok-typeName">script</span>></pre> <h2><a class="h_ident" id="h-NUFOUyK+lw" href="#h-NUFOUyK+lw" tabindex="-1" role="presentation"></a>Resumen</h2> -<p><a class="p_ident" id="p-DHLQ6WkIIW" href="#p-DHLQ6WkIIW" tabindex="-1" role="presentation"></a>Los controladores de eventos hacen posible detectar y reaccionar a eventos que ocurren en nuestra página web. El método <code>addEventListener</code> se utiliza para registrar dicho controlador.</p> +<p><a class="p_ident" id="p-Etnpwznq9T" href="#p-Etnpwznq9T" tabindex="-1" role="presentation"></a>Los manejadores de eventos hacen posible detectar y reaccionar a eventos que ocurren en nuestra página web. El método <code>addEventListener</code> se utiliza para registrar dicho manejador.</p> -<p><a class="p_ident" id="p-BqgD0KonPh" href="#p-BqgD0KonPh" tabindex="-1" role="presentation"></a>Cada evento tiene un tipo (<code>"keydown"</code>, <code>"focus"</code>, y así sucesivamente) que lo identifica. La mayoría de los eventos se activan en un elemento DOM específico y luego se <em>propagan</em> a los ancestros de ese elemento, lo que permite que los controladores asociados a esos elementos los manejen.</p> +<p><a class="p_ident" id="p-BqgD0KonPh" href="#p-BqgD0KonPh" tabindex="-1" role="presentation"></a>Cada evento tiene un tipo (<code>"keydown"</code>, <code>"focus"</code>, etc) que lo identifica. La mayoría de los eventos se activan en un elemento DOM específico y luego se <em>propagan</em> a los ancestros de ese elemento, lo que permite que los manejadores asociados a esos elementos los manejen.</p> -<p><a class="p_ident" id="p-h/n+rs4rlN" href="#p-h/n+rs4rlN" tabindex="-1" role="presentation"></a>Cuando se llama a un controlador de eventos, se le pasa un objeto de evento con información adicional sobre el evento. Este objeto también tiene métodos que nos permiten detener una mayor propagación (<code>stopPropagation</code>) y evitar el manejo predeterminado del evento por parte del navegador (<code>preventDefault</code>).</p> +<p><a class="p_ident" id="p-h/n+rs4rlN" href="#p-h/n+rs4rlN" tabindex="-1" role="presentation"></a>Cuando se llama a un manejador de eventos, se le pasa un objeto de evento con información adicional sobre el evento. Este objeto también tiene métodos que nos permiten detener una mayor propagación (<code>stopPropagation</code>) y evitar el manejo predeterminado del evento por parte del navegador (<code>preventDefault</code>).</p> <p><a class="p_ident" id="p-AyW1QVWdzT" href="#p-AyW1QVWdzT" tabindex="-1" role="presentation"></a>Presionar una tecla dispara eventos <code>"keydown"</code> y <code>"keyup"</code>. Presionar un botón del mouse dispara eventos <code>"mousedown"</code>, <code>"mouseup"</code> y <code>"click"</code>. Mover el mouse dispara eventos <code>"mousemove"</code>. La interacción con pantallas táctiles dará lugar a eventos <code>"touchstart"</code>, <code>"touchmove"</code> y <code>"touchend"</code>.</p> @@ -482,7 +482,7 @@ <h3><a class="i_ident" id="i-NaqVfjsbAA" href="#i-NaqVfjsbAA" tabindex="-1" role <p><a class="p_ident" id="p-b3Hqo7husK" href="#p-b3Hqo7husK" tabindex="-1" role="presentation"></a>Escribe una página que muestre un globo (usando el emoji de globo, 🎈). Cuando presiones la flecha hacia arriba, debería inflarse (crecer) un 10 por ciento, y cuando presiones la flecha hacia abajo, debería desinflarse (encoger) un 10 por ciento.</p> -<p><a class="p_ident" id="p-IEP4Bro5Ql" href="#p-IEP4Bro5Ql" tabindex="-1" role="presentation"></a>Puedes controlar el tamaño del texto (los emoji son texto) estableciendo la propiedad CSS <code>font-size</code> (<code>style.fontSize</code>) en su elemento padre. Recuerda incluir una unidad en el valor, por ejemplo, píxeles (<code>10px</code>).</p> +<p><a class="p_ident" id="p-IEP4Bro5Ql" href="#p-IEP4Bro5Ql" tabindex="-1" role="presentation"></a>Puedes controlar el tamaño del texto (los emoji son texto) estableciendo la propiedad CSS <code>font-size</code> (<code>style.fontSize</code>) en su elemento padre. Recuerda incluir las unidades en el valor, por ejemplo, píxeles (<code>10px</code>).</p> <p><a class="p_ident" id="p-SyLfo0cRkh" href="#p-SyLfo0cRkh" tabindex="-1" role="presentation"></a>Los nombres de las teclas de flecha son <code>"ArrowUp"</code> y <code>"ArrowDown"</code>. Asegúrate de que las teclas cambien solo el globo, sin hacer scroll en la página.</p> @@ -496,9 +496,9 @@ <h3><a class="i_ident" id="i-NaqVfjsbAA" href="#i-NaqVfjsbAA" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-GvD67qYWif" href="#p-GvD67qYWif" tabindex="-1" role="presentation"></a>Querrás registrar un manejador para el evento <code>"keydown"</code> y mirar <code>event.key</code> para saber si se presionó la tecla de flecha hacia arriba o hacia abajo.</p> +<p><a class="p_ident" id="p-hk0ZoK3F3l" href="#p-hk0ZoK3F3l" tabindex="-1" role="presentation"></a>Tendrás que registrar un manejador para el evento <code>"keydown"</code> y mirar <code>event.key</code> para saber si se presionó la tecla de flecha hacia arriba o hacia abajo.</p> -<p><a class="p_ident" id="p-FChVTGCUI7" href="#p-FChVTGCUI7" tabindex="-1" role="presentation"></a>El tamaño actual se puede mantener en un enlace para que puedas basarte en él para el nuevo tamaño. Será útil definir una función que actualice el tamaño, tanto el enlace como el estilo del globo en el DOM, para que puedas llamarla desde tu manejador de eventos, y posiblemente también una vez al inicio, para establecer el tamaño inicial.</p> +<p><a class="p_ident" id="p-FChVTGCUI7" href="#p-FChVTGCUI7" tabindex="-1" role="presentation"></a>El tamaño actual se puede mantener en una variable para que puedas basarte en ella para el nuevo tamaño. Será útil definir una función que actualice el tamaño —tanto la variable como el estilo del globo en el DOM— para que puedas llamarla desde tu manejador de eventos, y posiblemente también una vez al inicio, para establecer el tamaño inicial.</p> <p><a class="p_ident" id="p-CMyziBQORi" href="#p-CMyziBQORi" tabindex="-1" role="presentation"></a>Puedes cambiar el globo por una explosión reemplazando el nodo de texto por otro (usando <code>replaceChild</code>) o estableciendo la propiedad <code>textContent</code> de su nodo padre en una nueva cadena.</p> @@ -510,7 +510,7 @@ <h3><a class="i_ident" id="i-TacKwba7GU" href="#i-TacKwba7GU" tabindex="-1" role <p><a class="p_ident" id="p-SIIG63wuy4" href="#p-SIIG63wuy4" tabindex="-1" role="presentation"></a>Una de estas era la <em>estela del ratón</em> —una serie de elementos que seguirían al puntero del ratón mientras lo movías por la página.</p> -<p><a class="p_ident" id="p-o0XuQJDBDN" href="#p-o0XuQJDBDN" tabindex="-1" role="presentation"></a>En este ejercicio, quiero que implementes una estela del ratón. Utiliza elementos <code><div></code> con posición absoluta y un tamaño fijo y color de fondo (consulta el <a href="15_event.html#mouse_drawing">código</a> en la sección de “Clics de ratón” para un ejemplo). Crea un montón de estos elementos y, al mover el ratón, muéstralos en la estela del puntero del ratón.</p> +<p><a class="p_ident" id="p-o0XuQJDBDN" href="#p-o0XuQJDBDN" tabindex="-1" role="presentation"></a>En este ejercicio, quiero que implementes una estela del ratón. Utiliza elementos <code><div></code> con posición absoluta y un tamaño fijo y color de fondo (consulta el <a href="15_event.html#dibujo_con_rat%C3%B3n">código</a> en la sección de “Clics de ratón” para un ejemplo). Crea un montón de estos elementos y, al mover el ratón, muéstralos en la estela del puntero del ratón.</p> <p><a class="p_ident" id="p-bPEwHFdsMX" href="#p-bPEwHFdsMX" tabindex="-1" role="presentation"></a>Hay varias aproximaciones posibles aquí. Puedes hacer tu solución tan simple o tan compleja como desees. Una solución simple para empezar es mantener un número fijo de elementos de estela y recorrerlos, moviendo el siguiente a la posición actual del ratón cada vez que ocurra un evento <code>"mousemove"</code>.</p> @@ -533,11 +533,11 @@ <h3><a class="i_ident" id="i-TacKwba7GU" href="#i-TacKwba7GU" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-4AFLKpPOlK" href="#p-4AFLKpPOlK" tabindex="-1" role="presentation"></a>Crear los elementos es mejor hacerlo con un bucle. Adjúntalos al documento para que aparezcan. Para poder acceder a ellos más tarde y cambiar su posición, querrás almacenar los elementos en un array.</p> +<p><a class="p_ident" id="p-wtT6dYme/v" href="#p-wtT6dYme/v" tabindex="-1" role="presentation"></a>Para crear los elementos lo mejor es hacerlo con un bucle. Adjúntalos al documento para que aparezcan. Para poder acceder a ellos más tarde y cambiar su posición, tendrás que almacenar los elementos en un array.</p> -<p><a class="p_ident" id="p-O7Vg4b4/gp" href="#p-O7Vg4b4/gp" tabindex="-1" role="presentation"></a>Recorrerlos se puede hacer manteniendo una variable de contador y sumándole 1 cada vez que se dispare el evento <code>"mousemove"</code>. Luego se puede usar el operador de resto (<code>% elementos.<wbr>length</code>) para obtener un índice de array válido para elegir el elemento que deseas posicionar durante un evento dado.</p> +<p><a class="p_ident" id="p-GN6/P07OXe" href="#p-GN6/P07OXe" tabindex="-1" role="presentation"></a>Puedes recorrerlos manteniendo una variable de contador y sumándole 1 cada vez que se dispare el evento <code>"mousemove"</code>. Luego se puede usar el operador de resto (<code>% elementos.<wbr>length</code>) para obtener un índice de array válido para elegir el elemento que deseas posicionar durante un evento dado.</p> -<p><a class="p_ident" id="p-sJ/RGv4SnV" href="#p-sJ/RGv4SnV" tabindex="-1" role="presentation"></a>Otro efecto interesante se puede lograr modelando un simple sistema de física. Usa el evento <code>"mousemove"</code> solo para actualizar un par de enlaces que siguen la posición del ratón. Luego utiliza <code>requestAnimationFrame</code> para simular que los elementos rastreadores son atraídos a la posición del puntero del ratón. En cada paso de animación, actualiza su posición basándote en su posición relativa al puntero (y, opcionalmente, una velocidad que está almacenada para cada elemento). Descubrir una buena forma de hacer esto queda a tu cargo.</p> +<p><a class="p_ident" id="p-YKNUV599Bt" href="#p-YKNUV599Bt" tabindex="-1" role="presentation"></a>Otro efecto interesante se puede lograr modelando un simple sistema de física. Usa el evento <code>"mousemove"</code> solo para actualizar un par de enlaces que siguen la posición del ratón. Luego utiliza <code>requestAnimationFrame</code> para simular que los elementos rastreadores son atraídos a la posición del puntero del ratón. En cada paso de animación, actualiza su posición basándote en su posición relativa al puntero (y, opcionalmente, una velocidad que está almacenada para cada elemento). En tu mano está el descubrir una buena forma de hacer esto.</p> </div></details> @@ -547,7 +547,7 @@ <h3><a class="i_ident" id="i-FBlz6I/AHB" href="#i-FBlz6I/AHB" tabindex="-1" role <p><a class="p_ident" id="p-oCfJg8pvnS" href="#p-oCfJg8pvnS" tabindex="-1" role="presentation"></a>En este ejercicio debes implementar una interfaz de pestañas simple. Escribe una función, <code>asTabs</code>, que tome un nodo DOM y cree una interfaz de pestañas que muestre los elementos secundarios de ese nodo. Debería insertar una lista de elementos <code><button></code> en la parte superior del nodo, uno por cada elemento secundario, conteniendo el texto recuperado del atributo <code>data-tabname</code> del hijo. Todos los hijos originales excepto uno deben estar ocultos (con un estilo <code>display</code> de <code>none</code>). El nodo actualmente visible se puede seleccionar haciendo clic en los botones.</p> -<p><a class="p_ident" id="p-A5yNkNAmF9" href="#p-A5yNkNAmF9" tabindex="-1" role="presentation"></a>Cuando funcione, extiéndelo para dar estilo al botón de la pestaña actualmente seleccionada de manera diferente para que sea obvio cuál pestaña está seleccionada.</p> +<p><a class="p_ident" id="p-A5yNkNAmF9" href="#p-A5yNkNAmF9" tabindex="-1" role="presentation"></a>Cuando funcione, extiéndelo para dar estilo al botón de la pestaña actualmente seleccionada de manera diferente para que sea obvio qué pestaña está seleccionada.</p> <pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-IPOlbViOlf" href="#c-IPOlbViOlf" tabindex="-1" role="presentation"></a><<span class="tok-typeName">tab-panel</span>> <<span class="tok-typeName">div</span> data-tabname=<span class="tok-string">"one"</span>>Pestaña uno</<span class="tok-typeName">div</span>> From 68c516121fe928c0e38fd55dd10b0bfa491cc6d0 Mon Sep 17 00:00:00 2001 From: ckdvk <javicemarpe@gmail.com> Date: Sun, 23 Feb 2025 02:54:08 +0800 Subject: [PATCH 27/36] =?UTF-8?q?revisado=20cap=C3=ADtulo=2016.=20Cambiado?= =?UTF-8?q?=20nombre=20de=20los=20cap=C3=ADtulos=20de=20proyecto=20en=20to?= =?UTF-8?q?dos=20los=20cap=C3=ADtulos=20de=20este=20estilo.=20Arreglado=20?= =?UTF-8?q?el=20fichero=2016=5Fgame.md=20para=20que=20el=20c=C3=B3digo=20f?= =?UTF-8?q?uncione.=20Faltaba=20el=20meta=20del=20comienzo,=20que,=20adem?= =?UTF-8?q?=C3=A1s,=20parece=20tambi=C3=A9n=20estar=20mal=20escrito=20en?= =?UTF-8?q?=20el=20original=20del=20autor=20(en=20github).=20Ahora=20el=20?= =?UTF-8?q?c=C3=B3digo=20se=20ejecuta=20sin=20problemas,=20ya=20que=20los?= =?UTF-8?q?=20ficheros=20html=20pueden=20generarse=20correctamente.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 07_robot.md | 2 +- 12_language.md | 2 +- 16_game.md | 146 +++++++++++++++++++------------------ 19_paint.md | 2 +- 21_skillsharing.md | 4 +- html/16_game.html | 149 +++++++++++++++++++------------------- html/20_node.html | 2 +- html/21_skillsharing.html | 2 +- 8 files changed, 156 insertions(+), 153 deletions(-) diff --git a/07_robot.md b/07_robot.md index 93bf8585..13695496 100644 --- a/07_robot.md +++ b/07_robot.md @@ -11,7 +11,7 @@ quote}} {{figure {url: "img/chapter_picture_7.jpg", alt: "Ilustración de un robot sosteniendo una pila de paquetes", chapter: framed}}} -{{index "capítulo del proyecto", "leyendo código", "escribiendo código"}} +{{index "capítulo de proyecto", "leyendo código", "escribiendo código"}} En los capítulos "proyecto", dejaré de bombardearte con nueva teoría por un momento y, en su lugar, trabajaremos juntos en un programa. La teoría es necesaria para aprender a programar, pero leer y entender programas reales es igual de importante. diff --git a/12_language.md b/12_language.md index f9e2e6bd..ddccefac 100644 --- a/12_language.md +++ b/12_language.md @@ -8,7 +8,7 @@ El evaluador, que determina el significado de expresiones en un lenguaje de prog quote}} -{{index "Abelson, Hal", "Sussman, Gerald", SICP, "capítulo del proyecto"}} +{{index "Abelson, Hal", "Sussman, Gerald", SICP, "capítulo de proyecto"}} {{figure {url: "img/chapter_picture_12.jpg", alt: "Ilustración que muestra un huevo con agujeros, mostrando huevos más pequeños dentro, que a su vez tienen huevos aún más pequeños dentro de ellos, y así sucesivamente", chapter: "framed"}}} diff --git a/16_game.md b/16_game.md index 17bab486..ac74cfe7 100644 --- a/16_game.md +++ b/16_game.md @@ -1,3 +1,5 @@ +{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js", "code/stop_keys.js"], zip: "html", include: ["css/game.css"]}}} + # Proyecto: Un juego de plataformas {{quote {author: "Iain Banks", title: "The Player of Games", chapter: true} @@ -6,29 +8,29 @@ Toda la realidad es un juego. quote}} -{{index "Banks, Ian", "capítulo del proyecto", "simulación"}} +{{index "Banks, Ian", "capítulo de proyecto", "simulación"}} {{figure {url: "img/chapter_picture_16.jpg", alt: "Ilustración que muestra un personaje de un juego de computadora saltando sobre lava en un mundo bidimensional", chapter: "framed"}}} -Gran parte de mi fascinación inicial con las computadoras, al igual que la de muchos niños nerds, tenía que ver con los ((juegos)) de computadora. Me sentía atraído por los diminutos ((mundos)) simulados que podía manipular y en los que se desarrollaban historias (más o menos), supongo, debido a la forma en que proyectaba mi ((imaginación)) en ellos más que por las posibilidades que realmente ofrecían. +Gran parte de mi fascinación inicial con las computadoras, al igual que la de muchos niños _nerds_, tenía que ver con los ((juegos)) de computadora. Me sentía atraído por los diminutos ((mundos)) simulados que podía manipular y en los que se desarrollaban historias (más o menos), supongo, debido a la forma en que proyectaba mi ((imaginación)) en ellos más que por las posibilidades que realmente ofrecían. -No le desearía a nadie una ((carrera)) en programación de juegos. Al igual que la industria de la ((música)), la discrepancia entre la cantidad de jóvenes entusiastas que desean trabajar en ella y la demanda real de tales personas crea un entorno bastante insalubre. Pero escribir juegos por diversión resulta entretenido. +No le desearía a nadie una ((carrera)) en programación de juegos. Al igual que la industria de la ((música)), la discrepancia entre la cantidad de jóvenes entusiastas que desean trabajar en ella y la demanda real de tales personas crea un entorno bastante insalubre. Pero escribir juegos por diversión resulta ser entretenido. {{index "juego de saltos y carreras", dimensiones}} -Este capítulo guiará a través de la implementación de un pequeño ((juego de plataformas)). Los juegos de plataformas (o juegos de "saltos y carreras") son juegos que esperan que el ((jugador)) mueva una figura a través de un ((mundo)), que generalmente es bidimensional y se ve desde el lado, mientras salta sobre y sobre cosas. +Este capítulo guiará a través de la implementación de un pequeño ((juego de plataformas)). Los juegos de plataformas (o juegos de "saltos y carreras") son juegos que esperan que el ((jugador)) mueva una figura a través de un ((mundo)), que generalmente es bidimensional y se ve desde un lado, mientras salta cosas y sobre cosas. ## El juego {{index minimalismo, "Palef, Thomas", "Dark Blue (juego)"}} -Nuestro ((juego)) estará basado aproximadamente en [Dark Blue](http://www.lessmilk.com/games/10)[ (_www.lessmilk.com/games/10_)]{if book} de Thomas Palef. Elegí ese juego porque es entretenido, minimalista y se puede construir sin mucho ((código)). Se ve así: +Nuestro ((juego)) estará basado más o menos en [Dark Blue](http://www.lessmilk.com/games/10)[ (_www.lessmilk.com/games/10_)]{if book} de Thomas Palef. He elegido ese juego porque es entretenido, minimalista y se puede construir sin mucho ((código)). Tiene esta pinta: -{{figure {url: "img/darkblue.png", alt: "Captura de pantalla del juego 'Dark Blue', mostrando un mundo hecho de cajas de colores. Hay una caja negra que representa al jugador, de pie sobre líneas blancas en un fondo azul. Pequeñas monedas amarillas flotan en el aire, y algunas partes del fondo son rojas, representando lava."}}} +{{figure {url: "img/darkblue.png", alt: "Captura de pantalla del juego 'Dark Blue', mostrando un mundo hecho de cajas de colores. Hay una caja negra que representa al jugador, de pie sobre líneas blancas en un fondo azul. Pequeñas monedas amarillas flotan en el aire, y hay algunas partes rojas en el fondo que representan lava."}}} {{index moneda, lava}} -La caja oscura representa al ((jugador)), cuya tarea es recolectar las cajas amarillas (monedas) evitando las cosas rojas (lava). Un ((nivel)) se completa cuando se han recolectado todas las monedas. +La caja negra representa al ((jugador)), cuya tarea es recolectar las cajas amarillas (monedas) evitando las cosas rojas (lava). Un ((nivel)) se completa cuando se han recolectado todas las monedas. {{index teclado, saltos}} @@ -36,7 +38,7 @@ El jugador puede moverse con las teclas de flecha izquierda y derecha y puede sa {{index "número fraccionario", "discretización", "vida artificial", "vida electrónica"}} -El ((juego)) consiste en un ((fondo)) estático, dispuesto como una ((rejilla)), con los elementos móviles superpuestos en ese fondo. Cada campo en la rejilla está vacío, sólido o es ((lava)). Los elementos móviles son el jugador, las monedas y ciertas piezas de lava. Las posiciones de estos elementos no están restringidas a la rejilla: sus coordenadas pueden ser fraccionarias, permitiendo un ((movimiento)) suave. +El ((juego)) consiste en un ((fondo)) estático, dispuesto como una ((rejilla)), con los elementos móviles superpuestos en ese fondo. Cada campo en la rejilla puede ser vacío, sólido o ((lava)). Los elementos móviles son el jugador, las monedas y ciertas piezas de lava. Las posiciones de estos elementos no están restringidas a la rejilla: sus coordenadas pueden ser fraccionarias, permitiendo un ((movimiento)) suave. ## La tecnología @@ -54,7 +56,7 @@ Podemos representar el fondo como una tabla ya que es una ((cuadrícula)) inmuta {{index rendimiento, [DOM, "gráficos"]}} -En juegos y otros programas que deben animar ((gráficos)) y responder a la ((entrada)) del usuario sin retrasos notables, la ((eficiencia)) es importante. Aunque el DOM no fue diseñado originalmente para gráficos de alto rendimiento, en realidad es mejor en esto de lo que podrías esperar. Viste algunas ((animacione))s en el [Capítulo ?](dom#animacion). En una máquina moderna, un juego simple como este funciona bien, incluso si no nos preocupamos mucho por la ((optimización)). +En juegos y otros programas que deben animar ((gráficos)) y responder a la ((entrada)) del usuario sin retrasos notables, la ((eficiencia)) es importante. Aunque el DOM no fue diseñado originalmente para gráficos de alto rendimiento, en realidad es mejor en esto de lo que podrías esperarte. Viste algunas ((animacione))s en el [Capítulo ?](dom#animation). En una máquina moderna, un juego simple como este funciona bien, incluso si no nos preocupamos mucho por la ((optimización)). {{index lienzo, [DOM, "gráficos"]}} @@ -64,9 +66,9 @@ En el [próximo capítulo](canvas), exploraremos otra tecnología del ((navegado {{index dimensiones}} -Queremos una forma legible y editable por humanos para especificar niveles. Dado que está bien que todo comience en una cuadrícula, podríamos usar cadenas grandes en las que cada carácter represente un elemento, ya sea una parte de la cuadrícula de fondo o un elemento móvil. +Queremos una forma legible y editable por humanos para especificar niveles. Como podemos empezar a construir todo a partir de una cuadrícula, podríamos usar cadenas grandes en las que cada carácter represente un elemento, ya sea una parte de la cuadrícula de fondo o un elemento móvil. -El plan para un nivel pequeño podría verse así: +El plan para un nivel pequeño podría tener este aspecto: ```{includeCode: true} let simpleLevelPlan = ` @@ -87,7 +89,7 @@ Los puntos representan un espacio vacío, los caracteres de almohadilla (`#`) so {{index rebotar}} -Además de las dos formas adicionales de lava en movimiento, el carácter de tubería (`|`) crea blobs que se mueven verticalmente, y `v` indica lava goteante: lava que se mueve verticalmente y no rebota de un lado a otro, solo se mueve hacia abajo, volviendo a su posición de inicio cuando golpea el suelo. +Además, vamos a admitir dos formas más de lava en movimiento: el carácter de barra vertical (`|`) crea gotas que se mueven verticalmente, y `v` indica lava goteante: lava que se mueve verticalmente y no rebota de un lado a otro, solo se mueve hacia abajo, volviendo a su posición de inicio cuando golpea el suelo. Un ((juego)) completo consta de varios ((nivel))es que el ((jugador)) debe completar. Un nivel se completa cuando se han recolectado todas las ((moneda))s. Si el jugador toca la ((lava)), el nivel actual se restablece a su posición inicial y el jugador puede intentarlo de nuevo. @@ -124,11 +126,11 @@ class Level { {{index "método trim", "método split", [espacios en blanco, recorte]}} -El método `trim` se utiliza para eliminar los espacios en blanco al principio y al final de la cadena de plan. Esto permite que nuestro plan de ejemplo comience con una nueva línea para que todas las líneas estén directamente debajo unas de otras. La cadena restante se divide en líneas en ((caracteres de nueva línea)), y cada línea se convierte en un array, produciendo arrays de caracteres. +El método `trim` se utiliza para eliminar los espacios en blanco al principio y al final de la cadena de `plan`. Esto permite que nuestro plan de ejemplo comience con una nueva línea para que todas las líneas estén directamente debajo unas de otras. La cadena restante se divide en líneas en ((caracteres de nueva línea)), y cada línea se convierte en un array, produciendo arrays de caracteres. {{index [array, "como matriz"]}} -Entonces, `rows` contiene un array de arrays de caracteres, las filas del plan. Podemos derivar el ancho y alto del nivel a partir de estos. Pero aún debemos separar los elementos móviles de la cuadrícula de fondo. Llamaremos a los elementos móviles _actores_. Se almacenarán en un array de objetos. El fondo será un array de arrays de cadenas, que contienen tipos de campo como `"empty"`, `"wall"`, o `"lava"`. +Entonces, `rows` contiene un array de arrays de caracteres, las filas del plan. Podemos obtener el ancho y alto del nivel a partir de estos. Pero aún debemos separar los elementos móviles de la cuadrícula de fondo. Llamaremos a los elementos móviles _actores_. Se almacenarán en un array de objetos. El fondo será un array de arrays de cadenas, que contienen tipos de campo como `"empty"`, `"wall"`, o `"lava"`. {{index "método map"}} @@ -166,7 +168,7 @@ class State { La propiedad `status` cambiará a `"lost"` o `"won"` cuando el juego haya terminado. -Este es nuevamente una estructura de datos persistente: actualizar el estado del juego crea un nuevo estado y deja intacto el anterior. +Esta es nuevamente una estructura de datos persistente: actualizar el estado del juego crea un nuevo estado y deja intacto el anterior. ## Actores @@ -228,11 +230,11 @@ Player.prototype.size = new Vec(0.8, 1.5); Dado que un jugador tiene una altura de un cuadro y medio, su posición inicial se establece medio cuadro por encima de la posición donde apareció el carácter `@`. De esta manera, su parte inferior se alinea con la parte inferior del cuadro en el que apareció. -La propiedad `size` es la misma para todas las instancias de `Player`, por lo que la almacenamos en el prototipo en lugar de en las propias instancias. Podríamos haber utilizado un ((getter)) como `type`, pero eso crearía y devolvería un nuevo objeto `Vec` cada vez que se lee la propiedad, lo cual sería derrochador. (Las cadenas, al ser ((inmutables)), no tienen que ser recreadas cada vez que se evalúan). +La propiedad `size` es la misma para todas las instancias de `Player`, por lo que la almacenamos en el prototipo en lugar de en las propias instancias. Podríamos haber utilizado un ((getter)) como `type`, pero eso crearía y devolvería un nuevo objeto `Vec` cada vez que se lee la propiedad, lo cual sería derrochador (las cadenas, al ser ((inmutables)), no tienen que ser recreadas cada vez que se evalúan). {{index "Clase Lava", "rebotando"}} -Al construir un actor `Lava`, necesitamos inicializar el objeto de manera diferente dependiendo del personaje en el que se base. La lava dinámica se mueve a lo largo de su velocidad actual hasta que choca con un obstáculo. En ese momento, si tiene una propiedad de `reset`, saltará de nuevo a su posición de inicio (goteando). Si no la tiene, invertirá su velocidad y continuará en la otra dirección (rebotando). +Al construir un actor `Lava`, necesitamos inicializar el objeto de manera diferente dependiendo del personaje en el que se base. La lava dinámica se mueve a lo largo de su velocidad actual hasta que choca con un obstáculo. En ese momento, si tiene una propiedad de `reset`, saltará de nuevo a su posición de inicio (esto sirve para el efecto de goteo). Si no la tiene, invertirá su velocidad y continuará en la otra dirección (rebotando). El método `create` mira el carácter que pasa el constructor de `Level` y crea el actor de lava apropiado. @@ -286,11 +288,11 @@ Coin.prototype.size = new Vec(0.6, 0.6); {{index "Función Math.random", "número aleatorio", "Función Math.sin", seno, onda}} -En [Capítulo ?](dom#sin_cos), vimos que `Math.sin` nos da la coordenada y de un punto en un círculo. Esa coordenada va de ida y vuelta en una forma de onda suave a medida que nos movemos a lo largo del círculo, lo que hace que la función seno sea útil para modelar un movimiento ondulado. +En [Capítulo ?](dom#sin_cos), vimos que `Math.sin` nos da la coordenada y de un punto en un círculo. Esa coordenada va de ida y vuelta en una forma de onda suave a medida que nos movemos a lo largo del círculo, lo que hace que la función seno sea útil para modelar un movimiento de vaivén. {{index pi}} -Para evitar una situación en la que todas las monedas se mueven hacia arriba y hacia abajo sincrónicamente, la fase inicial de cada moneda se aleatoriza. El periodo de la onda de `Math.sin`, el ancho de una onda que produce, es 2π. Multiplicamos el valor devuelto por `Math.random` por ese número para darle a la moneda una posición inicial aleatoria en la onda. +Para evitar una situación en la que todas las monedas se mueven hacia arriba y hacia abajo sincrónicamente, la fase inicial de cada moneda se aleatoriza. El periodo de la onda de `Math.sin`, el ancho de una onda que produce, es 2π. Multiplicamos el valor devuelto por `Math.random` por ese número para darle a la moneda una posición inicial aleatoria en el vaivén. {{index map, [objeto, "como mapa"]}} @@ -308,11 +310,11 @@ Esto nos brinda todas las partes necesarias para crear una instancia de `Level`. ```{includeCode: strip_log} let simpleLevel = new Level(simpleLevelPlan); -console.log(`${simpleLevel.width} by ${simpleLevel.height}`); -// → 22 by 9 +console.log(`${simpleLevel.width} por ${simpleLevel.height}`); +// → 22 por 9 ``` -La tarea por delante es mostrar esos niveles en pantalla y modelar el tiempo y movimiento dentro de ellos. +Ahora toca mostrar esos niveles en pantalla y modelar el tiempo y movimiento dentro de ellos. {{id domdisplay}} @@ -326,15 +328,15 @@ Un objeto de visualización de juego dibuja un nivel y estado dados. Pasamos su {{index "atributo de estilo", CSS}} -Utilizaremos una hoja de estilo para establecer los colores reales y otras propiedades fijas de los elementos que conforman el juego. También sería posible asignar directamente a la propiedad `style` de los elementos al crearlos, pero eso produciría programas más verbosos. +Utilizaremos una hoja de estilo para establecer los colores y otras propiedades fijas de los elementos que conforman el juego. También sería posible asignarlos directamente a la propiedad `style` de los elementos al crearlos, pero eso produciría programas más verbosos. {{index "atributo de clase"}} La siguiente función auxiliar proporciona una forma concisa de crear un elemento y darle algunos atributos y nodos secundarios: ```{includeCode: true} -function elt(nombre, attrs, ...children) { - let dom = document.createElement(nombre); +function elt(name, attrs, ...children) { + let dom = document.createElement(name); for (let attr of Object.keys(attrs)) { dom.setAttribute(attr, attrs[attr]); } @@ -349,10 +351,10 @@ Una visualización se crea dándole un elemento padre al que debe adjuntarse y u ```{includeCode: true} class DOMDisplay { - constructor(padre, nivel) { - this.dom = elt("div", {class: "game"}, dibujarGrid(nivel)); + constructor(parent, level) { + this.dom = elt("div", {class: "game"}, drawGrid(level)); this.actorLayer = null; - padre.appendChild(this.dom); + parent.appendChild(this.dom); } clear() { this.dom.remove(); } @@ -365,25 +367,25 @@ La cuadrícula de fondo del nivel, que nunca cambia, se dibuja una vez. Los acto {{index scaling, "Clase DOMDisplay"}} -Nuestras coordenadas y tamaños se rastrean en unidades de cuadrícula, donde un tamaño o distancia de 1 significa un bloque de cuadrícula. Al establecer tamaños de píxeles, tendremos que escalar estas coordenadas: todo en el juego sería ridículamente pequeño con un solo píxel por cuadrado. La constante `scale` indica el número de píxeles que una unidad ocupa en la pantalla. +Nuestras coordenadas y tamaños se miden en unidades de cuadrícula, donde un tamaño o distancia de 1 significa un bloque de cuadrícula. Al establecer tamaños de píxeles, tendremos que escalar estas coordenadas: todo en el juego sería ridículamente pequeño con un solo píxel por cuadrado. La constante `scale` indica el número de píxeles que una unidad ocupa en la pantalla. ```{includeCode: true} -const escala = 20; +const scale = 20; -function dibujarGrid(nivel) { +function drawGrid(level) { return elt("table", { class: "background", - style: `width: ${nivel.width * escala}px` - }, ...nivel.rows.map(fila => - elt("tr", {style: `height: ${escala}px`}, - ...fila.map(tipo => elt("td", {class: tipo}))) + style: `width: ${level.width * scale}px` + }, ...level.rows.map(row => + elt("tr", {style: `height: ${scale}px`}, + ...row.map(type => elt("td", {class: type}))) )); } ``` {{index "table (etiqueta HTML)", "tr (etiqueta HTML)", "td (etiqueta HTML)", "operador de propagación"}} -El elemento `<table>` se corresponde bien con la estructura de la propiedad `rows` del nivel: cada fila de la cuadrícula se convierte en una fila de tabla (`<tr>`). Las cadenas en la cuadrícula se usan como nombres de clase para los elementos de celda de tabla (`<td>`). El código utiliza el operador de propagación (triple punto) para pasar matrices de nodos secundarios a `elt` como argumentos separados.El siguiente ((CSS)) hace que la tabla se vea como el fondo que queremos: +El elemento `<table>` se corresponde bien con la estructura de la propiedad `rows` del nivel: cada fila de la cuadrícula se convierte en una fila de tabla (`<tr>`). Las cadenas en la cuadrícula se usan como nombres de clase para los elementos de celda de tabla (`<td>`). El código utiliza el operador de propagación (triple punto) para pasar arrays de nodos secundarios a `elt` como argumentos separados.El siguiente ((CSS)) hace que la tabla se vea como el fondo que queremos: ```{lang: "css"} .background { background: rgb(52, 166, 251); @@ -398,7 +400,7 @@ Algunos de estos (`table-layout`, `border-spacing` y `padding`) se utilizan para La regla `background` establece el color de fondo. CSS permite que los colores se especifiquen tanto como palabras (`white`) como con un formato como `rgb(R, G, B)`, donde los componentes rojo, verde y azul del color se separan en tres números de 0 a 255. Por lo tanto, en `rgb(52, 166, 251)`, el componente rojo es 52, el verde es 166 y el azul es 251. Dado que el componente azul es el más grande, el color resultante será azulado. En la regla `.lava`, el primer número (rojo) es el más grande. -Dibujamos cada ((actor)) creando un elemento DOM para él y estableciendo la posición y el tamaño de ese elemento en función de las propiedades del actor. Los valores tienen que ser multiplicados por `scale` para pasar de unidades de juego a píxeles. +Dibujamos cada ((actor)) creando un elemento DOM para él y estableciendo la posición y el tamaño de ese elemento en función de las propiedades del actor. Los valores tienen que ser multiplicados por `scale` para pasar de unidades del juego a píxeles. ```{includeCode: true} function drawActors(actors) { @@ -413,7 +415,7 @@ function drawActors(actors) { } ``` -Para agregar más de una clase a un elemento, separamos los nombres de las clases por espacios. En el siguiente código ((CSS)) mostrado a continuación, la clase `actor` da a los actores su posición absoluta. El nombre de su tipo se utiliza como una clase adicional para darles un color. No tenemos que definir la clase `lava` de nuevo porque estamos reutilizando la clase para las casillas de lava de la cuadrícula que definimos anteriormente. +Para agregar más de una clase a un elemento, separamos los nombres de las clases por espacios. En el siguiente código ((CSS)), la clase `actor` da a los actores su posición absoluta. El nombre de su tipo se utiliza como una clase adicional para darles un color. No tenemos que definir la clase `lava` de nuevo porque estamos reutilizando la clase para las casillas de lava de la cuadrícula que definimos anteriormente. ```{lang: "css"} .actor { position: absolute; } @@ -421,7 +423,7 @@ Para agregar más de una clase a un elemento, separamos los nombres de las clase .player { background: rgb(64, 64, 64); } ``` -El método `syncState` se utiliza para que la pantalla muestre un estado dado. Primero elimina los gráficos de actores antiguos, si los hay, y luego vuelve a dibujar los actores en sus nuevas posiciones. Puede ser tentador intentar reutilizar los elementos DOM para actores, pero para que eso funcione, necesitaríamos mucho más trabajo adicional para asociar actores con elementos DOM y asegurarnos de que eliminamos elementos cuando sus actores desaparecen. Dado que típicamente habrá solo un puñado de actores en el juego, volver a dibujar todos ellos no es costoso. +El método `syncState` se utiliza para que la pantalla muestre un estado dado. Primero elimina los gráficos de actores antiguos, si los hay, y luego vuelve a dibujar los actores en sus nuevas posiciones. Puede ser tentador intentar reutilizar los elementos DOM para actores, pero para que eso funcione, necesitaríamos mucho más trabajo adicional para asociar actores con elementos DOM y asegurarnos de que eliminamos elementos cuando sus actores desaparecen. Como normalmente habrá solo un puñado de actores en el juego, volver a dibujar todos ellos no resulta costoso. ```{includeCode: true} DOMDisplay.prototype.syncState = function(state) { @@ -528,7 +530,7 @@ if}} {{index "enlace (etiqueta HTML)", CSS}} -La etiqueta `<link>`, cuando se utiliza con `rel="stylesheet"`, es una forma de cargar un archivo CSS en una página. El archivo `game.css` contiene los estilos necesarios para nuestro juego. +La etiqueta `<link>`, cuando se utiliza con `rel="stylesheet"`, proporciona una forma de cargar un archivo CSS en una página. El archivo `game.css` contiene los estilos necesarios para nuestro juego. ## Movimiento y colisión @@ -538,17 +540,17 @@ Ahora estamos en el punto en el que podemos comenzar a agregar movimiento. El en {{index "obstáculo", "detección de colisión"}} -Mover cosas es fácil. La parte difícil es lidiar con las interacciones entre los elementos. Cuando el jugador golpea una pared o el suelo, no debería simplemente atravesarlo. El juego debe notar cuando un movimiento dado hace que un objeto golpee a otro objeto y responder en consecuencia. Para las paredes, el movimiento debe detenerse. Al golpear una moneda, esa moneda debe ser recogida. Al tocar lava, el juego debería perderse. +Mover cosas es fácil. La parte difícil es lidiar con las interacciones entre los elementos. Cuando el jugador golpea una pared o el suelo, este no debería atravesarlos. El juego debe notar cuándo un movimiento dado hace que un objeto golpee a otro objeto y responder en consecuencia. Para las paredes, el movimiento debe detenerse. Al golpear una moneda, esa moneda debe ser recogida. Al tocar lava, la partida debería acabarse. -Resolver esto para el caso general es una tarea grande. Puedes encontrar bibliotecas, generalmente llamadas _((motores físicos))_, que simulan la interacción entre objetos físicos en dos o tres ((dimensiones)). Tomaremos un enfoque más modesto en este capítulo, manejando solo colisiones entre objetos rectangulares y manejándolas de una manera bastante simplista. +Resolver esto para un caso general es una tarea complicada. Puedes encontrar bibliotecas, generalmente llamadas _((motores físicos))_, que simulan la interacción entre objetos físicos en dos o tres ((dimensiones)). Adoptaremos un enfoque más modesto en este capítulo, manejando solo colisiones entre objetos rectangulares y manejándolas de una manera bastante simplista. {{index rebote, "detección de colisión", ["animación", "juego de plataformas"]}} -Antes de mover al ((jugador)) o un bloque de ((lava)), probamos si el movimiento los llevaría dentro de una pared. Si lo hace, simplemente cancelamos el movimiento por completo. La respuesta a tal colisión depende del tipo de actor. El jugador se detendrá, mientras que un bloque de lava rebotará. +Antes de mover al ((jugador)) o un bloque de ((lava)), probamos si el movimiento los llevaría dentro de una pared. Si lo hace, simplemente cancelamos el movimiento. La respuesta a tal colisión depende del tipo de actor. Si se trata del jugador, este se detendrá, mientras que un bloque de lava rebotará. {{index "discretización"}} -Este enfoque requiere que nuestros pasos de ((tiempo)) sean bastante pequeños, ya que hará que el movimiento se detenga antes de que los objetos realmente se toquen. Si los pasos de tiempo (y por lo tanto los pasos de movimiento) son demasiado grandes, el jugador terminaría elevándose a una distancia notable sobre el suelo. Otro enfoque, argumentablemente mejor pero más complicado, sería encontrar el punto exacto de colisión y moverse allí. Tomaremos el enfoque simple y ocultaremos sus problemas asegurando que la animación avance en pasos pequeños. +Este enfoque requiere que nuestros pasos de ((tiempo)) sean bastante pequeños, ya que hará que el movimiento se detenga antes de que los objetos realmente se toquen. Si los pasos de tiempo (y por lo tanto los pasos de movimiento) son demasiado grandes, el jugador terminaría flotando a una distancia notable sobre el suelo. Otro enfoque, bastante mejor pero más complicado, sería encontrar el punto exacto de colisión y moverse allí. Tomaremos el enfoque simple y ocultaremos sus problemas asegurando que la animación avance en pasos pequeños. {{index "obstáculo", "método touches", "detección de colisiones"}} @@ -577,9 +579,9 @@ Level.prototype.touches = function(pos, size, type) { {{index "función Math.floor", "función Math.ceil"}} -El método calcula el conjunto de cuadrados de rejilla con los que el cuerpo se ((superpone)) utilizando `Math.floor` y `Math.ceil` en sus ((coordenadas)). Recuerda que los cuadrados de la ((rejilla)) son de tamaño 1 por 1 unidad. Al ((redondear)) los lados de un cuadro hacia arriba y hacia abajo, obtenemos el rango de cuadrados de ((fondo)) que el cuadro toca. +El método calcula el conjunto de cuadrados de rejilla con los que el cuerpo se ((superpone)) utilizando `Math.floor` y `Math.ceil` en sus ((coordenadas)). Recuerda que los cuadrados de la ((rejilla)) son de tamaño 1 por 1 unidad. Al ((redondear)) los lados de un cuadro hacia arriba y hacia abajo, obtenemos el rango de cuadrados del ((fondo)) que el rectángulo toca. -{{figure {url: "img/game-grid.svg", alt: "Diagrama que muestra una rejilla con un cuadro negro superpuesto. Todos los cuadrados de la rejilla que están parcialmente cubiertos por el bloque están marcados.", width: "3cm"}}} +{{figure {url: "img/game-grid.svg", alt: "Diagrama que muestra una rejilla con un bloque negro superpuesto. Todos los cuadrados de la rejilla que están parcialmente cubiertos por el bloque están marcados.", width: "3cm"}}} Recorremos el bloque de cuadrados de ((rejilla)) encontrado al ((redondear)) las ((coordenadas)) y devolvemos `true` cuando se encuentra un cuadro coincidente. Los cuadrados fuera del nivel siempre se tratan como `"wall"` para asegurar que el jugador no pueda salir del mundo y que no intentemos leer fuera de los límites de nuestra matriz `rows`. @@ -607,16 +609,16 @@ State.prototype.update = function(time, keys) { }; ``` -El método recibe un paso de tiempo y una estructura de datos que le indica qué teclas se mantienen presionadas. Lo primero que hace es llamar al método `update` en todos los actores, produciendo un array de actores actualizados. Los actores también reciben el paso de tiempo, las teclas y el estado, para que puedan basar su actualización en esos valores. Solo el jugador realmente lee las teclas, ya que es el único actor controlado por el teclado. +El método recibe un paso de tiempo y una estructura de datos que le indica qué teclas se mantienen presionadas. Lo primero que hace es llamar al método `update` en todos los actores, produciendo un array de actores actualizados. Los actores también reciben el paso de tiempo, las teclas y el estado, para que puedan basar su actualización en esos valores. Solo el jugador lee realmente las teclas, ya que es el único actor controlado por el teclado. Si el juego ya ha terminado, no es necesario realizar más procesamiento (no se puede ganar el juego después de haber perdido, o viceversa). De lo contrario, el método prueba si el jugador está tocando lava de fondo. Si es así, se pierde el juego y hemos terminado. Finalmente, si el juego sigue en curso, verifica si algún otro actor se superpone al jugador.La superposición entre actores se detecta con la función `overlap`. Toma dos objetos actor y devuelve true cuando se tocan, lo cual sucede cuando se superponen tanto a lo largo del eje x como a lo largo del eje y. ```{includeCode: true} function overlap(actor1, actor2) { return actor1.pos.x + actor1.size.x > actor2.pos.x && - actor1.pos.x < actor2.pos.x + actor2.size.x && - actor1.pos.y + actor1.size.y > actor2.pos.y && - actor1.pos.y < actor2.pos.y + actor2.size.y; + actor1.pos.x < actor2.pos.x + actor2.size.x && + actor1.pos.y + actor1.size.y > actor2.pos.y && + actor1.pos.y < actor2.pos.y + actor2.size.y; } ``` @@ -641,7 +643,7 @@ Coin.prototype.collide = function(state) { {{index actor, "Clase Lava", lava}} -Los métodos `update` de los objetos actor toman como argumentos el paso de tiempo, el objeto de estado y un objeto `keys`. El de tipo actor `Lava` ignora el objeto `keys`. +Los métodos `update` de los objetos actor toman como argumentos el paso de tiempo, el objeto de estado y un objeto `keys`. El actor de tipo `Lava` ignora el objeto `keys`. ```{includeCode: true} Lava.prototype.update = function(time, state) { @@ -658,7 +660,7 @@ Lava.prototype.update = function(time, state) { {{index rebotante, "multiplicación", "Clase Vec", "detección de colisiones"}} -Este método `update` calcula una nueva posición agregando el producto del paso de tiempo y la velocidad actual a su posición anterior. Si no hay obstáculos que bloqueen esa nueva posición, se mueve allí. Si hay un obstáculo, el comportamiento depende del tipo de bloque de ((lava))—la lava goteante tiene una posición de `reset` a la que regresa cuando golpea algo. La lava rebotante invierte su velocidad multiplicándola por -1 para que comience a moverse en la dirección opuesta. +Este método `update` calcula una nueva posición agregando el producto del paso de tiempo y la velocidad actual a su posición anterior. Si no hay obstáculos que bloqueen esa nueva posición, se mueve allí. Si hay un obstáculo, el comportamiento depende del tipo de bloque de ((lava)) —la lava goteante tiene una posición de `reset` a la que regresa cuando golpea algo. La lava rebotante invierte su velocidad multiplicándola por -1 para que comience a moverse en el sentido opuesto. {{index "Clase Coin", coin, wave}} @@ -677,11 +679,11 @@ Coin.prototype.update = function(time) { {{index "Función Math.sin", seno, fase}} -La propiedad `wobble` se incrementa para hacer un seguimiento del tiempo y luego se utiliza como argumento para `Math.sin` para encontrar la nueva posición en la ((onda)). La posición actual de la moneda se calcula a partir de su posición base y un desplazamiento basado en esta onda. +La propiedad `wobble` se incrementa para hacer un seguimiento del tiempo y luego se utiliza como argumento para `Math.sin` para encontrar la nueva posición en el ((vaivén)). La posición actual de la moneda se calcula a partir de su posición base y un desplazamiento basado en esta onda. {{index "detección de colisiones", "clase Jugador"}} -Eso deja al ((jugador)) en sí. El movimiento del jugador se maneja por separado por ((eje)) porque golpear el suelo no debería impedir el movimiento horizontal, y golpear una pared no debería detener el movimiento de caída o de salto. +Ya solo nos queda el ((jugador)). El movimiento del jugador se maneja por separado por cada ((eje)), porque golpear el suelo no debería impedir el movimiento horizontal, y golpear una pared no debería detener el movimiento de caída o de salto. ```{includeCode: true} const playerXSpeed = 7; @@ -723,7 +725,7 @@ El movimiento vertical funciona de manera similar pero tiene que simular ((salto Comprobamos las paredes nuevamente. Si no golpeamos ninguna, se usa la nueva posición. Si _hay_ una pared, hay dos posibles resultados. Cuando se presiona la flecha hacia arriba _y_ estamos bajando (lo que significa que lo que golpeamos está debajo de nosotros), la velocidad se establece en un valor negativo relativamente grande. Esto hace que el jugador salte. Si ese no es el caso, el jugador simplemente chocó con algo y la velocidad se establece en cero. -La fuerza de la gravedad, la velocidad de ((salto)) y otras ((constantes)) en el juego se determinaron simplemente probando algunos números y viendo cuáles se sentían correctos. Puedes experimentar con ellos. +La fuerza de la gravedad, la velocidad de ((salto)) y otras ((constantes)) en el juego se determinaron simplemente probando algunos números y viendo cuáles se sentían más correctos. Puedes experimentar con ellos. ## Seguimiento de teclas @@ -752,11 +754,11 @@ function trackKeys(keys) { window.addEventListener("keyup", track); return down; } -``` +const arrowKeys = + trackKeys(["ArrowLeft", "ArrowRight", "ArrowUp"]); ``` -const arrowKeys = trackKeys(["ArrowLeft", "ArrowRight", "ArrowUp"]); -``` + {{index "evento keydown", "evento keyup"}} @@ -797,7 +799,7 @@ La función también convierte los pasos de tiempo a segundos, que son una canti {{index "función de devolución de llamada", "función runLevel", ["animación", "juego de plataformas"]}} -La función `runLevel` toma un objeto `Level` y un constructor de ((display)) y devuelve una promesa. Muestra el nivel (en `document.body`) y permite al usuario jugar a través de él. Cuando el nivel termina (perdido o ganado), `runLevel` espera un segundo más (para que el usuario vea qué sucede), luego borra la pantalla, detiene la animación y resuelve la promesa con el estado final del juego. +La función `runLevel` toma un objeto `Level` y un constructor de ((display)) y devuelve una promesa. Muestra el nivel (en `document.body`) y permite al usuario jugar a través de él. Cuando el nivel termina (perdiendo o ganando), `runLevel` espera un segundo más (para que el usuario vea qué sucede), luego borra la pantalla, detiene la animación y resuelve la promesa con el estado final del juego. ```{includeCode: true} function runLevel(level, Display) { @@ -825,26 +827,26 @@ function runLevel(level, Display) { {{index "función runGame"}} -Un juego es una secuencia de ((niveles)). Cada vez que el ((jugador)) muere, el nivel actual se reinicia. Cuando se completa un nivel, pasamos al siguiente nivel. Esto se puede expresar mediante la siguiente función, que toma un array de planes de nivel (cadenas) y un constructor de ((display)): +Un juego es una secuencia de ((niveles)). Cada vez que el ((jugador)) muere, el nivel actual se reinicia. Cuando se completa un nivel, pasamos al siguiente nivel. Esto se puede expresar mediante la siguiente función, que toma un array de planos de nivel (cadenas) y un constructor de ((display)): ```{includeCode: true} async function runGame(plans, Display) { for (let level = 0; level < plans.length;) { let status = await runLevel(new Level(plans[level]), Display); - if (status == "ganado") level++; + if (status == "won") level++; } - console.log("¡Has ganado!"); + console.log("You've won!"); } ``` {{index "programación asíncrona", "manejo de eventos"}} -Debido a que hicimos que `runLevel` devuelva una promesa, `runGame` puede escribirse utilizando una función `async`, como se muestra en el [Capítulo ?](async). Devuelve otra promesa, que se resuelve cuando el jugador termina el juego. +Como hemos hecho que `runLevel` devuelva una promesa, `runGame` puede escribirse utilizando una función `async`, como se muestra en el [Capítulo ?](async). Devuelve otra promesa, que se resuelve cuando el jugador termina el juego. {{index juego, "conjunto de datos GAME_LEVELS"}} -Hay un conjunto de planes de ((niveles)) disponibles en el enlace `GAME_LEVELS` en el [sandbox de este capítulo](https://eloquentjavascript.net/code#16)[ ([_https://eloquentjavascript.net/code#16_](https://eloquentjavascript.net/code#16))]{if book}. Esta página los alimenta a `runGame`, comenzando un juego real. +Hay un conjunto de planos de ((niveles)) disponibles en la asociación `GAME_LEVELS` en el [sandbox de este capítulo](https://eloquentjavascript.net/code#16)[ ([_https://eloquentjavascript.net/code#16_](https://eloquentjavascript.net/code#16))]{if book}. Esta página los alimenta a `runGame`, comenzando un juego real. ```{sandbox: null, focus: yes, lang: html, startCode: true} <link rel="stylesheet" href="css/game.css"> @@ -858,17 +860,17 @@ Hay un conjunto de planes de ((niveles)) disponibles en el enlace `GAME_LEVELS` {{if interactive -Intenta vencerlos. Me divertí construyéndolos. +Intenta pasártelos. Yo me he divertido construyéndolos. if}} ## Ejercicios -### Juego terminado +### Fin del juego {{index "vidas (ejercicio)", juego}} -Es tradicional que los ((juegos de plataformas)) hagan que el jugador comience con un número limitado de _vidas_ y resten una vida cada vez que mueren. Cuando el jugador se queda sin vidas, el juego se reinicia desde el principio. +Es tradición que los ((juegos de plataformas)) hagan que el jugador comience con un número limitado de _vidas_ y resten una vida cada vez que mueren. Cuando el jugador se queda sin vidas, el juego se reinicia desde el principio. {{index "función runGame"}} @@ -901,7 +903,7 @@ if}} {{index "pausa (ejercicio)", "tecla de escape", teclado}} -Haz posible pausar y despausar el juego presionando la tecla Esc. +Haz que se pueda pausar y reanudar el juego presionando la tecla Esc. {{index "función runLevel", "manejo de eventos"}} @@ -913,7 +915,7 @@ La interfaz de `runAnimation` puede no parecer adecuada para esto a primera vist {{index [enlace, global], "función trackKeys"}} -Cuando tengas eso funcionando, hay algo más que podrías intentar. La forma en que hemos estado registrando los controladores de eventos de teclado es algo problemática. El objeto `arrowKeys` es actualmente una asignación global, y sus controladores de eventos se mantienen incluso cuando no hay ningún juego en ejecución. Podrías decir que _escapan_ de nuestro sistema. Amplía `trackKeys` para proporcionar una forma de anular el registro de sus controladores y luego cambia `runLevel` para registrar sus controladores cuando comienza y desregistrarlos nuevamente cuando termine. +Cuando eso esté funcionando, hay algo más que podrías intentar. La forma en que hemos estado registrando los controladores de eventos de teclado es algo problemática. El objeto `arrowKeys` es actualmente una asignación global, y sus controladores de eventos se mantienen incluso cuando no hay ningún juego en ejecución. Podrías decir que _escapan_ de nuestro sistema. Amplía `trackKeys` para proporcionar una forma de anular el registro de sus controladores y luego cambia `runLevel` para registrar sus controladores cuando comienza y desregistrarlos nuevamente cuando termine. {{if interactive @@ -963,7 +965,7 @@ Así que necesitamos comunicar el hecho de que estamos pausando el juego a la fu {{index "event handling", "removeEventListener method", [function, "as value"]}} -Al encontrar una forma de anular los controladores registrados por `trackKeys`, recuerda que el valor de función _exactamente_ igual que se pasó a `addEventListener` debe pasarse a `removeEventListener` para quitar con éxito un controlador. Por lo tanto, el valor de función `handler` creado en `trackKeys` debe estar disponible para el código que anula los controladores. +Al encontrar una forma de anular los controladores registrados por `trackKeys`, recuerda que _exactamente_ el mismo valor de función que se pasó a `addEventListener` debe pasarse a `removeEventListener` para quitar con éxito un controlador. Por lo tanto, el valor de función `handler` creado en `trackKeys` debe estar disponible para el código que anula los controladores. Puedes agregar una propiedad al objeto devuelto por `trackKeys`, que contenga ese valor de función o un método que maneje la anulación directamente. @@ -973,11 +975,11 @@ hint}} {{index "monster (exercise)"}} -Es tradicional que los juegos de plataformas tengan enemigos a los que puedes saltar encima para derrotar. Este ejercicio te pide que agregues un tipo de actor así al juego. +Es tradición que los juegos de plataformas tengan enemigos a los que puedes saltar encima para derrotar. Este ejercicio te pide que agregues un tipo de actor así al juego. Lo llamaremos monstruo. Los monstruos se mueven solo horizontalmente. Puedes hacer que se muevan en la dirección del jugador, que reboten de un lado a otro como lava horizontal, o tengan cualquier patrón de movimiento que desees. La clase no tiene que manejar caídas, pero debe asegurarse de que el monstruo no atraviese paredes. -Cuando un monstruo toca al jugador, el efecto depende de si el jugador está saltando encima de ellos o no. Puedes aproximarlo comprobando si el final del jugador está cerca de la parte superior del monstruo. Si este es el caso, el monstruo desaparece. Si no, el juego se pierde. +Cuando un monstruo toca al jugador, el efecto depende de si el jugador está saltando encima de ellos o no. Puedes aproximarlo comprobando si el final del jugador está cerca de la parte superior del monstruo. Si este es el caso, el monstruo desaparece. Si no, la partida termina. {{if interactive diff --git a/19_paint.md b/19_paint.md index b0423f0a..d46bc611 100644 --- a/19_paint.md +++ b/19_paint.md @@ -8,7 +8,7 @@ Observo los muchos colores ante mí. Observo mi lienzo en blanco. Luego, intento quote}} -{{index "Miro, Joan", "ejemplo de programa de dibujo", "capítulo del proyecto"}} +{{index "Miro, Joan", "ejemplo de programa de dibujo", "capítulo de proyecto"}} {{figure {url: "img/chapter_picture_19.jpg", alt: "Ilustración que muestra un mosaico de baldosas negras, con tarros de otras baldosas junto a él", chapter: "framed"}}} diff --git a/21_skillsharing.md b/21_skillsharing.md index 6ee64b03..f8e12bb5 100644 --- a/21_skillsharing.md +++ b/21_skillsharing.md @@ -8,13 +8,13 @@ Si tienes conocimiento, permite que otros enciendan sus velas en él. quote}} -{{index "proyecto de intercambio de habilidades", meetup, "capítulo del proyecto"}} +{{index "proyecto de intercambio de habilidades", meetup, "capítulo de proyecto"}} {{figure {url: "img/chapter_picture_21.jpg", alt: "Ilustración que muestra dos monociclos apoyados en un buzón", chapter: "framed"}}} Una reunión de ((intercambio de habilidades)) es un evento en el que personas con un interés compartido se reúnen y dan pequeñas presentaciones informales sobre cosas que saben. En una reunión de intercambio de habilidades de ((jardinería)), alguien podría explicar cómo cultivar ((apio)). O en un grupo de intercambio de habilidades de programación, podrías pasar y contarles a la gente sobre Node.js. -En este último capítulo del proyecto, nuestro objetivo es configurar un ((sitio web)) para gestionar las ((charla))s impartidas en una reunión de intercambio de habilidades. Imagina un pequeño grupo de personas que se reúnen regularmente en la oficina de uno de los miembros para hablar sobre ((monociclos)). El organizador anterior de las reuniones se mudó a otra ciudad y nadie se ofreció a asumir esta tarea. Queremos un sistema que permita a los participantes proponer y discutir charlas entre ellos, sin un organizador activo. +En este último capítulo de proyecto, nuestro objetivo es configurar un ((sitio web)) para gestionar las ((charla))s impartidas en una reunión de intercambio de habilidades. Imagina un pequeño grupo de personas que se reúnen regularmente en la oficina de uno de los miembros para hablar sobre ((monociclos)). El organizador anterior de las reuniones se mudó a otra ciudad y nadie se ofreció a asumir esta tarea. Queremos un sistema que permita a los participantes proponer y discutir charlas entre ellos, sin un organizador activo. [Al igual que en el [capítulo anterior](node), parte del código en este capítulo está escrito para Node.js y es poco probable que funcione si se ejecuta directamente en la página HTML que estás viendo.]{if interactive} El código completo del proyecto se puede ((descargar)) desde [_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip). diff --git a/html/16_game.html b/html/16_game.html index 4dffb3a5..6dbb3bc0 100644 --- a/html/16_game.html +++ b/html/16_game.html @@ -4,7 +4,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Proyecto: Un juego de plataformas :: Eloquent JavaScript + var page = {"type":"chapter","number":16,"load_files":["code/chapter/16_game.js","code/levels.js","code/stop_keys.js"]}