diff --git a/00_intro.md b/00_intro.md index b8932db0..5c23443a 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,17 +110,17 @@ 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. - 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, 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 `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,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 `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"}} -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 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))); @@ -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/01_values.md b/01_values.md index b21a1572..f2b19eaf 100644 --- a/01_values.md +++ b/01_values.md @@ -1,19 +1,19 @@ # Valores, Tipos y Operadores -{{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} -Debajo de la superficie de la máquina, el programa se mueve. Sin esfuerzo, se expande y contrae. En gran armonía, los electrones se dispersan y se reagrupan. Las formas en el monitor no son más que ondas en el agua. La esencia permanece invisible debajo. +Bajo de la superficie de la máquina, el programa se mueve. Sin esfuerzo, se expande y contrae. En gran armonía, los electrones se dispersan y reagrupan. Las formas en el monitor no son sino ondas en el agua. La esencia permanece invisible debajo. quote}} {{figure {url: "img/chapter_picture_1.jpg", alt: "Una foto de un mar de bits", chapter: "framed"}}} -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. +En el mundo de la computadora, solo existe la información. Puedes leer datos, modificar datos, crear nuevos datos, pero no se puede hablar de nada que no sean datos. Todos estos datos se almacenan como largas secuencias de bits, y, por tanto, en el fondo, son todos iguales. -_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. +Llamamos _bits_ a cualquier tipo de cosa con dos posibles valores, y generalmente los describimos usando ceros y unos. Dentro de la computadora, aparecen en forma de 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. -Por ejemplo, podemos expresar el número 13 en bits. Esto funciona de la misma manera que un número decimal, pero en lugar de diez ((dígito))s diferentes, tenemos solo 2, y el peso de cada uno aumenta por un factor de 2 de derecha a izquierda. Aquí están los bits que componen el número 13, con los pesos de los dígitos mostrados debajo de ellos: +Por ejemplo, podemos expresar el número 13 en bits de la misma manera que lo hacemos como número decimal, pero en lugar de utilizando diez ((dígito))s diferentes, usando solo 2, aumentando el peso de cada dígito de la representación por un factor de 2 de derecha a izquierda. Aquí están los bits que componen el número 13, con los pesos de los dígitos mostrados debajo de ellos: ```{lang: null} 0 0 0 0 1 1 0 1 @@ -24,11 +24,13 @@ Ese es el número binario 00001101. Sus dígitos no nulos representan 8, 4 y 1, ## Valores -Imagina una mar de bits—un océano de ellos. Una computadora moderna típica tiene más de 100 mil millones de bits en su almacenamiento de datos volátil (memoria de trabajo). El almacenamiento no volátil (el disco duro o equivalente) tiende a tener aún unos cuantos órdenes de magnitud más. +Imagina una mar de bits—un océano de ellos. Una computadora corriente de hoy en día tiene más de 100 mil millones de bits en su almacenamiento de datos volátil (memoria de trabajo o memoria RAM). El almacenamiento no volátil (el disco duro o cualquier equivalente) tiende a tener aún unos cuantos órdenes de magnitud más. -Para poder trabajar con tales cantidades de bits sin perderse, los separamos en trozos que representan piezas de información. En un entorno de JavaScript, esos trozos se llaman _((valor))es_. Aunque todos los valores están hechos de bits, desempeñan roles diferentes. Cada valor tiene un ((tipo)) que determina su función. Algunos valores son números, otros son fragmentos de texto, otros son funciones, y así sucesivamente. +Para poder trabajar con tales cantidades de bits sin perdernos, los separamos en trozos que representan piezas de información. En un entorno de JavaScript, esos trozos se llaman _((valor))es_. Aunque todos los valores están hechos de bits, desempeñan roles diferentes. Cada valor tiene un ((tipo)) que determina su función. Algunos valores son números, otros son fragmentos de texto, otros son funciones, etc. -Para crear un valor, simplemente debes invocar su nombre. Esto es conveniente. No tienes que recolectar material de construcción para tus valores ni pagar por ellos. Solo solicitas uno, y ¡zas!, lo tienes. Por supuesto, los valores no se crean realmente de la nada. Cada uno tiene que almacenarse en algún lugar, y si deseas usar gigantescas cantidades de ellos al mismo tiempo, podrías quedarte sin memoria de computadora. Afortunadamente, este es un problema solo si los necesitas todos simultáneamente. Tan pronto como dejes de usar un valor, se disipará, dejando atrás sus bits para ser reciclados como material de construcción para la próxima generación de valores. El resto de este capítulo presenta los elementos atómicos de los programas de JavaScript, es decir, los tipos de valores simples y los operadores que pueden actuar sobre dichos valores. +Para crear un valor, basta con invocar su nombre. Esto es conveniente. No tienes que recolectar material de construcción para tus valores ni pagar por ellos. Solo solicitas uno, y ¡pum!, ahí está. Por supuesto, los valores no se crean realmente de la nada. Cada uno tiene que almacenarse en algún lugar, y, si deseas usar gigantescas cantidades de ellos al mismo tiempo, podrías quedarte sin memoria en la computadora. Por suerte, esto es un problema solo si los necesitas todos a la vez. Tan pronto como dejes de usar un valor, se disipará, dejando atrás sus bits para ser reciclados como material de construcción para la próxima generación de valores. + +El resto de este capítulo presenta los elementos atómicos de los programas de JavaScript, es decir, los tipos de valores simples y los operadores que pueden actuar sobre dichos valores. ## Números @@ -46,17 +48,17 @@ Usar esto en un programa hará que el patrón de bits para el número 13 exista {{index ["número", "representación"], bit}} -JavaScript utiliza un número fijo de bits, 64 de ellos, para almacenar un único valor numérico. Hay un número limitado de patrones que puedes hacer con 64 bits, lo que limita la cantidad de números diferentes que se pueden representar. Con _N_ ((dígitos)) decimales, puedes representar 10^N^ números. De manera similar, dada una cifra de 64 dígitos binarios, puedes representar 2^64^ números diferentes, que son alrededor de 18 mil trillones (un 18 seguido de 18 ceros). Eso es mucho. +JavaScript utiliza una cantidad fija de bits, 64 de ellos, para almacenar cada valor numérico. Hay una cantidad limitada de patrones que puedes formar con 64 bits, lo que limita la cantidad de números diferentes que se pueden representar. Con _N_ ((dígitos)) decimales, puedes representar 10^N^ números distintos. De manera similar, utilizando 64 dígitos binarios, puedes representar 2^64^ números diferentes, que son alrededor de 18 mil trillones (un 18 seguido de 18 ceros). Eso es un montón. -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. +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 acabar por error con un _((desbordamiento))_ (comúnmente conocido como _overflow_) con un conjunto posible de números tan pequeño —es decir, terminar con un número que no puede ser representado con la cantidad dada de bits. Hoy en día, incluso un dispositivo que cabe en tu bolsillo tiene mucha memoria, por lo que puedes utilizar trozos de 64 bits para representar números, y solo necesitas preocuparte por el desbordamiento cuando pretendes representar cantidades realmente astronómicas. {{index signo, "número de punto flotante", "bit de signo"}} -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. +En cualquier caso, no todos los números naturales menores que 18 mil trillones caben en un número de JavaScript. Esos bits también almacenan números negativos, por lo que un bit se usa para indicar el signo del número. Y aún más complicado 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ás grande que en realidad se puede almacenar está más en el rango de los 9 mil billones (15 ceros), que sigue siendo increíblemente grande. {{index ["número", "notación"], "número fraccionario"}} -Los números fraccionarios se escriben usando un punto: +Para representar números con parte decimal se utiliza un punto: ``` 9.81 @@ -64,23 +66,23 @@ Los números fraccionarios se escriben usando un punto: {{index exponente, "notación científica", ["número", "notación"]}} -Para números muy grandes o muy pequeños, también puedes usar notación científica agregando una _e_ (de _exponente_), seguida del exponente del número: +Para números muy grandes o muy pequeños, también se puede usar notación científica agregando una _e_ (de _exponente_), seguida del exponente del número: ``` 2.998e8 ``` -Eso es 2.998 × 10^8^ = 299,800,000. +Esto representaría al número 2.998 × 10^8^ = 299,800,000. {{index pi, ["número", "precisión de"], "número de punto flotante"}} -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. +La precisión de cualquier cálculo con números _((enteros))_ con valor absoluto menor a los 9 mil billones antes mencionados está siempre garantizada. Por desgracia, la de los cálculos con números no enteros generalmente no está. Así como π (pi) no puede expresarse con total precisión mediante un número finito de dígitos decimales, muchos números pierden algo de precisión en su representación cuando solo tenemos 64 bits para almacenarlos. Es una lástima, pero solo provoca problemas prácticos en situaciones específicas. Lo importante tenerlo en cuenta y tratar los números digitales con parte decimal como aproximaciones, no como valores precisos. ### Aritmética {{index [sintaxis, operador], operador, "operador binario", "aritmética", suma, "multiplicación"}} -Lo principal que se puede hacer con los números es la aritmética. Operaciones aritméticas como la suma o la multiplicación toman dos valores numéricos y producen un nuevo número a partir de ellos. Así es como se ven en JavaScript: +Con números, lo principal que uno puede hacer es aritmética. Operaciones aritméticas como la suma o la multiplicación toman dos valores numéricos y producen un nuevo número a partir de ellos. Esta es la pinta que tienen en JavaScript: ```{meta: "expr"} 100 + 4 * 11 @@ -92,7 +94,7 @@ Los símbolos `+` y `*` se llaman _operadores_. El primero representa la suma y {{index "agrupación", "paréntesis", precedencia}} -¿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: +¿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, esto se puede cambiar escribiendo la suma entre paréntesis: ```{meta: "expr"} (100 + 4) * 11 @@ -108,17 +110,17 @@ No te preocupes demasiado por estas reglas de precedencia. Cuando tengas dudas, {{index "operador de módulo", "división", "operador de residuo", "% operator"}} -Hay un operador aritmético más, que quizás no reconozcas de inmediato. El símbolo `%` se utiliza para representar la operación de _residuo_. `X % Y` es el residuo de dividir `X` por `Y`. Por ejemplo, `314 % 100` produce `14`, y `144 % 12` da `0`. La precedencia del operador de residuo es la misma que la de multiplicación y división. También verás a menudo a este operador referido como _módulo_. +Hay un operador aritmético más que quizás no te suene tanto. El símbolo `%` se utiliza para representar la operación de _resto_. `X % Y` es el resto de la división entera de `X` entre `Y`. Por ejemplo, `314 % 100` produce `14`, y `144 % 12` da `0`. La precedencia del operador de resto es la misma que la de multiplicación y división. También verás a menudo a este operador referido como _módulo_. ### Números especiales {{index ["número", "valores especiales"], infinito}} -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`. +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`, etc. De todos modos, no te fíes demasiado de las cuentas que involucren al infinito. No suele tener sentido matemáticamente y rápidamente te llevará a encontrarte con el siguiente número especial: `NaN`. {{index NaN, "no es un número", "división por cero"}} -`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. +`NaN` significa "no es un número" (en inglés, _not a number_), aunque _es_ un valor del tipo _number_. Obtendrás este resultado cuando, por ejemplo, intentes calcular `0 / 0` (cero dividido por cero), `Infinity - Infinity`, o cualquier otra cuanta que no tenga un sentido determinado. ## Cadenas @@ -126,7 +128,7 @@ Hay tres valores especiales en JavaScript que se consideran números pero no se {{index [sintaxis, cadena], texto, "carácter", [cadena, "notación"], "comilla simple", "comilla doble", comillas, comilla invertida}} -El siguiente tipo de dato básico es la _((cadena))_. Las cadenas se utilizan para representar texto. Se escriben encerrando su contenido entre comillas. +El siguiente tipo de dato básico es la _((cadena))_ (en inglés, _string_). Las cadenas se utilizan para representar texto. Se escriben encerrando su contenido entre comillas. ``` `En el mar` @@ -134,15 +136,15 @@ El siguiente tipo de dato básico es la _((cadena))_. Las cadenas se utilizan pa 'Flotando en el océano' ``` -Puedes usar comillas simples, comillas dobles o acentos graves para marcar las cadenas, siempre y cuando las comillas al principio y al final de la cadena coincidan. +Puedes usar comillas simples, comillas dobles o acentos graves para marcar las cadenas, siempre y cuando el tipo de comillas al principio y al final de la cadena coincidan. {{index "salto de línea", "carácter de nueva línea"}} -Puedes poner casi cualquier cosa entre comillas para que JavaScript genere un valor de cadena a partir de ello. Pero algunos caracteres son más difíciles. Puedes imaginar lo complicado que sería poner comillas entre comillas, ya que parecerían el final de la cadena. _Saltos de línea_ (los caracteres que obtienes al presionar [enter]{keyname}) solo se pueden incluir cuando la cadena está entre acentos graves (`` ` ``). +Puedes poner casi cualquier cosa entre comillas para que JavaScript genere un valor de tipo cadena a partir de ello. Pero algunos caracteres son más difíciles. Ya te puedes imaginar lo complicado que sería poner comillas entre comillas, ya que parecerían el final de la cadena. _Saltos de línea_ (los caracteres que obtienes cuando le das al [enter]{keyname}) solo se pueden incluir cuando la cadena está entre acentos graves (`` ` ``). {{index [escape, "en cadenas"], ["carácter de barra invertida", "en cadenas"]}} -Para poder incluir dichos caracteres en una cadena, se utiliza la siguiente notación: una barra invertida (`\`) dentro de un texto entre comillas indica que el carácter posterior tiene un significado especial. Esto se llama _escapar_ el carácter. Una comilla que va precedida por una barra invertida no finalizará la cadena, sino que formará parte de ella. Cuando un carácter `n` aparece después de una barra invertida, se interpreta como un salto de línea. De manera similar, un `t` después de una barra invertida significa un ((carácter de tabulación)). Toma la siguiente cadena: +Para poder incluir tales caracteres en una cadena, se utiliza la siguiente notación: una barra invertida (`\`) dentro de un texto entre comillas indica que el carácter posterior tiene un significado especial. A esto se le llama _escapar_ el carácter. Una comilla que va precedida por una barra invertida no finalizará la cadena, sino que formará parte de ella. Cuando un carácter `n` aparece después de una barra invertida, se interpreta como un salto de línea. De manera similar, un `t` después de una barra invertida significa un ((carácter de tabulación)). Consideremos la siguiente cadena: ``` "Esta es la primera línea\nY esta es la segunda" @@ -155,21 +157,21 @@ Esta es la primera línea Y esta es la segunda ``` -Por supuesto, hay situaciones en las que deseas que una barra invertida en una cadena sea simplemente una barra invertida, no un código especial. Si dos barras invertidas van seguidas, se colapsarán juntas y solo quedará una en el valor de cadena resultante. Así es como se puede expresar la cadena "_Un carácter de nueva línea se escribe como `"`\n`"`._": +Por supuesto, hay situaciones en las que te gustaría que una barra invertida en una cadena fuera simplemente una barra invertida, no un código especial. Si aparecen dos barras invertidas seguidas en una cadena, estas colapsarán en una en el valor de cadena resultante. Así es como se puede expresar la cadena "_Un carácter de salto de línea se escribe como `"`\n`"`._": ``` -"Un carácter de nueva línea se escribe como \"\\n\"." +"Un carácter de salto de línea se escribe como \"\\n\"." ``` {{id unicode}} {{index [cadena, "representación"], Unicode, "carácter"}} -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. +Las cadenas también hay que modelarlas como una serie de bits para que puedan 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, etc. 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. {{index "UTF-16", emoji}} -Pero hay una complicación: la representación de JavaScript utiliza 16 bits por elemento de cadena, lo que puede describir hasta 2^16^ 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 ?](higher_order#code_units). +Pero hay una complicación: la representación de JavaScript utiliza 16 bits por cada elemento de tipo cadena, lo que puede describir hasta 2^16^ caracteres diferentes. Sin embargo, Unicode define más caracteres (alrededor del doble que eso, de 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 ?](higher_order#code_units). {{index "operador +", "concatenación"}} @@ -179,7 +181,7 @@ Las cadenas no se pueden dividir, multiplicar o restar. El operador `+` se puede "con" + "cat" + "e" + "nar" ``` -Los valores de cadena tienen una serie de funciones asociadas (_métodos_) que se pueden utilizar para realizar otras operaciones con ellos. Hablaré más sobre esto en el [Capítulo ?](data#methods). +Los valores de tipo cadena tienen una serie de funciones asociadas (_métodos_) que se pueden utilizar para realizar otras operaciones con ellos. Hablaré más sobre esto en el [Capítulo ?](data#methods). {{index "interpolación", acento grave}} @@ -189,13 +191,13 @@ Las cadenas escritas con comillas simples o dobles se comportan de manera muy si `la mitad de 100 es ${100 / 2}` ``` -Cuando escribes algo dentro de `${}` en una plantilla literal, su resultado se calculará, se convertirá en una cadena y se incluirá en esa posición. Este ejemplo produce "_la mitad de 100 es 50_". +Cuando escribes algo dentro de `${}` en un _template literal_, su resultado se calculará, se convertirá en una cadena, y se incluirá en esa posición. Este ejemplo produce "_la mitad de 100 es 50_". ## Operadores unarios {{index operador, "operador typeof", tipo}} -No todos los operadores son símbolos. Algunos se escriben como palabras. Un ejemplo es el operador `typeof`, que produce un valor de cadena que indica el tipo del valor que le proporcionas. +No todos los operadores son símbolos. Algunos se representan con palabras. Un ejemplo es el operador `typeof`, que produce un valor de cadena que indica el tipo del valor que le proporcionas. ``` console.log(typeof 4.5) @@ -208,11 +210,11 @@ console.log(typeof "x") {{id "console.log"}} -Utilizaremos `console.log` en ejemplos de código para indicar que queremos ver el resultado de evaluar algo. Más sobre eso en el [próximo capítulo](program_structure). +Utilizaremos `console.log` en ejemplos de código para indicar que queremos ver el resultado de evaluar algo (veremos más sobre esto en el [próximo capítulo](program_structure)). {{index "negación", "- operador", "operador binario", "operador unario"}} -Los otros operadores mostrados hasta ahora en este capítulo operaron sobre dos valores, pero `typeof` toma solo uno. Los operadores que utilizan dos valores se llaman operadores _binarios_, mientras que aquellos que toman uno se llaman operadores _unarios_. El operador menos se puede usar tanto como un operador binario como un operador unario. +Los otros operadores mostrados hasta ahora en este capítulo operaron sobre dos valores, pero `typeof` toma solo uno. Los operadores que utilizan dos valores se llaman operadores _binarios_, mientras que aquellos que toman uno se llaman operadores _unarios_. El operador menos (`-`) se puede usar tanto como un operador binario como un operador unario. ``` console.log(- (10 - 2)) @@ -223,7 +225,7 @@ console.log(- (10 - 2)) {{index Booleano, operador, true, false, bit}} -A menudo es útil tener un valor que distinga solo entre dos posibilidades, como "sí" y "no" o "encendido" y "apagado". Para este propósito, JavaScript tiene un tipo _Booleano_, que tiene solo dos valores, true y false, escritos como esas palabras. +A menudo es útil tener un valor que distinga solo entre dos posibilidades, como "sí" y "no" o "encendido" y "apagado". Para este propósito, JavaScript tiene un tipo _Booleano_, que tiene solo dos valores, true y false, y que se escriben tal cual como esas palabras. ### Comparación @@ -240,7 +242,7 @@ console.log(3 < 2) {{index ["comparación", "de números"], "> operador", "< operador", "más grande que", "menos que"}} -Los signos `>` y `<` son símbolos tradicionales para "es mayor que" y "es menor que", respectivamente. Son operadores binarios. Aplicarlos da como resultado un valor booleano que indica si son verdaderos en este caso. +Los signos `>` y `<` son símbolos tradicionales para "es mayor que" y "es menor que", respectivamente. Son operadores binarios. Aplicarlos da como resultado un valor booleano que indica si son verdaderos en el caso en cuestión. Las cadenas se pueden comparar de la misma manera: @@ -251,7 +253,7 @@ console.log("Aardvark" < "Zoroaster") {{index ["comparación", "de cadenas"]}} -La forma en que se ordenan las cadenas es aproximadamente alfabética pero no es realmente lo que esperarías ver en un diccionario: las letras mayúsculas son siempre "menores" que las minúsculas, por lo que `"Z" < "a"`, y los caracteres no alfabéticos (!, -, y así sucesivamente) también se incluyen en la ordenación. Al comparar cadenas, JavaScript recorre los caracteres de izquierda a derecha, comparando los códigos ((Unicode)) uno por uno. +El orden entre casenas es más o menos el alfabético pero no exactamente como se hace en un diccionario: las letras mayúsculas son siempre "menores" que las minúsculas, conque `"Z" < "a"`; y los caracteres no alfabéticos (!, -, etc.) también se incluyen en la ordenación. Para comparar cadenas, lo que JavaScript hace es recorrer los caracteres de izquierda a derecha, comparando los códigos ((Unicode)) uno por uno. {{index igualdad, "operador >=", "operador <=", "operador ==", "operador !="}} @@ -266,7 +268,7 @@ console.log("Perla" == "Amatista") {{index ["comparación", "de NaN"], NaN}} -Solo hay un valor en JavaScript que no es igual a sí mismo, y ese es `NaN` ("no es un número"). +Solo hay un valor en JavaScript que no es igual a sí mismo, y ese es `NaN`. ``` console.log(NaN == NaN) @@ -283,7 +285,7 @@ También hay algunas operaciones que se pueden aplicar a los propios valores Boo {{index "&& operador", "and lógico"}} -El operador `&&` representa el _and_ lógico. Es un operador binario, y su resultado es verdadero solo si ambos valores dados son verdaderos. +El operador `&&` representa el _and_ lógico. Es un operador binario, y su resultado es verdadero solo si los dos valores dados son verdaderos. ``` console.log(true && false) @@ -305,11 +307,11 @@ console.log(false || false) {{index "negación", "! operador"}} -_Not_ se escribe con un signo de exclamación (`!`). Es un operador unario que invierte el valor dado; `!true` produce `false` y `!false` produce `true`. +_Not_ se escribe con un signo de exclamación (`!`). Es un operador unario que invierte el valor del valor dado; `!true` produce `false` y `!false` produce `true`. {{index precedencia}} -Al combinar estos operadores Booleanos con operadores aritméticos y otros operadores, no siempre es obvio cuándo se necesitan paréntesis. En la práctica, generalmente puedes avanzar sabiendo que de los operadores que hemos visto hasta ahora, `||` tiene la menor precedencia, luego viene `&&`, luego los operadores de comparación (`>`, `==`, etc.), y luego el resto. Este orden ha sido elegido de tal manera que, en expresiones típicas como la siguiente, se necesiten la menor cantidad de paréntesis posible: +Al combinar estos operadores Booleanos con operadores aritméticos y otros operadores, no siempre es obvio cuándo se necesitan paréntesis. En la práctica, generalmente puedes tirar para alante sabiendo que, de los operadores que hemos visto hasta ahora, `||` tiene la menor precedencia, luego viene `&&`, luego los operadores de comparación (`>`, `==`, etc.), y luego el resto. Este orden ha sido elegido de tal manera que, en expresiones típicas como la siguiente, se necesite la menor cantidad de paréntesis posible: ```{meta: "expr"} 1 + 1 == 2 && 10 * 10 > 50 @@ -317,7 +319,7 @@ Al combinar estos operadores Booleanos con operadores aritméticos y otros opera {{index "ejecución condicional", "operador ternario", "?: operador", "operador condicional", "carácter dos puntos", "signo de interrogación"}} -El último operador lógico que veremos no es unario ni binario, sino _ternario_, operando en tres valores. Se escribe con un signo de interrogación y dos puntos, así: +El último operador lógico que veremos no es unario ni binario, sino _ternario_, operando en tres valores. Se escribe con un signo de interrogación y dos puntos, como sigue: ``` console.log(true ? 1 : 2); @@ -326,21 +328,21 @@ console.log(false ? 1 : 2); // → 2 ``` -Este se llama el operador _condicional_ (o a veces simplemente _el operador ternario_ ya que es el único operador de este tipo en el lenguaje). El operador usa el valor a la izquierda del signo de interrogación para decidir cuál de los otros dos valores "elegir". Si escribes `a ? b : c`, el resultado será `b` cuando `a` es verdadero y `c` de lo contrario. +Este es el llamado operador _condicional_ (o a veces simplemente _el operador ternario_ ya que es el único operador de este tipo en el lenguaje). El operador usa el valor a la izquierda del signo de interrogación para decidir cuál de los otros dos valores "elegir". Si escribes `a ? b : c`, el resultado será `b` cuando `a` es verdadero y `c` en caso contrario. ## Valores vacíos {{index indefinido, nulo}} -Hay dos valores especiales, escritos `null` y `undefined`, que se utilizan para denotar la ausencia de un valor _significativo_. Son valores en sí mismos, pero no llevan ninguna información. Muchas operaciones en el lenguaje que no producen un valor significativo devuelven `undefined` simplemente porque tienen que devolver _algún_ valor. +Hay dos valores especiales que se escriben `null` y `undefined` y que se utilizan para denotar la ausencia de un valor _significativo_. Son valores en sí mismos, pero no llevan ninguna información. Muchas operaciones en el lenguaje que no producen un valor significativo devuelven `undefined` simplemente porque tienen que devolver _algún_ valor. -La diferencia en el significado entre `undefined` y `null` es un accidente del diseño de JavaScript, y la mayoría de las veces no importa. En casos en los que realmente tienes que preocuparte por estos valores, recomiendo tratarlos como en su mayoría intercambiables. +La diferencia en el significado entre `undefined` y `null` es un accidente del diseño de JavaScript, y la mayoría de las veces no es relevante. En casos en los que tienes que tratar con estos valores, recomiendo tratarlos como esencialmente intercambiables. ## Conversión automática de tipos {{index NaN, "coerción de tipos"}} -En la Introducción, mencioné que JavaScript se esfuerza por aceptar casi cualquier programa que le des, incluso programas que hacen cosas extrañas. Esto se demuestra claramente con las siguientes expresiones: +En la [Introducción](intro), mencioné que JavaScript se pasa por aceptando cualquier programa que le des, incluso programas que hacen cosas extrañas. Esto se demuestra claramente con las siguientes expresiones: ``` console.log(8 * null) @@ -357,15 +359,15 @@ console.log(false == 0) {{index "+ operator", "aritmética", "* operator", "- operator"}} -Cuando se aplica un operador al tipo de valor "incorrecto", JavaScript convertirá silenciosamente ese valor al tipo que necesita, utilizando un conjunto de reglas que a menudo no son las que deseas o esperas. Esto se llama _((coerción de tipos))_. El `null` en la primera expresión se convierte en `0` y el `"5"` en la segunda expresión se convierte en `5` (de cadena a número). Sin embargo, en la tercera expresión, `+` intenta la concatenación de cadenas antes que la suma numérica, por lo que el `1` se convierte en `"1"` (de número a cadena). +Cuando se aplica un operador al tipo de valor "incorrecto", JavaScript convertirá silenciosamente ese valor al tipo que necesita, utilizando una serie de reglas que a menudo no son las que buscas o esperas. Esto se llama _((coerción de tipos))_. El `null` en la primera expresión se convierte en `0` y el `"5"` en la segunda expresión se convierte en `5` (de cadena a número). Sin embargo, en la tercera expresión, `+` intenta la concatenación de cadenas antes que la suma de números, por lo que el `1` se convierte en `"1"` (de número a cadena). {{index "coerción de tipos", ["número", "conversión a"]}} -Cuando algo que no se corresponde con un número de manera obvia (como `"five"` o `undefined`) se convierte en un número, obtienes el valor `NaN`. Más operaciones aritméticas en `NaN` siguen produciendo `NaN`, así que si te encuentras con uno de estos en un lugar inesperado, busca conversiones de tipo accidentales. +Cuando se convierte en un número algo que no se corresponde de manera obvia con un número (como `"five"` o `undefined`), obtienes el valor `NaN`. Más operaciones aritméticas en `NaN` siguen produciendo `NaN`, así que si ves que te sale uno de estos en un lugar inesperado, busca conversiones de tipo accidentales. {{index null, undefined, ["comparación", "de valores undefined"], "== operador"}} -Cuando se comparan valores del mismo tipo usando el operador `==`, el resultado es fácil de predecir: deberías obtener verdadero cuando ambos valores son iguales, excepto en el caso de `NaN`. Pero cuando los tipos difieren, JavaScript utiliza un conjunto de reglas complicado y confuso para determinar qué hacer. En la mayoría de los casos, simplemente intenta convertir uno de los valores al tipo del otro valor. Sin embargo, cuando `null` o `undefined` aparece en cualquiera de los lados del operador, produce verdadero solo si ambos lados son uno de `null` o `undefined`. +Cuando se comparan valores del mismo tipo usando el operador `==`, el resultado es fácil de predecir: deberías obtener verdadero cuando ambos valores son iguales, excepto en el caso de `NaN`. Pero cuando los tipos difieren, JavaScript utiliza una serie de reglas extrañas para determinar qué hacer. En la mayoría de los casos, simplemente intenta convertir uno de los valores al tipo del otro valor. Sin embargo, cuando `null` o `undefined` aparecen en cualquiera de los lados del operador, este produce verdadero solo si el valor de ambos lados está entre `null` y `undefined`. ``` console.log(null == undefined); @@ -374,17 +376,19 @@ console.log(null == 0); // → false ``` -Ese comportamiento a menudo es útil. Cuando quieres probar si un valor tiene un valor real en lugar de `null` o `undefined`, puedes compararlo con `null` usando el operador `==` o `!=`. +Ese comportamiento a menudo es útil. Cuando quieres comprobar si un valor tiene un valor real en lugar de `null` o `undefined`, puedes simplemente compararlo con `null` usando el operador `==` o `!=`. {{index "coerción de tipos", [Boolean, "conversión a"], "=== operador", "!== operador", "comparación"}} -¿Qué sucede si quieres probar si algo se refiere al valor preciso `false`? Expresiones como `0 == false` y `"" == false` también son verdaderas debido a la conversión automática de tipos. Cuando _no_ deseas que ocurran conversiones de tipo, hay dos operadores adicionales: `===` y `!==`. El primero prueba si un valor es _precisamente_ igual al otro, y el segundo prueba si no es precisamente igual. Por lo tanto, `"" === false` es falso como se espera. Recomiendo usar los operadores de comparación de tres caracteres defensivamente para evitar conversiones de tipo inesperadas que puedan complicarte las cosas. Pero cuando estés seguro de que los tipos en ambos lados serán los mismos, no hay problema en usar los operadores más cortos. +¿Qué sucede si quieres probar si algo se refiere al valor preciso `false`? Expresiones como `0 == false` y `"" == false` también dan verdadero debido a la conversión automática de tipos. Cuando _no_ deseas que ocurran conversiones de tipo, hay dos operadores adicionales: `===` y `!==`. El primero prueba si un valor es _precisamente_ igual al otro, y el segundo prueba si no es precisamente igual. Por lo tanto, `"" === false` es falso, como era de esperar. + +Recomiendo usar los operadores de comparación de tres caracteres de manera preventiva para evitar conversiones de tipo inesperadas que puedan complicarte las cosas. Pero cuando estés seguro de que los tipos en ambos lados serán los mismos, no hay problema en usar los otros operadores más cortos. ### Cortocircuito de operadores lógicos {{index "coerción de tipo", [Boolean, "conversión a"], operador}} -Los operadores lógicos `&&` y `||` manejan valores de diferentes tipos de una manera peculiar. Convertirán el valor del lado izquierdo a tipo Booleano para decidir qué hacer, pero dependiendo del operador y el resultado de esa conversión, devolverán ya sea el valor original del lado izquierdo o el valor del lado derecho. +Los operadores lógicos `&&` y `||` manejan valores de tipos distintos de una forma peculiar. Convierten el valor del lado izquierdo a tipo Booleano para decidir qué hacer, pero, dependiendo del operador y el resultado de esa conversión, devuelven el valor _original_ del lado izquierdo o el valor del lado derecho. {{index "operador ||"}} @@ -399,11 +403,11 @@ console.log("Agnes" || "usuario") {{index "valor predeterminado"}} -Podemos utilizar esta funcionalidad como una forma de utilizar un valor predeterminado. Si tienes un valor que podría estar vacío, puedes colocar `||` después de él con un valor de reemplazo. Si el valor inicial se puede convertir en false, obtendrás el valor de reemplazo en su lugar. Las reglas para convertir cadenas y números en valores Booleanos establecen que `0`, `NaN` y la cadena vacía (`""`) cuentan como `false`, mientras que todos los demás valores cuentan como `true`. Esto significa que `0 || -1` produce `-1`, y `"" || "!?"` da como resultado `"!?"`. +Podemos utilizar esta funcionalidad como una forma de utilizar un valor predeterminado. Si tienes un valor que podría estar vacío, puedes colocar `||` después de él con un valor de reemplazo. Si el valor inicial se puede convertir en false, obtendrás el valor de reemplazo en su lugar. Las reglas para convertir cadenas y números en valores Booleanos establecen que `0`, `NaN` y la cadena vacía (`""`) cuentan como `false`, mientras que todos los demás valores serán `true`. Esto significa que `0 || -1` produce `-1`, y `"" || "!?"` da como resultado `"!?"`. {{index "operador ??", null, undefined}} -El operador `??` se asemeja a `||`, pero devuelve el valor de la derecha solo si el de la izquierda es null o undefined, no si es algún otro valor que se pueda convertir en `false`. A menudo, este comportamiento es preferible al de `||`. +El operador `??` se parece a `||`, pero devuelve el valor de la derecha solo si el de la izquierda es null o undefined, no si es algún otro valor que se pueda convertir en `false`. Normalmente, este comportamiento es preferible al de `||`. ``` console.log(0 || 100); @@ -416,9 +420,9 @@ console.log(null ?? 100); {{index "operador &&"}} -El operador `&&` funciona de manera similar pero en sentido contrario. Cuando el valor a su izquierda es algo que se convierte en false, devuelve ese valor, y de lo contrario devuelve el valor de su derecha. +El operador `&&` funciona de manera parecida pero en sentido contrario. Cuando el valor a su izquierda es algo que se convierte en false, devuelve ese valor, y de lo contrario devuelve el valor de su derecha. -Otra propiedad importante de estos dos operadores es que la parte de su derecha se evalúa solo cuando es necesario. En el caso de `true || X`, no importa qué sea `X`, incluso si es una parte del programa que hace algo _terrible_, el resultado será true, y `X` nunca se evaluará. Lo mismo ocurre con `false && X`, que es false e ignorará `X`. Esto se llama _evaluación de cortocircuito_. +Otra propiedad importante de estos dos operadores es que la parte de su derecha se evalúa solo cuando es necesario. En el caso de `true || X`, no importa lo que sea `X` —incluso si es una parte del programa que hace algo _terrible_—, el resultado será true, y `X` nunca se evaluará. Lo mismo ocurre con `false && X`, que es false e ignorará `X`. Esto se llama _evaluación de cortocircuito_ (o _evaluación mínima_). {{index "operador ternario", "operador ?:", "operador condicional"}} @@ -426,6 +430,8 @@ El operador condicional funciona de manera similar. De los valores segundo y ter ## Resumen -En este capítulo examinamos cuatro tipos de valores en JavaScript: números, cadenas, Booleanos y valores indefinidos. Tales valores son creados escribiendo su nombre (`true`, `null`) o valor (`13`, `"abc"`). Puedes combinar y transformar valores con operadores. Vimos operadores binarios para aritmética (`+`, `-`, `*`, `/` y `%`), concatenación de cadenas (`+`), comparación (`==`, `!=`, `===`, `!==`, `<`, `>`, `<=`, `>=`) y lógica (`&&`, `||`, `??`), así como varios operadores unarios (`-` para negar un número, `!` para negar lógicamente, y `typeof` para encontrar el tipo de un valor) y un operador ternario (`?:`) para elegir uno de dos valores basado en un tercer valor. +En este capítulo examinamos cuatro tipos de valores en JavaScript: números, cadenas, Booleanos y valores indefinidos. Tales valores son creados escribiendo su nombre (`true`, `null`) o valor (`13`, `"abc"`). + +Puedes combinar y transformar valores con operadores. Hemos visto operadores binarios para aritmética (`+`, `-`, `*`, `/` y `%`), concatenación de cadenas (`+`), comparación (`==`, `!=`, `===`, `!==`, `<`, `>`, `<=`, `>=`) y lógica (`&&`, `||`, `??`), así como varios operadores unarios (`-` para obtener el opuesto de un número, `!` para negar lógicamente, y `typeof` para descubrir el tipo de un valor) y un operador ternario (`?:`) para elegir uno de dos valores basado en un tercer valor. -Esto te proporciona suficiente información para usar JavaScript como una calculadora de bolsillo, pero no mucho más. El [próximo capítulo](program_structure) comenzará a unir estas expresiones en programas básicos. +Esto es información suficiente para usar JavaScript como una calculadora de bolsillo, pero no mucho más. El [próximo capítulo](program_structure) comenzará a unir estas expresiones en programas básicos. diff --git a/02_program_structure.md b/02_program_structure.md index cc0060f9..01d89fa8 100644 --- a/02_program_structure.md +++ b/02_program_structure.md @@ -1,8 +1,8 @@ # Estructura del Programa -{{quote {author: "_why", title: "Guía (conmovedora) de Ruby de Why", chapter: true} +{{quote {author: "_why", title: "La (Conmovedora) Guía de Ruby de Why", chapter: true} -Y mi corazón brilla intensamente bajo mi piel diáfana y translúcida, y tienen que administrarme 10cc de JavaScript para hacerme volver. (Respondo bien a las toxinas en la sangre.) ¡Hombre, esa cosa sacará los melocotones de tus agallas! +Y mi corazón brilla de color rojo intenso bajo mi diáfana y translúcida piel, y tienen que administrarme 10cc de JavaScript para traerme de vuelta. —Tolero bien las toxinas en la sangre. ¡Amigo, esa cosa sería capaz hasta de sacarte un melocotón atorado en las branquias! quote}} @@ -10,40 +10,40 @@ quote}} {{figure {url: "img/chapter_picture_2.jpg", alt: "Ilustración que muestra varios tentáculos sujetando piezas de ajedrez", chapter: framed}}} -En este capítulo, comenzaremos a hacer cosas que realmente pueden ser llamadas _programación_. Ampliaremos nuestro dominio del lenguaje JavaScript más allá de los sustantivos y fragmentos de oraciones que hemos visto hasta ahora, hasta el punto en que podamos expresar prosa significativa. +En este capítulo, vamos a empezar a hacer cosas que realmente podrían llamarse _programación_. Ampliaremos nuestro dominio del lenguaje JavaScript más allá de los sustantivos y fragmentos de oraciones que hemos visto hasta ahora, hasta el punto en que podamos expresar prosa significativa. ## Expresiones y declaraciones {{index grammar, [sintaxis, "expresión"], ["código", "estructura de"], "gramática", [JavaScript, sintaxis]}} -En el [Capítulo ?](values) creamos valores y le aplicamos operadores para obtener nuevos valores. Crear valores de esta manera es la sustancia principal de cualquier programa JavaScript. Pero esa sustancia debe enmarcarse en una estructura más grande para ser útil. Eso es lo que cubriremos en este capítulo. +En el [Capítulo ?](values) hemos creado valores y les hemos aplicado operadores para obtener nuevos valores. Crear valores de esta manera es la esencia principal de cualquier programa de JavaScript. Pero esa esencia debe enmarcarse en una estructura más grande para ser de utilidad. Eso es lo que vamos a cubrir en este capítulo. {{index "expresión literal", ["paréntesis", "expresión"]}} -Un fragmento de código que produce un valor se llama una _((expresión))_. Cada valor que está escrito literalmente (como `22` o `"psicoanálisis"`) es una expresión. Una expresión entre paréntesis también es una expresión, al igual que un ((operador binario)) aplicado a dos expresiones o un ((operador unario)) aplicado a uno. +Un trozo de código que produce un valor se llama una _((expresión))_. Cada valor escrito literalmente (como `22` o `"psicoanálisis"`) es una expresión. Una expresión entre paréntesis también es una expresión, al igual que un ((operador binario)) aplicado a dos expresiones o un ((operador unario)) aplicado a una. {{index [anidamiento, "de expresiones"], "lenguaje humano"}} -Esto muestra parte de la belleza de una interfaz basada en un lenguaje. Las expresiones pueden contener otras expresiones de manera similar a cómo las oraciones están anidadas en el lenguaje humano: una oración puede contener sus propias oraciones y así sucesivamente. Esto nos permite construir expresiones que describen cálculos arbitrariamente complejos. +Esto muestra parte de la belleza de una interfaz basada en un lenguaje. Las expresiones pueden contener otras expresiones de manera similar a cómo las oraciones están anidadas en el lenguaje humano —una oración puede contener sus propias oraciones y así sucesivamente. Esto nos permite construir expresiones que describen cálculos arbitrariamente complejos. {{index "declaración", punto y coma, programa}} -Si una expresión corresponde a un fragmento de oración, una _declaración_ de JavaScript corresponde a una oración completa. Un programa es una lista de declaraciones. +Si una expresión corresponde a un fragmento de oración, una _declaración_ (o sentencia, o instrucción) de JavaScript corresponde a una oración completa. Un programa es una lista de declaraciones. {{index [sintaxis, "declaración"]}} -El tipo más simple de declaración es una expresión con un punto y coma al final. Este es un programa: +El tipo más simple de declaración es una expresión con un punto y coma al final. Esto es un programa: ``` 1; !false; ``` -Sin embargo, es un programa inútil. Una ((expresión)) puede conformarse con simplemente producir un valor, que luego puede ser utilizado por el código que la contiene. Sin embargo, una ((declaración)) se mantiene por sí misma, por lo que si no afecta al mundo, es inútil. Puede mostrar algo en la pantalla, como con `console.log`, o cambiar el estado de la máquina de una manera que afectará a las declaraciones que vienen después de ella. Estos cambios se llaman _((efectos secundarios))_. Las declaraciones en el ejemplo anterior simplemente producen los valores `1` y `verdadero`, y luego los desechan inmediatamente. Esto no deja ninguna impresión en el mundo en absoluto. Cuando ejecutas este programa no sucede nada observable. +Aunque es un programa inútil. Una ((expresión)) puede conformarse con simplemente producir un valor, que luego podrá ser utilizado por el código que la contiene. Sin embargo, una ((declaración)) es autónoma, por lo que, si no afecta al mundo, es inútil. Puede mostrar algo en la pantalla, como con `console.log`, o cambiar el estado de la máquina de una manera que afectará a las declaraciones que vienen después de ella. Estos cambios se llaman _((efectos secundarios))_. Las declaraciones en el ejemplo anterior simplemente producen los valores `1` y `verdadero`, y luego los desecha inmediatamente. Esto no deja huella alguna en el mundo. Cuando ejecutas este programa no sucede nada observable. {{index "estilo de programación", "inserción automática de punto y coma", punto y coma}} -En algunos casos, JavaScript te permite omitir el punto y coma al final de una declaración. En otros casos, debe estar ahí, o la próxima ((línea)) se tratará como parte de la misma declaración. Las reglas sobre cuándo se puede omitir de manera segura son algo complejas y propensas a errores. Por lo tanto, en este libro, cada declaración que necesite un punto y coma siempre recibirá uno. Te recomiendo que hagas lo mismo, al menos hasta que hayas aprendido más sobre las sutilezas de la omisión del punto y coma. +A veces, JavaScript te permite omitir el punto y coma al final de una declaración. Otras veces, debe estar ahí, o, si no, la próxima ((línea)) se tratará como parte de la misma declaración. Las reglas sobre cuándo se puede omitir de manera segura son algo complejas y propensas a causar errores. Por lo tanto, en este libro vamos a ponerle un punto y coma a cada declaración que lo necesite. Te recomiendo que hagas lo mismo, al menos hasta que aprendas más sobre las sutilezas que conlleva la omisión del punto y coma. ## Enlaces @@ -51,7 +51,9 @@ En algunos casos, JavaScript te permite omitir el punto y coma al final de una d {{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 cambia los valores antiguos, y el nuevo valor debe utilizarse inmediatamente o se disipará nuevamente. Para atrapar y retener valores, JavaScript proporciona una cosa llamada un _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** 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; @@ -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 un enlace. Está seguida por el nombre del enlace 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. -El ejemplo crea un enlace llamado `caught` y lo 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 mantiene 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 en lugar de 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 `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`. Volveré a 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 para poder referirse fácilmente a él más tarde. +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. Los dígitos pueden formar parte de los nombres de enlaces, `catch22` es un nombre válido, por ejemplo, pero el nombre no puede empezar con un dígito. Un nombre de enlace puede incluir signos de dólar (`$`) o subrayados (`_`), pero no otros signos de puntuación o caracteres especiales. +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]}} -Palabras con un significado especial, como `let`, son _((palabra clave))_, y no pueden ser usadas como nombres de enlaces. 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,13 +150,13 @@ switch this throw true try typeof var void while with yield {{index [sintaxis, error]}} -No te preocupes por memorizar esta lista. Cuando al crear un enlace se produce un error de sintaxis inesperado, verifica 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 {{index "entorno estándar", [navegador, entorno]}} -La colección de enlaces y sus valores que existen en un momento dado se llama _((entorno))_. Cuando un programa se inicia, este entorno no está vacío. Siempre contiene enlaces que forman parte del lenguaje ((estándar)), y la mayoría de las veces también tiene enlaces que proporcionan formas de interactuar con el sistema circundante. Por ejemplo, en un navegador, existen funciones para interactuar con el sitio web cargado actualmente y para leer la entrada del ((ratón)) y el ((teclado)). +La colección de enlaces y sus valores que existen en un momento dado se llama _((entorno))_. Cuando un programa se inicia, este entorno no está vacío. Siempre contiene enlaces que forman parte del ((estándar)) del lenguaje, y la mayoría de las veces también tiene enlaces que proporcionan formas de interactuar con el sistema circundante. Por ejemplo, en un navegador, existen funciones para interactuar con el sitio web cargado actualmente y para leer la entrada del ((ratón)) y el ((teclado)). ## Funciones @@ -164,7 +166,7 @@ La colección de enlaces y sus valores que existen en un momento dado se llama _ {{index salida, "función", ["función", "aplicación"], [navegador, entorno]}} -Muchos de los valores proporcionados en el entorno predeterminado tienen el tipo de _((función))_. Una función es un fragmento de programa envuelto en un valor. Estos valores pueden ser _aplicados_ para ejecutar el programa envuelto. Por ejemplo, en un entorno de navegador, el enlace `prompt` contiene una función que muestra un pequeño ((cuadro de diálogo)) pidiendo la entrada del usuario. Se utiliza de la siguiente manera: +Muchos de los valores proporcionados en el entorno predeterminado tienen el tipo _((función))_. Una función es un fragmento de programa encapsulado en un valor. Estos valores pueden ser _aplicados_ para ejecutar el programa encapsulado. Por ejemplo, en un entorno de navegador, el enlace `prompt` contiene una función que muestra un pequeño ((cuadro de diálogo)) pidiendo la entrada del usuario. Se utiliza de la siguiente manera: ``` prompt("Enter passcode"); @@ -174,7 +176,7 @@ prompt("Enter passcode"); {{index "parámetro", ["función", "aplicación"], ["paréntesis", argumentos]}} -Ejecutar una función se llama _invocar_, _llamar_, o _aplicar_ la función. Puedes llamar una función poniendo paréntesis después de una expresión que produce un valor de función. Usualmente usarás directamente el nombre del enlace que contiene la función. Los valores entre paréntesis se le pasan al programa dentro de la función. En el ejemplo, la función `prompt` utiliza la cadena que le pasamos como el texto a mostrar en el cuadro de diálogo. Los valores dados a las funciones se llaman _((argumento))s_. Diferentes funciones pueden necesitar un número diferente o diferentes tipos de argumentos. +Ejecutar una función es lo que se conoce como _invocar_, _llamar_, o _aplicar_ la función. Puedes llamar a una función poniendo paréntesis después de una expresión que produce un valor de función. Usualmente usarás directamente el nombre del enlace que contiene la función. Los valores entre paréntesis se le pasan al programa de dentro de la función. En el ejemplo, la función `prompt` utiliza la cadena que le pasamos como el texto a mostrar en el cuadro de diálogo. Los valores dados a las funciones se llaman _((argumento))s_. Diferentes funciones pueden necesitar un número diferente o diferentes tipos de argumentos. La función `prompt` no se usa mucho en la programación web moderna, principalmente porque no tienes control sobre cómo se ve el cuadro de diálogo resultante, pero puede ser útil en programas simples y experimentos. @@ -182,7 +184,7 @@ La función `prompt` no se usa mucho en la programación web moderna, principalm {{index "consola JavaScript", "herramientas para desarrolladores", "Node.js", "console.log", salida, [navegador, entorno]}} -En los ejemplos, utilicé `console.log` para mostrar valores. La mayoría de los sistemas de JavaScript (incluidos todos los navegadores web modernos y Node.js) proveen una función `console.log` que escribe sus argumentos en _algún_ dispositivo de salida de texto. En los navegadores, la salida va a la ((consola de JavaScript)). Esta parte de la interfaz del navegador está oculta por defecto, pero la mayoría de los navegadores la abren cuando presionas F12 o, en Mac, [comando]{keyname}-[opción]{keyname}-I. Si eso no funciona, busca a través de los menús un elemento llamado Herramientas para Desarrolladores o similar. +En los ejemplos, he usado `console.log` para mostrar valores. La mayoría de los sistemas de JavaScript (incluidos todos los navegadores web modernos y Node.js) proveen una función `console.log` que escribe sus argumentos en _algún_ dispositivo de salida de texto. En los navegadores, la salida va a la ((consola de JavaScript)). Esta parte de la interfaz del navegador está oculta por defecto, pero la mayoría de los navegadores la abren cuando pulsas F12 o, en Mac, [comando]{keyname}-[opción]{keyname}-I. Si eso no funciona, busca a través de los menús un elemento llamado Herramientas para Desarrolladores o similar. {{if interactive @@ -198,14 +200,14 @@ 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 {{index ["comparación", "de números"], "valor de retorno", "función Math.max", "máximo"}} -Mostrar un cuadro de diálogo o escribir texto en la pantalla es un ((efecto secundario)). Muchas funciones son útiles debido a los efectos secundarios que producen. Las funciones también pueden producir valores, en cuyo caso no necesitan tener un efecto secundario para ser útiles. Por ejemplo, la función `Math.max` toma cualquier cantidad de argumentos numéricos y devuelve el mayor: +Mostrar un cuadro de diálogo o escribir texto en la pantalla es un ((efecto secundario)). Muchas funciones son útiles debido a los efectos secundarios que producen. Las funciones también pueden producir valores, en cuyo caso no necesitan tener un efecto secundario para ser útiles. Por ejemplo, la función `Math.max` toma una cantidad cualquiera de argumentos numéricos y devuelve el mayor de ellos: ``` console.log(Math.max(2, 4)); @@ -223,11 +225,11 @@ console.log(Math.min(2, 4) + 100); El [Capítulo ?](functions) explicará cómo escribir tus propias funciones. -## Control de flujo +## Flujo de control -{{index "orden de ejecución", programa, "control de flujo"}} +{{index "orden de ejecución", programa, "flujo de control"}} -Cuando tu programa contiene más de una ((sentencia)), las sentencias se ejecutan como si fueran una historia, de arriba hacia abajo. Por ejemplo, el siguiente programa tiene dos sentencias. La primera le pide al usuario un número, y la segunda, que se ejecuta después de la primera, muestra el ((cuadrado)) de ese número: +Cuando tu programa contiene más de una declaración (o ((sentencia))), estas se ejecutan como si fueran una historia, de arriba hacia abajo. Por ejemplo, el siguiente programa tiene dos declaraciones. La primera le pide al usuario un número, y la segunda, que se ejecuta después de la primera, muestra el ((cuadrado)) de ese número: ``` let elNumero = Number(prompt("Elige un número")); @@ -237,15 +239,15 @@ console.log("Tu número es la raíz cuadrada de " + {{index ["número", "conversión a"], "coerción de tipo", "función Number", "función String", "función Boolean", [Boolean, "conversión a"]}} -La función `Number` convierte un valor a un número. Necesitamos esa conversión porque el resultado de `prompt` es un valor de tipo string, y queremos un número. Hay funciones similares llamadas `String` y `Boolean` que convierten valores a esos tipos. +La función `Number` convierte un valor a un número. Necesitamos esa conversión porque el resultado de `prompt` es un valor de tipo _string_ (una cadena), y queremos un número (un valor de tipo _number_). Hay funciones similares llamadas `String` y `Boolean` que convierten valores a esos tipos. -Aquí está la representación esquemática bastante trivial del flujo de control en línea recta: +Aquí está la más bien trivial representación esquemática del flujo de control en línea recta: {{figure {url: "img/controlflow-straight.svg", alt: "Diagrama mostrando una flecha recta", width: "4cm"}}} ## Ejecución condicional -{{index Boolean, ["control de flujo", condicional]}} +{{index Boolean, ["flujo de control", condicional]}} No todos los programas son caminos rectos. Podríamos, por ejemplo, querer crear una carretera ramificada donde el programa tome la rama adecuada basada en la situación en cuestión. Esto se llama _((ejecución condicional))_. @@ -253,7 +255,7 @@ No todos los programas son caminos rectos. Podríamos, por ejemplo, querer crear {{index [sintaxis, sentencia], "función Number", "palabra clave if"}} -La ejecución condicional se crea con la palabra clave `if` en JavaScript. En el caso simple, queremos que cierto código se ejecute si, y solo si, una cierta condición es verdadera. Por ejemplo, podríamos querer mostrar el cuadrado de la entrada solo si la entrada es realmente un número: +La ejecución condicional se crea con la palabra clave `if` en JavaScript. La idea es que queremos que cierto código se ejecute si, y solo si, una cierta condición es verdadera. Por ejemplo, podríamos querer mostrar el cuadrado de la entrada solo si la entrada es realmente un número: ```{test: wrap} let elNumero = Number(prompt("Elige un número")); @@ -267,7 +269,7 @@ Con esta modificación, si introduces "loro", no se mostrará ninguna salida. {{index ["paréntesis", sentencia]}} -La palabra clave `if` ejecuta o salta una sentencia dependiendo del valor de una expresión booleana. La expresión de decisión se escribe después de la palabra clave, entre paréntesis, seguida de la sentencia a ejecutar. +La palabra clave `if` ejecuta o salta una sentencia dependiendo del valor de una expresión booleana. La expresión de decisión (la condición) se escribe después de la palabra clave, entre paréntesis, seguida de la sentencia a ejecutar. {{index "función Number.isNaN"}} @@ -275,7 +277,7 @@ La función `Number.isNaN` es una función estándar de JavaScript que devuelve {{index "agrupación", "{} (bloque)", [llaves, "bloque"]}} -La sentencia después del `if` está envuelta entre llaves (`{` y `}`) en este ejemplo. Las llaves se pueden usar para agrupar cualquier cantidad de sentencias en una sola sentencia, llamada un _((bloque))_. También podrías haber omitido en este caso, ya que contienen solo una sentencia, pero para evitar tener que pensar si son necesarias, la mayoría de los programadores de JavaScript las usan en cada sentencia envuelta de esta manera. Seguiremos principalmente esa convención en este libro, excepto por los casos ocasionales de una sola línea. +La sentencia después del `if` está envuelta entre llaves (`{` y `}`) en este ejemplo. Las llaves se pueden usar para agrupar cualquier cantidad de sentencias en una sola sentencia llamada _((bloque))_. También las podrías haber omitido en este caso, ya que contienen solo una sentencia, pero para evitar tener que pensar si son necesarias, la mayoría de los programadores de JavaScript las usan en todas las sentencias que forman parte de un `if`. Seguiremos principalmente esa convención en este libro, excepto por ocasionales expresiones de una sola línea (o _one-liners_). ``` if (1 + 1 == 2) console.log("Es verdad"); @@ -284,7 +286,7 @@ if (1 + 1 == 2) console.log("Es verdad"); {{index "else keyword"}} -A menudo no solo tendrás código que se ejecuta cuando una condición es verdadera, sino también código que maneja el otro caso. Esta ruta alternativa está representada por la segunda flecha en el diagrama. Puedes usar la palabra clave `else`, junto con `if`, para crear dos caminos de ejecución alternativos y separados: +A menudo no solo tendrás código que se ejecuta cuando una condición es verdadera, sino también código que se encarga de lo que ocurre en caso contrario. Esta ruta alternativa está representada por la segunda flecha en el diagrama. Puedes usar la palabra clave `else`, junto con `if`, para crear dos caminos de ejecución alternativos y separados: ```{test: wrap} let elNumero = Number(prompt("Elige un número")); @@ -292,13 +294,13 @@ if (!Number.isNaN(elNumero)) { console.log("Tu número es la raíz cuadrada de " + elNumero * elNumero); } else { - console.log("Oye. ¿Por qué no me diste un número?"); + console.log("Oye. ¿Por qué no me has dado un número?"); } ``` {{index ["if keyword", chaining]}} -Si tienes más de dos caminos para elegir, puedes "encadenar" múltiples pares `if`/`else`. Aquí tienes un ejemplo: +Si tienes más de dos caminos entre los que elegir, puedes "encadenar" múltiples pares `if`/`else`. Aquí tienes un ejemplo: ``` let num = Number(prompt("Escoge un número")); @@ -321,7 +323,7 @@ El esquema de este programa se ve más o menos así: {{id loops}} ## Bucles while y do -Considera un programa que imprime todos los números pares de 0 a 12. Una forma de escribirlo es la siguiente: +Considera un programa que muestre en la consola todos los números pares de 0 a 12. Una forma de escribirlo es la siguiente: ``` console.log(0); @@ -335,13 +337,13 @@ console.log(12); {{index ["control flow", loop]}} -Eso funciona, pero la idea de escribir un programa es hacer _menos_ trabajo, no más. Si necesitáramos todos los números pares menores que 1,000, este enfoque sería inviable. Lo que necesitamos es una manera de ejecutar un fragmento de código múltiples veces. Esta forma de control de flujo se llama _((bucle))_. +Y eso funciona, pero la idea de escribir un programa es hacer _menos_ trabajo, no más. Si necesitáramos todos los números pares menores que 1000, este enfoque sería inviable. Lo que necesitamos es una manera de ejecutar un fragmento de código múltiples veces. Esta forma de flujo de control se llama _((bucle))_. {{figure {url: "img/controlflow-loop.svg", alt: "Diagrama que muestra una flecha que apunta a un punto que tiene una flecha cíclica que regresa a sí mismo y otra flecha que continúa", width: "4cm"}}} {{index [syntax, statement], "variable de contador"}} -El control de flujo mediante bucles nos permite regresar a algún punto en el programa donde estábamos antes y repetirlo con nuestro estado de programa actual. Si combinamos esto con una variable que cuente, podemos hacer algo como esto: +El flujo de control mediante bucles nos permite regresar a algún punto en el programa donde estábamos antes y repetirlo con nuestro estado de programa actual. Si combinamos esto con una variable contadora, podemos hacer algo como esto: ``` let numero = 0; @@ -356,36 +358,36 @@ while (numero <= 12) { {{index "while loop", Boolean, [parentheses, statement]}} -Una ((sentencia)) que comienza con la palabra clave `while` crea un bucle. La palabra `while` va seguida de una ((expresión)) entre paréntesis y luego un enunciado, similar a `if`. El bucle sigue ejecutando ese enunciado mientras la expresión produzca un valor que se convierta en `true` al convertirse a Booleano. +Una ((sentencia)) que empiece con la palabra clave `while` crea un bucle. La palabra `while` va seguida de una ((expresión)) entre paréntesis y luego una sentencia, como con el `if`. El bucle sigue ejecutando esa sentencia mientras la expresión del paréntesis produzca un valor que dé `true` al convertirse a Booleano. {{index [estado, "en enlace"], [enlace, "como estado"]}} -El enlace 'numero' demuestra la forma en que un ((enlace)) puede seguir el progreso de un programa. Cada vez que se repite el bucle, 'numero' obtiene un valor que es 2 más que su valor anterior. Al comienzo de cada repetición, se compara con el número 12 para decidir si el trabajo del programa ha terminado. +El enlace `numero` demuestra la forma en que un ((enlace)) puede seguir el progreso de un programa. Cada vez que se repite el bucle, `numero` obtiene un valor que es 2 unidades más que su valor anterior. Al comienzo de cada repetición, se compara con el número 12 para decidir si el trabajo del programa ha terminado. {{index "exponenciación"}} Como ejemplo de algo realmente útil, ahora podemos escribir un programa que calcule y muestre el valor de 2^10^ (2 elevado a la 10ª potencia). Usamos dos enlaces: uno para llevar un seguimiento de nuestro resultado y otro para contar cuántas veces hemos multiplicado este resultado por 2. El bucle comprueba si el segundo enlace ya ha alcanzado 10 y, si no, actualiza ambos enlaces. ``` -let result = 1; -let counter = 0; -while (counter < 10) { - result = result * 2; - counter = counter + 1; +let resultado = 1; +let contador = 0; +while (contador < 10) { + resultado = resultado * 2; + contador = contador + 1; } -console.log(result); +console.log(resultado); // → 1024 ``` -El contador también podría haber comenzado en `1` y haber comprobado si era `<= 10`, pero por razones que se harán evidentes en el [Capítulo ?](data#array_indexing), es buena idea acostumbrarse a contar desde 0. +El contador también podría haber comenzado en `1` y haber comprobado si era `<= 10`, pero por razones que se harán evidentes en el [Capítulo ?](data#array_indexing), conviene acostumbrarse a contar desde 0. {{index "** operador"}} -Ten en cuenta que JavaScript también tiene un operador para la potencia (`2 ** 10`), que usarías para calcular esto en un código real, pero eso habría arruinado el ejemplo. +Ten en cuenta que JavaScript también tiene un operador para la potencia (`2 ** 10`), que sería lo que usarías para calcular esto en un código real —pero entonces nos quedaríamos sin ejemplo. {{index "cuerpo del bucle", "bucle do", ["flujo de control", bucle]}} -Un bucle `do` es una estructura de control similar a un bucle `while`. La única diferencia radica en que un bucle `do` siempre ejecuta su cuerpo al menos una vez, y comienza a probar si debe detenerse solo después de esa primera ejecución. Para reflejar esto, la prueba aparece después del cuerpo del bucle: +Un bucle `do` es una estructura de control similar a un bucle `while`. La única diferencia radica en que un bucle `do` siempre ejecuta su cuerpo al menos una vez, y comienza a probar si debe detenerse solo después de esa primera ejecución. Para reflejar esto, podemos hacer una comprobación después del cuerpo del bucle: ``` let tuNombre; @@ -397,36 +399,36 @@ console.log("Hola " + tuNombre); {{index [Booleano, "conversión a"], operador "!"}} -Este programa te obligará a ingresar un nombre. Preguntará una y otra vez hasta que obtenga algo que no sea una cadena vacía. Aplicar el operador `!` convertirá un valor al tipo Booleano antes de negarlo, y todas las cadenas excepto `""` se convierten en `true`. Esto significa que el bucle continúa hasta que proporciones un nombre no vacío. +Este programa te obligará a introducir un nombre. Preguntará una y otra vez hasta que obtenga algo que no sea una cadena vacía. Aplicar el operador `!` convertirá un valor al tipo Booleano antes de negarlo, y todas las cadenas excepto `""` se convierten en `true`. Esto significa que el bucle continúa hasta que proporciones un nombre no vacío. ## Sangrado de Código {{index ["código", "estructura de"], [espacios en blanco, sangrado], "estilo de programación"}} -En los ejemplos, he estado agregando espacios delante de las sentencias que son parte de alguna otra sentencia más grande. Estos espacios no son necesarios: la computadora aceptará el programa perfectamente sin ellos. De hecho, incluso los ((saltos)) de línea en los programas son opcionales. Podrías escribir un programa como una sola línea larga si así lo deseas. +En los ejemplos, he estado agregando espacios delante de cada sentencia que forma parte de alguna otra sentencia más grande. Estos espacios no son necesarios: la computadora aceptará el programa perfectamente sin ellos. De hecho, incluso los ((saltos)) de línea en los programas son opcionales. Podrías escribir un programa como una sola línea larga si quisieras. -El papel de este ((sangrado)) dentro de los ((bloque))s es hacer que la estructura del código resalte para los lectores humanos. En el código donde se abren nuevos bloques dentro de otros bloques, puede volverse difícil ver dónde termina un bloque y comienza otro. Con un sangrado adecuado, la forma visual de un programa corresponde a la forma de los bloques dentro de él. A mí me gusta usar dos espacios para cada bloque abierto, pero los gustos difieren: algunas personas usan cuatro espacios y otras usan ((caracteres de tabulación)). Lo importante es que cada nuevo bloque agregue la misma cantidad de espacio. +El papel de este ((sangrado)) dentro de los ((bloque))s es resaltar la estructura del código para los lectores humanos. En código donde se abren nuevos bloques dentro de otros bloques, puede hacerse complicado ver dónde termina un bloque y comienza otro. Con un sangrado adecuado, la forma visual de un programa corresponde a la forma de los bloques dentro de él. A mí me gusta usar dos espacios para cada bloque abierto, pero los gustos difieren: algunas personas usan cuatro espacios y otras usan ((caracteres de tabulación)). Lo importante es que cada nuevo bloque agregue la misma cantidad de espacio. ``` if (false != true) { console.log("Tiene sentido."); if (1 < 2) { - console.log("No hay sorpresas ahí."); + console.log("Sin sorpresas."); } } ``` La mayoría de los programas de edición (incluido el de este libro) ayudarán automáticamente con la sangría adecuada al escribir nuevas líneas. -## bucles for +## Bucles for {{index [sintaxis, "declaración"], "bucle while", "variable de contador"}} -Muchos bucles siguen el patrón mostrado en los ejemplos de `while`. Primero se crea una variable de "contador" para rastrear el progreso del bucle. Luego viene un bucle `while`, generalmente con una expresión de prueba que verifica si el contador ha alcanzado su valor final. Al final del cuerpo del bucle, el contador se actualiza para rastrear el progreso. +Muchos bucles siguen el patrón mostrado en los ejemplos de `while`. Primero se crea una variable "contador" para rastrear el progreso del bucle. Luego viene un bucle `while`, generalmente con una expresión de prueba que verifica si el contador ha alcanzado su valor final. Al final del cuerpo del bucle, el contador se actualiza para rastrear el progreso. {{index "bucle for", bucle}} -Debido a que este patrón es tan común, JavaScript y lenguajes similares proporcionan una forma ligeramente más corta y completa, el bucle `for`: +Debido a que este patrón es tan común, JavaScript y lenguajes similares proporcionan una forma ligeramente más corta y entendible, el bucle `for`: ``` for (let numero = 0; numero <= 12; numero = numero + 2) { @@ -439,11 +441,11 @@ for (let numero = 0; numero <= 12; numero = numero + 2) { {{index ["flujo de control", bucle], estado}} -Este programa es exactamente equivalente al [anterior](program_structure#loops) ejemplo de impresión de números pares. La única diferencia es que todas las ((declaraciones)) relacionadas con el "estado" del bucle están agrupadas después de `for`. +Este programa es exactamente equivalente al [anterior](program_structure#loops) ejemplo de impresión de números pares en la consola. La única diferencia es que todas las ((declaraciones)) relacionadas con el "estado" del bucle están agrupadas después de `for`. {{index [variable, como estado], ["paréntesis", "declaración"]}} -Los paréntesis después de la palabra clave `for` deben contener dos ((punto y coma)). La parte antes del primer punto y coma _inicializa_ el bucle, generalmente definiendo una variable. La segunda parte es la ((expresión)) que _verifica_ si el bucle debe continuar. La parte final _actualiza_ el estado del bucle después de cada iteración. En la mayoría de los casos, esto es más corto y claro que un `while` tradicional. +Los paréntesis después de la palabra clave `for` deben contener dos ((punto y coma)). La parte antes del primer punto y coma _inicializa_ el bucle, normalmente definiendo una variable. La segunda parte es la ((expresión)) que _verifica_ si el bucle debe continuar. La parte final _actualiza_ el estado del bucle después de cada iteración. En la mayoría de los casos, esto es más corto y claro que un `while` tradicional. {{index "exponenciación"}} @@ -462,7 +464,7 @@ console.log(resultado); {{index [bucle, "terminación de"], "palabra clave break"}} -Hacer que la condición del bucle produzca `false` no es la única forma en que un bucle puede terminar. La instrucción `break` tiene el efecto de salir inmediatamente del bucle que la contiene. Su uso se demuestra en el siguiente programa, que encuentra el primer número que es mayor o igual a 20 y divisible por 7: +Hacer que la condición del bucle produzca `false` no es la única forma en que un bucle puede terminar. La instrucción `break` tiene el efecto de salir inmediatamente del bucle que la contiene. Su uso se demuestra en el siguiente programa, que encuentra el primer número mayor o igual a 20 que es divisible por 7: ``` for (let actual = 20; ; actual = actual + 1) { @@ -482,11 +484,11 @@ Usar el operador de resto (`%`) es una forma sencilla de comprobar si un número La construcción `for` en el ejemplo no tiene una parte que verifique el final del bucle. Esto significa que el bucle nunca se detendrá a menos que se ejecute la instrucción `break` dentro de él. -Si eliminaras esa declaración `break` o escribieses accidentalmente una condición final que siempre produzca `true`, tu programa quedaría atrapado en un _((bucle infinito))_. Un programa atrapado en un bucle infinito nunca terminará de ejecutarse, lo cual suele ser algo malo. +Si eliminaras esa declaración `break` o escribieses accidentalmente una condición final que siempre produzca `true`, tu programa quedaría atrapado en un _((bucle infinito))_. Un programa atrapado en un bucle infinito nunca terminará de ejecutarse, lo cual suele ser malo. {{if interactive -Si creas un bucle infinito en uno de los ejemplos en estas páginas, generalmente se te preguntará si deseas detener el script después de unos segundos. Si eso falla, deberás cerrar la pestaña en la que estás trabajando para recuperarte. +Si creas un bucle infinito en uno de los ejemplos en estas páginas, generalmente se te preguntará si deseas detener el script después de unos segundos. Si eso falla, deberás cerrar la pestaña en la que estás trabajando para pararlo. if}} @@ -501,34 +503,34 @@ La palabra clave `continue` es similar a `break` en que influye en el progreso d Especialmente al hacer bucles, un programa a menudo necesita "actualizar" un enlace para que contenga un valor basado en el valor anterior de ese enlace. ```{test: no} -counter = counter + 1; +contador = contador + 1; ``` JavaScript proporciona un atajo para esto: ```{test: no} -counter += 1; +contador += 1; ``` -Atajos similares funcionan para muchos otros operadores, como `result *= 2` para duplicar `result` o `counter -= 1` para contar hacia atrás. +Para muchos otros operadores hay atajos similares, como `resultado *= 2` para duplicar `resultado` o `contador -= 1` para contar hacia atrás. Esto nos permite acortar aún más nuestro ejemplo de contar: ``` -for (let number = 0; number <= 12; number += 2) { - console.log(number); +for (let número = 0; número <= 12; número += 2) { + console.log(número); } ``` {{index "++ operator", "-- operator"}} -Para `counter += 1` y `counter -= 1`, existen equivalentes aún más cortos: `counter++` y `counter--`. +Para `contador += 1` y `contador -= 1`, existen equivalentes más cortos aún: `contador++` y `contador--`. -## Despachar un valor con switch +## Despachar según un valor con switch {{index [syntax, statement], "conditional execution", dispatch, ["if keyword", chaining]}} -No es raro que el código luzca así: +No es raro encontrar código con esta pinta: ```{test: no} if (x == "valor1") accion1(); @@ -539,7 +541,7 @@ else accionPredeterminada(); {{index "colon character", "switch keyword"}} -Existe una construcción llamada `switch` que está destinada a expresar dicho "despacho" de una manera más directa. Desafortunadamente, la sintaxis que JavaScript utiliza para esto (heredada de la línea de lenguajes de programación C/Java) es algo incómoda; una cadena de declaraciones `if` puede verse mejor. Aquí hay un ejemplo: +Existe una construcción llamada `switch` que está destinada a expresar dicho "despacho" de una manera más directa. Desafortunadamente, la sintaxis que JavaScript utiliza para esto (heredada de lenguajes de programación en la línea de C/Java) es algo incómoda —una cadena de declaraciones `if` podría quedar mejor. Aquí hay un ejemplo: ``` switch (prompt("¿Cómo está el clima?")) { @@ -547,7 +549,7 @@ switch (prompt("¿Cómo está el clima?")) { console.log("Recuerda llevar un paraguas."); break; case "soleado": - console.log("Vístete ligero."); + console.log("Vístete con ropa ligera."); case "nublado": console.log("Sal al exterior."); break; @@ -559,13 +561,13 @@ switch (prompt("¿Cómo está el clima?")) { {{index fallthrough, "break keyword", "case keyword", "default keyword"}} -Puedes colocar cualquier cantidad de etiquetas `case` dentro del bloque abierto por `switch`. El programa comenzará a ejecutarse en la etiqueta que corresponda al valor que se le dio a `switch`, o en `default` si no se encuentra ningún valor coincidente. Continuará ejecutándose, incluso a través de otras etiquetas, hasta que alcance una declaración `break`. En algunos casos, como el caso `"soleado"` en el ejemplo, esto se puede usar para compartir algo de código entre casos (recomienda salir al exterior tanto para el clima soleado como para el nublado). Sin embargo, ten cuidado, es fácil olvidar un `break` de este tipo, lo que hará que el programa ejecute código que no deseas ejecutar. +Puedes colocar cualquier cantidad de etiquetas `case` dentro del cuerpo de `switch`. El programa comenzará a ejecutarse en la etiqueta que corresponda al valor que se le dio a `switch`, o en `default`, si no se encuentra ningún valor coincidente. Continuará ejecutándose, incluso a través de otras etiquetas, hasta que alcance una declaración `break`. En algunos casos, como el caso `"soleado"` del ejemplo, esto se puede usar para compartir algo de código entre casos (recomienda salir al exterior tanto para el clima soleado como para el nublado). Pero ten cuidado, es fácil olvidar un `break` de este tipo, lo que hará que el programa ejecute código que no deseas ejecutar. ## Uso de mayúsculas {{index "capitalización", [binding, nombrar], [espacios en blanco, sintaxis]}} -Los nombres de los enlaces no pueden contener espacios, sin embargo, 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 @@ -576,7 +578,7 @@ fuzzyLittleTurtle {{index "camel case", "estilo de programación", "carácter de subrayado"}} -El primer estilo puede ser difícil de leer. Personalmente me gusta más la apariencia de los guiones bajos, aunque ese estilo es un poco difícil de escribir. Las funciones estándar de ((JavaScript)) y la mayoría de los programadores de JavaScript siguen el último estilo: escriben con mayúscula cada palabra excepto la primera. No es difícil acostumbrarse a pequeñas cosas como esa, y el código con estilos de nombrado mixtos puede resultar molesto de leer, así que seguimos esta ((convención)). +El primer estilo puede ser difícil de leer. Personalmente me gusta más la apariencia de los guiones bajos, aunque ese estilo es un poco difícil de escribir. Las funciones estándar de ((JavaScript)) y la mayoría de los programadores de JavaScript siguen el último estilo: escriben con mayúscula cada palabra excepto la primera. No es difícil adoptar pequeñas costumbres como esta, y el código con estilos de nombrado mixtos puede resultar molesto de leer, así que seguiremos esta última ((convención)). {{index "Función Número", constructor}} @@ -586,7 +588,7 @@ En algunos casos, como en la función `Number`, la primera letra de un enlace ta {{index legibilidad}} -A menudo, el código sin formato no transmite toda la información que deseas que un programa transmita a los lectores humanos, o lo hace de una manera tan críptica que las personas podrían no entenderlo. En otros momentos, es posible que solo quieras incluir algunos pensamientos relacionados como parte de tu programa. Para eso sirven los _((comentarios))_. +A menudo, el código sin formato no transmite toda la información que quieres que un programa transmita a los lectores humanos, o lo hace de una manera tan críptica que la gente podría no entenderlo. Otras veces, es posible que solo quieras incluir algunos pensamientos relacionados como parte de tu programa. Para eso sirven los _((comentarios))_. {{index "carácter de barra", "comentario de línea"}} @@ -594,23 +596,23 @@ Un comentario es un fragmento de texto que forma parte de un programa pero que e ```{test: no} let saldoCuenta = calcularSaldo(cuenta); -// Es un hueco verde donde canta un río +// Al olmo viejo, hendido por el rayo saldoCuenta.ajustar(); -// Atrapando locamente pedazos blancos en la hierba. +// y en su mitad podrido, let informe = new Informe(); -// Donde el sol en la orgullosa montaña resuena: +// con las lluvias de abril y el sol de mayo agregarAInforme(saldoCuenta, informe); -// Es un valle pequeño, espumoso como la luz en un vaso. +// algunas hojas verdes le han salido. ``` {{index "comentario de bloque"}} -Un comentario con `//` solo va hasta el final de la línea. Una sección de texto entre `/*` y `*/` será ignorada por completo, independientemente de si contiene saltos de línea. Esto es útil para agregar bloques de información sobre un archivo o un fragmento de programa: +Un comentario con `//` solo va hasta el final de la línea. Una sección de texto entre `/*` y `*/` será ignorada por completo, independientemente de si contiene saltos de línea o no. Esto es útil para agregar bloques de información sobre un archivo o un fragmento de programa: ``` /* - Encontré este número por primera vez garabateado en la parte posterior de un viejo - cuaderno. Desde entonces, a menudo ha aparecido, mostrándose en + Encontré este número por primera vez garabateado en la parte de atrás de un viejo + cuaderno. Desde entonces, ha aparecido con frecuencia en números de teléfono y números de serie de productos que he comprado. Obviamente le gusto, así que he decidido quedármelo. */ @@ -619,9 +621,9 @@ const miNumero = 11213; ## Resumen -Ahora sabes que un programa está construido a partir de declaraciones, que a veces contienen más declaraciones. Las declaraciones tienden a contener expresiones, que a su vez pueden estar construidas a partir de expresiones más pequeñas. Poner declaraciones una después de la otra te da un programa que se ejecuta de arriba hacia abajo. Puedes introducir alteraciones en el flujo de control usando declaraciones condicionales (`if`, `else` y `switch`) y bucles (`while`, `do` y `for`). +Ahora sabes que un programa está construido a partir de declaraciones (o sentencias), que a veces contienen más declaraciones. Las declaraciones tienden a contener expresiones, que a su vez pueden estar construidas a partir de expresiones más pequeñas. Poner declaraciones una después de la otra te da un programa que se ejecuta de arriba hacia abajo. Puedes introducir alteraciones en el flujo de control usando sentencias condicionales (`if`, `else` y `switch`) y bucles (`while`, `do` y `for`). -Los enlaces se pueden usar para guardar fragmentos de datos bajo un nombre, y son útiles para hacer un seguimiento del estado en tu programa. El entorno es el conjunto de enlaces que están definidos. Los sistemas de JavaScript siempre colocan varios enlaces estándar útiles en tu entorno. +Los enlaces se pueden usar para guardar fragmentos de datos bajo un nombre, y son útiles para hacer un seguimiento del estado en tu programa. El entorno es el conjunto de enlaces que están definidos. Los sistemas de JavaScript siempre ponen varios enlaces estándar útiles en tu entorno. Las funciones son valores especiales que encapsulan un fragmento de programa. Puedes invocarlas escribiendo `nombreDeFuncion(argumento1, argumento2)`. Dicha llamada a función es una expresión y puede producir un valor. @@ -629,9 +631,9 @@ Las funciones son valores especiales que encapsulan un fragmento de programa. Pu {{index ejercicios}} -Si no estás seguro de cómo probar tus soluciones a los ejercicios, consulta la [Introducción](intro). +Si no sabes cómo comprobar tus soluciones a los ejercicios, consulta la [Introducción](intro). -Cada ejercicio comienza con una descripción del problema. Lee esta descripción e intenta resolver el ejercicio. Si encuentras problemas, considera leer las pistas [después del ejercicio]{if interactive}[al [final del libro](hints)]{if book}. Puedes encontrar soluciones completas a los ejercicios en línea en [_https://eloquentjavascript.net/code_](https://eloquentjavascript.net/code#2). Si deseas aprender algo de los ejercicios, te recomiendo mirar las soluciones solo después de haber resuelto el ejercicio, o al menos después de haberlo intentado lo suficiente como para tener un ligero dolor de cabeza. +Cada ejercicio comienza con una descripción del problema. Léela e intenta resolver el ejercicio. Si tienes problemas, considera leer las pistas [después del ejercicio]{if interactive}[al [final del libro](hints)]{if book}. Puedes encontrar soluciones completas a los ejercicios en línea en [_https://eloquentjavascript.net/code_](https://eloquentjavascript.net/code#2). Si quieres aprender algo de los ejercicios, te recomiendo mirar las soluciones solo después de haber resuelto el ejercicio, o al menos después de haberlo intentado lo suficiente como para tener un ligero dolor de cabeza. ### Haciendo un triángulo con bucles @@ -651,7 +653,7 @@ Escribe un ((bucle)) que realice siete llamadas a `console.log` para mostrar el {{index [cadena, longitud]}} -Puede ser útil saber que puedes encontrar la longitud de una cadena escribiendo `.length` después de ella. +Puede ser útil saber que puedes calcular la longitud de una cadena escribiendo `.length` después de ella. ``` let abc = "abc"; @@ -674,7 +676,7 @@ if}} Puedes comenzar con un programa que imprime los números del 1 al 7, el cual puedes obtener haciendo algunas modificaciones al ejemplo de impresión de números pares dado anteriormente en el capítulo, donde se introdujo el bucle `for`. -Ahora considera la equivalencia entre los números y las cadenas de caracteres "#" . Puedes pasar de 1 a 2 sumando 1 (`+= 1`). Puedes pasar de `"#"` a `"##"` agregando un carácter (`+= "#"`). Por lo tanto, tu solución puede seguir de cerca el programa de impresión de números. +Luego, considera la equivalencia entre los números y las cadenas de caracteres "#" . Puedes pasar de 1 a 2 sumando 1 (`+= 1`). Puedes pasar de `"#"` a `"##"` agregando un carácter (`+= "#"`). Por lo tanto, tu solución puede basarse fuertemente en el programa de impresión de números. hint}} @@ -684,9 +686,9 @@ hint}} Escribe un programa que use `console.log` para imprimir todos los números del 1 al 100, con dos excepciones. Para los números divisibles por 3, imprime `"Fizz"` en lugar del número, y para los números divisibles por 5 (y no por 3), imprime `"Buzz"` en su lugar. -Cuando tengas eso funcionando, modifica tu programa para imprimir `"FizzBuzz"` para los números que son divisibles por 3 y 5 (y sigue imprimiendo `"Fizz"` o `"Buzz"` para los números que son divisibles solo por uno de esos). +Cuando eso esté listo, modifica tu programa para imprimir `"FizzBuzz"` para los números que son divisibles por 3 y 5 (y sigue imprimiendo `"Fizz"` o `"Buzz"` para los números que son divisibles solo por uno de esos). -(Esto es en realidad una ((pregunta de entrevista)) que se ha afirmado que elimina a un porcentaje significativo de candidatos a programadores. Entonces, si lo resolviste, tu valor en el mercado laboral acaba de aumentar.) +(Esto es en realidad una ((pregunta de entrevista)) que se ha afirmado que elimina a un porcentaje significativo de candidatos a programadores. Por tanto, si lo resolviste, tu valor en el mercado laboral acaba de aumentar.) {{if interactive ``` @@ -698,13 +700,13 @@ if}} {{index "FizzBuzz (exercise)", "remainder operator", "% operator"}} -Claramente, recorrer los números es un trabajo de bucle, y seleccionar qué imprimir es una cuestión de ejecución condicional. Recuerda el truco de usar el operador de resto (`%`) para verificar si un número es divisible por otro número (tiene un resto de cero). +Claramente, recorrer los números es tarea para un bucle, y seleccionar qué imprimir es una cuestión de ejecución condicional. Recuerda el truco de usar el operador de resto (`%`) para verificar si un número es divisible por otro número (tiene un resto de cero). -En la primera versión, hay tres resultados posibles para cada número, por lo que tendrás que crear una cadena `if`/`else if`/`else`. +En la primera versión, hay tres resultados posibles para cada número, por lo que tendrás que crear una secuencia `if`/`else if`/`else`. {{index "|| operator", ["if keyword", chaining]}} -La segunda versión del programa tiene una solución sencilla y una inteligente. La solución simple es agregar otra "rama" condicional para probar exactamente la condición dada. Para la solución inteligente, construye una cadena que contenga la palabra o palabras a imprimir e imprime esta palabra o el número si no hay palabra, potencialmente haciendo un buen uso del operador `||`. +La segunda versión del programa tiene una solución sencilla y una inteligente. La solución sencilla es agregar otra "rama" condicional para probar exactamente la condición dada. Para la solución inteligente, construye una cadena que contenga la palabra o palabras a imprimir e imprime esta palabra o el número si no hubiera palabra, potencialmente haciendo un buen uso del operador `||`. hint}} @@ -712,7 +714,7 @@ hint}} Escribe un programa que cree una cadena que represente un tablero de 8x8, usando caracteres de salto de línea para separar las líneas. En cada posición del tablero hay un carácter de espacio o un carácter "#". Los caracteres deben formar un tablero de ajedrez. -Al pasar esta cadena a `console.log` debería mostrar algo como esto: +Al pasar esta cadena a `console.log`, debería mostrar algo como esto: ```{lang: null} # # # # @@ -735,9 +737,9 @@ if}} {{hint -Para trabajar con dos dimensiones, necesitarás un bucle dentro de otro bucle. Pon llaves alrededor de los cuerpos de ambos bucles para que sea fácil ver dónde empiezan y terminan. Intenta indentar correctamente estos cuerpos. El orden de los bucles debe seguir el orden en el que construimos la cadena (línea por línea, de izquierda a derecha, de arriba abajo). Entonces el bucle exterior maneja las líneas y el bucle interior maneja los caracteres en una línea. +Para trabajar con dos dimensiones, necesitarás un bucle dentro de otro bucle. Pon llaves alrededor de los cuerpos de ambos bucles para que sea fácil ver dónde empiezan y terminan. Intenta añadir sangrado (o _indentar_) correctamente a estos cuerpos. El orden de los bucles debe seguir el orden en el que construimos la cadena (línea por línea, de izquierda a derecha, de arriba abajo). Entonces el bucle exterior maneja las líneas y el bucle interior maneja los caracteres en una línea. -Necesitarás dos variables para hacer un seguimiento de tu progreso. Para saber si debes colocar un espacio o un signo de hash en una posición determinada, podrías verificar si la suma de los dos contadores es par (`% 2`). +Necesitarás dos variables para hacer un seguimiento de tu progreso. Para saber si debes colocar un espacio o un signo de almohadilla en una posición determinada, podrías verificar si la suma de los dos contadores es par (`% 2`). Terminar una línea agregando un carácter de salto de línea debe ocurrir después de que se haya construido la línea, así que hazlo después del bucle interno pero dentro del bucle externo. diff --git a/03_functions.md b/03_functions.md index 0ce723ed..f8546a88 100644 --- a/03_functions.md +++ b/03_functions.md @@ -2,7 +2,7 @@ {{quote {author: "Donald Knuth", chapter: true} -La gente piensa que la informática es el arte de los genios, pero la realidad actual es la opuesta, simplemente muchas personas haciendo cosas que se construyen unas sobre otras, como un muro de mini piedras. +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. quote}} @@ -12,26 +12,27 @@ quote}} {{index "función", [code, "estructura de"]}} -Las funciones son una de las herramientas más centrales en la programación en JavaScript. El concepto de envolver un fragmento de programa en un valor tiene muchos usos. 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í. +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 escrito suele ser de mal gusto, pero en programación es indispensable. +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. {{index "abstracción", vocabulario}} -Los hablantes de inglés adultos típicos tienen alrededor de 20,000 palabras en su vocabulario. Pocos lenguajes de programación vienen con 20,000 comandos incorporados. Y el vocabulario que _está_ disponible tiende a estar más precisamente definido, y por lo tanto menos flexible, que en el lenguaje humano. Por lo tanto, _tenemos_ que introducir nuevas palabras para evitar la verbosidad excesiva. +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 {{index "ejemplo de cuadrado", ["función", "definición"], ["enlace", "definición"]}} -Una definición de función es un enlace habitual donde el valor del enlace es una función. Por ejemplo, este código define `square` para que se refiera a una función que produce el cuadrado de un número dado: +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 square = function(x) { +const cuadrado = function(x) { return x * x; }; -console.log(square(12)); +console.log(cuadrado(12)); // → 144 ``` @@ -39,51 +40,55 @@ console.log(square(12)); {{index ["corchetes", "cuerpo de la función"], "bloque", ["sintaxis", "función"], "palabra clave de función", ["función", "cuerpo"], ["función", "como valor"], ["paréntesis", "argumentos"]}} -Una función se crea con una expresión que comienza con la palabra clave `function`. Las funciones tienen un conjunto de _((parámetro))s_ (en este caso, 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 cuando consiste en una única ((declaración)). +Una función se crea con una expresión que comienza con la palabra clave `function`. Las funciones tienen un conjunto de _((parámetro))s_ (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)). {{index "ejemplo de roundTo"}} -Una función puede tener varios parámetros o ninguno en absoluto. En el siguiente ejemplo, `makeNoise` no enumera nombres de parámetros, mientras que `roundTo` (que redondea `n` al múltiplo más cercano de `step`) enumera dos: +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 makeNoise = function() { - console.log("¡Pling!"); +const hacerRuido = function() { + console.log("¡Cling!"); }; -makeNoise(); -// → ¡Pling! +hacerRuido(); +// → ¡Cling! -const roundTo = function(n, step) { - let resto = n % step; - return n - resto + (resto < step / 2 ? 0 : step); +const redondearA = function(n, paso) { + let resto = n % paso; + return n - resto + (resto < paso / 2 ? 0 : paso); }; -console.log(roundTo(23, 10)); +console.log(redondearA(23, 10)); // → 20 ``` {{index "valor de retorno", "palabra clave de retorno", indefinido}} -Algunas funciones, como `roundTo` y `square`, producen un valor, y otras no, como `makeNoise`, 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. Una palabra clave `return` sin una expresión después de ella hará que la función devuelva `undefined`. Las funciones que no tienen ninguna instrucción `return` en absoluto, como `makeNoise`, devuelven igualmente `undefined`. +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`. {{index "parámetro", ["función", "aplicación"], [enlace, "desde parámetro"]}} -Los parámetros de una función se comportan como enlaces habituales, pero sus valores iniciales son dados por el _llamador_ de la función, no por el código en la función en sí misma. +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 {{indexsee "ámbito de nivel superior", "ámbito global"}} {{index "palabra clave var", "ámbito global", [enlace, global], [enlace, "ámbito de"]}} -Cada enlace tiene un _((ámbito))_, que es la parte del programa en la que el enlace es visible. Para los enlaces definidos fuera de cualquier función, bloque o módulo (ver [Capítulo ?](modules)), el ámbito es todo el programa—puedes hacer referencia a esos enlaces donde quieras. Estos se llaman _globales_. +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 ?](modules)), el ámbito es todo el programa —puedes hacer referencia a esas asociaciones donde quieras. Estas asociaciones se llaman asociaciones _globales_. {{index "ámbito local", [enlace, local]}} -Los enlaces creados para los parámetros de una función o declarados dentro de una función solo pueden ser referenciados en esa función, por lo que se conocen como enlaces _locales_. Cada vez que se llama a la función, se crean nuevas instancias de estos enlaces. Esto proporciona cierto aislamiento entre funciones—cada llamada a función actúa en su propio 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 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. {{index "palabra clave let", "palabra clave const", "palabra clave var"}} -Los enlaces declarados con `let` y `const` en realidad son locales al _((bloque))_ en el que se declaran, por lo que si creas uno de ellos dentro de un bucle, el código antes y después del bucle no puede "verlo". En JavaScript anterior a 2015, solo las funciones creaban nuevos ámbitos, por lo que los enlaces de estilo antiguo, creados con la palabra clave `var`, son visibles en toda función en la que aparecen—o en todo el ámbito global, si no están dentro de una función. +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. + + +{{note "**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 @@ -95,15 +100,15 @@ if (true) { {{index [enlace, visibilidad]}} -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 enlaces tienen el mismo nombre—en ese caso, el código solo puede ver el más interno. Por ejemplo, cuando el código dentro de la función `halve` hace referencia a `n`, está viendo su _propio_ `n`, no el `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 halve = function(n) { +const mitad = function(n) { return n / 2; }; let n = 10; -console.log(halve(100)); +console.log(mitad(100)); // → 50 console.log(n); // → 10 @@ -115,52 +120,54 @@ console.log(n); {{index [anidamiento, "de funciones"], [anidamiento, "de ámbito"], "ámbito", "función interna", "ámbito léxico"}} -JavaScript distingue no solo entre enlaces globales y locales. Bloques y funciones pueden ser creados dentro de otros bloques y funciones, produciendo múltiples grados de localidad. +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. {{index "ejemplo de paisaje"}} -Por ejemplo, esta función—que muestra los ingredientes necesarios para hacer un lote de hummus—tiene otra función dentro de ella: +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 ingredient = function(amount, unit, name) { - let ingredientAmount = amount * factor; - if (ingredientAmount > 1) { - unit += "s"; + const ingrediente = function(cantidad, unidad, nombre) { + let cantidadDeIngrediente = cantidad * factor; + if (cantidadDeIngrediente != 1) { + unidad += "s de"; + } else { + unidad += " de"; } - console.log(`${ingredientAmount} ${unit} ${name}`); + console.log(`${cantidadDeIngrediente} ${unidad} ${nombre}`); }; - ingredient(1, "lata", "garbanzos"); - ingredient(0.25, "taza", "tahini"); - ingredient(0.25, "taza", "jugo de limón"); - ingredient(1, "diente", "ajo"); - ingredient(2, "cucharada", "aceite de oliva"); - ingredient(0.5, "cucharadita", "comino"); + 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"); }; ``` {{index ["función", alcance], alcance}} -El código dentro de la función `ingredient` puede ver el enlace `factor` de la función exterior, pero sus enlaces locales, como `unit` o `ingredientAmount`, no son visibles en la función exterior. +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 enlaces visibles dentro de un bloque está determinado por el lugar de ese bloque en el texto del programa. Cada bloque local también puede ver todos los bloques locales que lo contienen, y todos los bloques pueden ver el bloque global. Este enfoque de visibilidad de enlaces se llama _((ámbito léxico))_. +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 {{index ["función", "como valor"], [enlace, "definición"]}} -Generalmente un enlace de función simplemente actúa como un nombre para una parte específica del programa. Este enlace se define una vez y nunca se cambia. Esto hace que sea fácil confundir la función y su nombre. +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. {{index [enlace, "asignación"]}} -Pero los dos son diferentes. Un valor de función puede hacer todas las cosas que pueden hacer otros valores: se puede utilizar en expresiones arbitrarias, no solo llamarlo. Es posible almacenar un valor de función en un nuevo enlace, pasarlo como argumento a una función, etc. De manera similar, un enlace que contiene una función sigue siendo solo un enlace habitual y, si no es constante, se le puede asignar un nuevo valor, así: +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í: ```{test: no} -let launchMissiles = function() { - missileSystem.launch("now"); +let lanzarMisiles = function() { + sistemaDeMisil.lanzar("ahora"); }; -if (safeMode) { - launchMissiles = function() {/* no hacer nada */}; +if (modoSeguro) { + lanzarMisiles = function() {/* no hacer nada */}; } ``` @@ -172,50 +179,50 @@ En el [Capítulo ?](higher_order), discutiremos las cosas interesantes que podem {{index [sintaxis, "función"], "palabra clave función", "ejemplo cuadrado", ["función", "definición"], ["función", "declaración"]}} -Hay una manera ligeramente más corta de crear un enlace de función. Cuando se utiliza la palabra clave `function` al inicio de una declaración, funciona de manera diferente: +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: ```{test: wrap} -function square(x) { +function cuadrado(x) { return x * x; } ``` {{index futura, "orden de ejecución"}} -Esta es una función _declarativa_. La declaración define el enlace `square` y lo 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. +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 definición de función. +Hay una sutileza con esta forma de definir una función. ``` -console.log("El futuro dice:", future()); +console.log("El futuro dice:", futuro()); -function future() { - return "Nunca tendrás autos voladores"; +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 declaraciones de función no forman parte del flujo de control regular de arriba hacia abajo. Conceptualmente se mueven al principio de su alcance y pueden ser utilizadas por todo el código en ese alcance. 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. +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 de flecha +## Funciones flecha -{{index "función", "función de flecha"}} +{{index "función", "función flecha"}} -Hay una tercera notación para funciones, que se ve muy diferente de 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 roundTo = (n, step) => { - let remainder = n % step; - return n - remainder + (remainder < step / 2 ? 0 : step); +const redondearA = (n, paso) => { + let resto = n % paso; + return n - resto + (resto < paso / 2 ? 0 : paso); }; ``` {{index [function, body]}} -La flecha viene _después_ de la lista de parámetros y es seguida por el cuerpo de la función. Expresa algo así como "esta entrada (los ((parámetros))) produce este resultado (el cuerpo)". +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)". {{index [braces, "function body"], "ejemplo de exponente", ["paréntesis", argumentos]}} -Cuando solo hay un nombre de parámetro, puedes 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, esa expresión será devuelta por la función. Por lo tanto, estas dos definiciones de `exponente` hacen lo mismo: +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; }; @@ -224,17 +231,17 @@ const exponente2 = x => x * x; {{index ["paréntesis", argumentos]}} -Cuando una función de flecha no tiene parámetros en absoluto, su lista de parámetros es simplemente un conjunto vacío de paréntesis. +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("Toot"); + console.log("Honk"); }; ``` {{index verbosidad}} -No hay una razón profunda para tener tanto funciones de flecha como expresiones `function` en el lenguaje. Aparte de un detalle menor, que discutiremos en el [Capítulo ?](object), hacen lo mismo. Las funciones de flecha se agregaron 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 ?](higher_order) . +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 ?](object), 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 ?](higher_order) . {{id pila}} @@ -243,11 +250,11 @@ No hay una razón profunda para tener tanto funciones de flecha como expresiones {{indexsee pila, "pila de llamadas"}} {{index "pila de llamadas", ["función", "aplicación"]}} -La forma en que el control fluye a través de las funciones es un tanto complicada. Echemos un vistazo más de cerca. Aquí hay un programa simple que realiza algunas llamadas de función: +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(quien) { - console.log("Hola " + quien); +function saludar(quién) { + console.log("Hola " + quién); } saludar("Harry"); console.log("Adiós"); @@ -255,38 +262,38 @@ console.log("Adiós"); {{index ["flujo de control", funciones], "orden de ejecución", "console.log"}} -Una ejecución de este programa va 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 que la llamó, línea 4. La línea siguiente llama a `console.log` nuevamente. Después de ese retorno, el programa llega a su fin. +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 de esta manera: +Podríamos mostrar el flujo de control esquemáticamente como sigue: ```{lang: null} -no en función +fuera de función en saludar en console.log en saludar -no en función +fuera de función en console.log -no en función +fuera de función ``` {{index "palabra clave return", [memoria, pila de llamadas]}} -Dado que una función tiene que regresar al lugar que la 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. +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 almacena en la parte superior de esta pila. Cuando una función devuelve, elimina el contexto superior de la pila y usa ese contexto para continuar la ejecución. +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. {{index "bucle infinito", "desbordamiento de pila", "recursió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 la tiene, nos quedaremos sin espacio o "reventaremos la pila". +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". ```{test: no} -function chicken() { - return egg(); +function gallina() { + return huevo(); } -function egg() { - return chicken(); +function huevo() { + return gallina(); } -console.log(chicken() + " salió primero."); +console.log(gallina() + " salió primero."); // → ?? ``` @@ -297,51 +304,51 @@ console.log(chicken() + " salió primero."); El siguiente código está permitido y se ejecuta sin ningún problema: ``` -function square(x) { return x * x; } -console.log(square(4, true, "erizo")); +function cuadrado(x) { return x * x; } +console.log(cuadrado(4, true, "erizo")); // → 16 ``` -Hemos definido `square` con solo un ((parámetro)). Sin embargo, cuando lo llamamos con tres, el lenguaje no se queja. Ignora los argumentos adicionales y calcula el cuadrado del primero. +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. {{index indefinido}} -JavaScript es extremadamente flexible en cuanto al número de argumentos que puedes pasar a una función. Si pasas demasiados, los extras son ignorados. Si pasas muy pocos, los parámetros faltantes se les asigna el valor `undefined`. +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 el número incorrecto de argumentos a las 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 `minus` intenta imitar al operador `-` actuando sobre uno o dos argumentos: +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 minus(a, b) { +function menos(a, b) { if (b === undefined) return -a; else return a - b; } -console.log(minus(10)); +console.log(menos(10)); // → -10 -console.log(minus(10, 5)); +console.log(menos(10, 5)); // → 5 ``` {{id roundTo}} {{index "argumento opcional", "valor por defecto", "parámetro", ["operador =", "para valor por defecto"], "ejemplo de redondeo"}} -Si escribes un operador `=` después de un parámetro, seguido de una expresión, el valor de esa expresión reemplazará al argumento cuando no se le dé. Por ejemplo, esta versión de `roundTo` hace que su segundo argumento sea opcional. Si no lo proporcionas o pasas el valor `undefined`, por defecto será uno: +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: ```{test: wrap} -function roundTo(n, step = 1) { - let remainder = n % step; - return n - remainder + (remainder < step / 2 ? 0 : step); +function redondearA(n, paso = 1) { + let resto = n % paso; + return n - resto + (resto < paso / 2 ? 0 : paso); }; -console.log(roundTo(4.5)); +console.log(redondearA(4.5)); // → 5 -console.log(roundTo(4.5, 2)); +console.log(redondearA(4.5, 2)); // → 4 ``` {{index "console.log"}} -[El próximo capítulo](data#rest_parameters) introducirá una forma en que un cuerpo de función puede acceder a la lista completa de argumentos que se le pasaron. 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: +[El próximo capítulo](data#rest_parameters) 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); @@ -352,99 +359,99 @@ console.log("C", "O", 2); {{index "pila de llamadas", "vinculación local", ["función", "como valor"], alcance}} -La capacidad de tratar las funciones como valores, combinada con el hecho de que los enlaces locales se recrean cada vez que se llama a una función, plantea una pregunta interesante: ¿qué sucede con los enlaces locales cuando la llamada a la función que los creó ya no está activa?El siguiente código muestra un ejemplo de esto. Define una función, `wrapValue`, que crea un enlace local. Luego devuelve una función que accede a este enlace local y lo devuelve: +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 wrapValue(n) { +function envuelveValor(n) { let local = n; return () => local; } -let wrap1 = wrapValue(1); -let wrap2 = wrapValue(2); -console.log(wrap1()); +let envoltura1 = envuelveValor(1); +let envoltura2 = envuelveValor(2); +console.log(envoltura1()); // → 1 -console.log(wrap2()); +console.log(envoltura2()); // → 2 ``` -Esto está permitido y funciona como esperarías: ambas instancias del enlace aún pueden accederse. Esta situación es una buena demostración de que los enlaces locales se crean nuevamente para cada llamada, y las diferentes llamadas no afectan los enlaces locales de los demás. +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 un enlace local en un ámbito superior, se llama _((clausura))_. Una función que hace referencia a enlaces de ámbitos locales a su alrededor se llama _una_ clausura. Este comportamiento no solo te libera de tener que preocuparte por la vida útil de los enlaces, sino que también hace posible usar valores de función de formas creativas. +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. {{index "función de multiplicador"}} -Con un ligero cambio, podemos convertir el ejemplo anterior en una forma de crear funciones que multiplican por una cantidad arbitraria: +Con un pequeño cambio, podemos convertir el ejemplo anterior en una forma de crear funciones que multiplican por una cantidad arbitraria: ``` -function multiplier(factor) { - return number => number * factor; +function multiplicador(factor) { + return número => número * factor; } -let twice = multiplier(2); -console.log(twice(5)); +let doble = multiplicador(2); +console.log(doble(5)); // → 10 ``` {{index [enlace, "desde parámetro"]}} -El enlace explícito `local` del ejemplo `wrapValue` realmente no es necesario, ya que un parámetro es en sí mismo un enlace local. +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. {{index ["función", "modelo de"]}} -Pensar en programas de esta manera requiere algo de práctica. Un buen modelo mental es pensar en los valores de función como que contienen tanto el código en su cuerpo como el entorno en el que fueron creados. Cuando se llama, el cuerpo de la función ve el entorno en el que fue creado, no el entorno en el que se llama. +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 `multiplier` y crea un entorno en el que su parámetro `factor` está vinculado a 2. El valor de función que devuelve, que se almacena en `twice`, recuerda este entorno para que cuando se llame, multiplique su argumento por 2. +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 {{index "ejemplo de potencia", "desbordamiento de pila", "recursión", ["función", "aplicació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. Una función que se llama a sí misma se llama _recursiva_. La recursión permite que algunas funciones se escriban de una manera diferente. Toma, por ejemplo, esta función `power`, que hace lo mismo que el operador `**` (potenciació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): ```{test: wrap} -function power(base, exponent) { - if (exponent == 0) { +function potencia(base, exponente) { + if (exponente == 0) { return 1; } else { - return base * power(base, exponent - 1); + return base * potencia(base, exponente - 1); } } -console.log(power(2, 3)); +console.log(potencia(2, 3)); // → 8 ``` {{index ciclo, legibilidad, "matemáticas"}} -Esto se asemeja bastante 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 ?](program_structure). La función se llama a sí misma varias veces con exponentes cada vez más pequeños para lograr la multiplicación repetida. +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 ?](program_structure). 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. {{index ["función", "aplicación"], eficiencia}} -Sin embargo, esta implementación tiene un problema: en implementaciones típicas de JavaScript, es aproximadamente tres veces más lenta que una versión que utiliza un bucle `for`. Recorrer un simple bucle suele ser más económico que llamar a una función múltiples veces. +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. {{index "optimización"}} -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 cualquier programa puede ser acelerado haciendo que sea más extenso y complicado. El programador debe encontrar un equilibrio apropiado. +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 `power`, una versión poco elegante (con bucles) sigue siendo bastante simple y fácil de leer. No tiene mucho sentido reemplazarla con 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. +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. {{index perfilado}} -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. +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. {{index "optimización prematura"}} -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 lo suficiente como para tomar una cantidad significativa de tiempo—puedes medir después y mejorarlo si es necesario. +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. {{index "recursión de ramificación"}} -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 mayor frecuencia, estos son problemas que requieren explorar o procesar varias "ramas", cada una de las cuales podría ramificarse nuevamente en aún más ramas. +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. {{id rompecabezas_recursivo}} {{index "recursión", "ejemplo de rompecabezas numérico"}} -Considera este rompecabezas: al comenzar desde el número 1 y repetidamente 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 podría alcanzarse en absoluto. +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: @@ -469,11 +476,13 @@ console.log(encontrarSolucion(24)); 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 trabajar juntos, 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 comenzando desde este número, devuelve `null`. +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`. {{index null, "operador ??", "evaluación de cortocircuito"}} -Para hacer esto, la función realiza una de 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 se devuelve. 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 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`, se devuelve. De lo contrario, se devuelve la segunda llamada, independientemente de si produce una cadena o `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`. {{index "pila de llamadas"}} @@ -495,34 +504,34 @@ encontrar(1, "1") ¡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` a la primera llamada. Allí, el operador `??` hace que ocurra la llamada que explora `(1 * 3)`. Esta búsqueda tiene más suerte: su primera llamada recursiva, a través de otra llamada recursiva, 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. +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 {{index ["función", "definición"]}} -Hay dos formas más o menos naturales de introducir funciones en los programas. +Hay dos formas más o menos naturales de que las funciones aparezcan en los programas. {{index "repetición"}} -La primera ocurre cuando te encuentras escribiendo código similar varias veces. Preferirías no hacer eso, ya que tener más código significa más espacio para que se escondan los errores y más material para que las personas que intentan entender el programa lo lean. Por lo tanto, tomas la funcionalidad repetida, encuentras un buen nombre para ella y la colocas en una función. +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 te das cuenta de que necesitas alguna funcionalidad que aún no has escrito y que suena como si mereciera su propia función. Comienzas por nombrar la función, 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í. +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í. {{index ["función", nombramiento], [variable, nombramiento]}} -Lo difícil que 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 envolver con ella. Vamos a través de un ejemplo. +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. {{index "ejemplo de granja"}} -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 rellenados antes de ambos números para que siempre tengan tres dígitos: +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: ```{lang: null} 007 Vacas 011 Pollos ``` -Esto pide una función con dos argumentos: el número de vacas y el número de pollos. ¡Vamos a programar! +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) { @@ -542,17 +551,17 @@ imprimirInventarioGranja(7, 11); {{index ["propiedad length", "para cadenas"], "bucle while"}} -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 tengan al menos tres caracteres de longitud. +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 jugosa factura), ella llama y nos dice que también ha comenzado a criar cerdos, ¿podríamos extender el software para imprimir también los cerdos? +¡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? {{index "programación copiar y pegar"}} -¡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í está un primer intento: +¡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(numero, etiqueta) { - let cadenaNumero = String(numero); +function imprimirConRellenoYEtiqueta(número, etiqueta) { + let cadenaNumero = String(número); while (cadenaNumero.length < 3) { cadenaNumero = "0" + cadenaNumero; } @@ -570,15 +579,15 @@ imprimirInventarioGranja(7, 11, 3); {{index ["función", nombramiento]}} -¡Funciona! Pero ese nombre, `imprimirConRellenoYEtiqueta`, es un poco incómodo. Confluye tres cosas: imprimir, rellenar con ceros y añadir una etiqueta, en una sola función. +¡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. {{index "función rellenarConCeros"}} -En lugar de sacar la parte repetida de nuestro programa completamente, intentemos sacar un solo _concepto_: +En lugar de extraer completamente la parte repetida de nuestro programa, intentemos sacar un solo _concepto_: ``` -function rellenarConCeros(numero, ancho) { - let cadena = String(numero); +function rellenarConCeros(número, ancho) { + let cadena = String(número); while (cadena.length < ancho) { cadena = "0" + cadena; } @@ -596,13 +605,13 @@ imprimirInventarioGranja(7, 16, 3); {{index legibilidad, "función pura"}} -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é hace. Además, una función así es útil en más situaciones que solo este programa específico. Por ejemplo, podrías usarla para ayudar a imprimir tablas de números alineadas correctamente. +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. {{index [interfaz, "diseño"]}} -¿Qué tan 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. +¿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))" genéricos para cada trozo de funcionalidad que te encuentres. Resiste esa tentación. No lograrás hacer ningún trabajo real: estarás demasiado ocupado escribiendo código que nunca usará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. {{id puro}} @@ -610,23 +619,23 @@ Un principio útil es abstenerse de agregar ingenio a menos que estés absolutam {{index "efecto secundario", "función pura", ["función", pureza]}} -Las funciones pueden dividirse aproximadamente en aquellas que se llaman por sus efectos secundarios y aquellas que se llaman por su valor de retorno (aunque también es posible tener efectos secundarios y devolver un valor). +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). {{index "reutilización"}} -La primera función auxiliar en el ((ejemplo de la granja)), `imprimirConRellenoYEtiqueta`, se llama por su efecto secundario: imprime 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. +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. {{index "sustitución"}} -Una función _pura_ es un tipo específico de función productora de valor que no solo no tiene efectos secundarios, sino que tampoco depende de efectos secundarios de otro código, por ejemplo, no lee enlaces 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 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. Las funciones no puras tienden a requerir más andamiaje para probarlas. +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. {{index "optimización", "console.log"}} -Aún así, no hay necesidad de sentirse mal al 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. +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ñó 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 de flecha son otra forma de crear funciones. +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 @@ -643,9 +652,9 @@ function g(a, b) { let h = a => a % 3; ``` -Una parte clave para entender las funciones es comprender los ámbitos (scopes). Cada bloque crea un nuevo ámbito. Los parámetros y los enlaces declarados en un ámbito dado son locales y no son visibles desde el exterior. Los enlaces declarados con `var` se comportan de manera diferente: terminan en el ámbito de la función más cercana o en el ámbito global. +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 piezas que hacen cosas específicas. +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 @@ -653,7 +662,7 @@ Separar las tareas que realiza tu programa en diferentes funciones es útil. No {{index "Math object", "minimum (exercise)", "Math.min function", minimum}} -El [capítulo previo](program_structure#return_values) 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](program_structure#return_values) 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. {{if interactive @@ -671,7 +680,7 @@ if}} {{index "minimum (exercise)"}} -Si tienes problemas para colocar 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. +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. {{index "return keyword"}} @@ -689,7 +698,7 @@ Hemos visto que podemos usar `%` (el operador de resto) para verificar si un nú - El uno es impar. -- Para cualquier otro número _N_, su paridad es la misma que _N_ - 2. +- Para cualquier otro número _N_, su paridad es la misma que la de _N_ - 2. 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. @@ -712,32 +721,34 @@ 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}} -Es probable que tu función se parezca en cierta medida a la función interna `encontrar` en el ejemplo recursivo `encontrarSolucion` [ejemplo](functions#recursive_puzzle) 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 alguna otra manera asegurarse de que se devuelva un valor específico. +Es probable que tu función se parezca en cierta medida a la función interna `encontrar` en el ejemplo recursivo `encontrarSolucion` [ejemplo](functions#recursive_puzzle) 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. {{index "stack overflow"}} -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. Eventualmente se quedará sin espacio en la pila y se abortará. +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á. hint}} -### Contando frijoles +### Contando minuciosamente {{index "bean counting (exercise)", [string, indexing], "zero-based counting", ["length property", "for string"]}} -Puedes obtener el *é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. +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 `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} -// Your code here. +// Tu código aquí. console.log(countBs("BOB")); // → 2 @@ -751,10 +762,10 @@ if}} {{index "bean counting (exercise)", ["length property", "for string"], "counter variable"}} -Tu función necesida un ((bucle)) que mire cada carácter en la cadena. Puede ejecutar un índice desde cero hasta uno menos que su longitud (`< string.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 de contador. 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"}} -Ten cuidado de que todas las vinculaciones utilizadas en la función sean _locales_ a la función, declarándolas correctamente con la palabra clave `let` o `const`. +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`. hint}} diff --git a/04_data.md b/04_data.md index 5d40014a..20374579 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,15 +96,17 @@ 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"]}} Al igual que las cadenas de texto, los arrays tienen una propiedad `length` que nos dice cuántos elementos tiene el array. -{{id "métodos"}} +{{id "methods"}} ## Métodos @@ -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"}} +{{id "array_methods"}} -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 carácter 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"}} +{{id "namespace_pollution"}} -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,45 +940,47 @@ 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)`. ## 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(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`. +{{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 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 `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 @@ -997,25 +1009,25 @@ 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. -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, `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 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 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 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? +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 @@ -1039,13 +1051,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 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 solía estar 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}} @@ -1076,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}} @@ -1105,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 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 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"}} @@ -1115,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 eso es 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}} @@ -1127,18 +1139,18 @@ 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 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: 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,14 +1167,14 @@ 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"}} -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"}} -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/05_higher_order.md b/05_higher_order.md index 12662882..ac0c6a84 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 observa 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 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}} -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]); +console.log(caballoZapato[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.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,9 +451,9 @@ 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 carácter of rosaDragón) { + console.log(caracter); } // → 🌹 // → 🐉 @@ -453,28 +465,28 @@ Si tienes un carácter (que será una cadena de una o dos unidades de código), {{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, carácter => { + let sistema = sistemaCaracteres(carácter.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/06_object.md b/06_object.md index 5f1a8fc7..db5868d6 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,57 +696,57 @@ 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 {{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( - 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,21 +754,21 @@ 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 -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. @@ -774,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)"}} @@ -782,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). @@ -804,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}} @@ -818,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"}} @@ -860,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"}} @@ -878,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 @@ -905,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/07_robot.md b/07_robot.md index 9b2fe132..13695496 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}} @@ -11,9 +11,9 @@ 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 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/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/09_regexp.md b/09_regexp.md index 7fd24c38..e7e71952 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 procesarINI(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(procesarINI(` +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/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/11_async.md b/11_async.md index 12022481..7799689d 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. +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 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 los que proporciona `Date.now()`). ```{lang: null} 1695709940692 @@ -677,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 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 ```{test: no} -async function activityTable(day) { +async function activityTable(día) { let logFileList = await textFile("camera_logs.txt"); // Tu código aquí } @@ -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` ( 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 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 @@ -724,47 +736,47 @@ function activityTable(día) { } activityTable(6) - .then(tabla => console.log(gráficoActividad(tabla))); + .then(tabla => console.log(activityGraph(tabla))); ``` 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("camera_logs.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/12_language.md b/12_language.md index 7b729f67..ddccefac 100644 --- a/12_language.md +++ b/12_language.md @@ -2,23 +2,23 @@ # 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. 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"}}} 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/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/14_dom.md b/14_dom.md index a1d48dae..4da8dc79 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: @@ -81,25 +81,25 @@ Otra forma de visualizar nuestro ((árbol)) de documento es la siguiente: Las hojas son nodos de texto, y las flechas indican las relaciones padre-hijo entre nodos. -{{id "estándar"}} +{{id "standard"}} ## El estándar {{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> ``` @@ -375,7 +375,7 @@ if}} {{index "método `getBoundingClientRect`", "posición", "propiedad `pageXOffset`", "propiedad `pageYOffset`"}} -{{id "rectángulo delimitador"}} +{{id "boundingRect"}} La manera más efectiva de encontrar la posición precisa de un elemento en la pantalla es el método `getBoundingClientRect`. Devuelve un objeto con las propiedades `top`, `bottom`, `left` y `right`, indicando las posiciones en píxeles de los lados del elemento en relación con la esquina superior izquierda de la pantalla. Si los quieres en relación al documento completo, debes sumar la posición actual de desplazamiento, que puedes encontrar en las variables `pageXOffset` y `pageYOffset`. @@ -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/15_event.md b/15_event.md index bb24743a..dd2a79c5 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 "mouse_drawing"}} 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"}} @@ -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/16_game.md b/16_game.md index b5aa2d7a..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 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. +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/17_canvas.md b/17_canvas.md index fc855b23..5f74b9ab 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -14,21 +14,21 @@ quote}} {{index CSS, "transform (CSS)", [DOM, "gráficos"]}} -Los navegadores nos ofrecen varias formas de mostrar ((gráficos)). La forma más simple es usar estilos para posicionar y colorear elementos DOM regulares. Esto puede llevarnos bastante lejos, como mostró el juego en el [capítulo anterior](game). Al agregar imágenes de fondo parcialmente transparentes a los nodos, podemos hacer que se vean exactamente como queremos. Incluso es posible rotar o sesgar nodos con el estilo `transform`. +Los navegadores nos ofrecen varias formas de mostrar ((gráficos)). La forma más simple es usar estilos para posicionar y colorear elementos normales del DOM. Esto puede llevarnos bastante lejos, como mostró el juego del [capítulo anterior](game). Al agregar imágenes de fondo parcialmente transparentes a los nodos, podemos hacer que se vean exactamente como queremos. Incluso es posible rotar o sesgar nodos con el estilo `transform`. -Pero estaríamos utilizando el DOM para algo para lo que no fue diseñado originalmente. Algunas tareas, como dibujar una ((línea)) entre puntos arbitrarios, son extremadamente incómodas de hacer con elementos HTML regulares. +Pero estaríamos utilizando el DOM para algo para lo que no fue diseñado originalmente. Algunas tareas, como dibujar una ((línea)) entre puntos arbitrarios, son extremadamente incómodas de hacer con elementos usuales de HTML. {{index SVG, "img (etiqueta HTML)"}} -Hay dos alternativas. La primera es basada en el DOM pero utiliza _Gráficos Vectoriales Escalables_ (SVG), en lugar de HTML. Piensa en SVG como un dialecto de marcado de ((documento)) que se centra en las ((forma))s en lugar de en el texto. Puedes incrustar un documento SVG directamente en un documento HTML o incluirlo con una etiqueta `<img>`. +Hay dos alternativas. La primera es basada en el DOM pero utiliza _Gráficos Vectoriales Escalables_ (SVG, por sus siglas en inglés), en lugar de HTML. Piensa en SVG como un dialecto de marcado de ((documento)) que se centra en las ((forma))s en lugar de en el texto. Puedes incrustar un documento SVG directamente en un documento HTML o incluirlo con una etiqueta `<img>`. {{index despejando, ["gráficos" DOM], [interfaz, lienzo]}} -La segunda alternativa se llama _((lienzo))_. Un lienzo es un solo elemento DOM que encapsula una ((imagen)). Proporciona una interfaz de programación para dibujar ((forma))s en el espacio ocupado por el nodo. La principal diferencia entre un lienzo y una imagen SVG es que en SVG se conserva la descripción original de las formas para que puedan moverse o redimensionarse en cualquier momento. Un lienzo, por otro lado, convierte las formas en ((píxel))s (puntos de color en una cuadrícula) en cuanto se dibujan y no recuerda qué representan estos píxeles. La única forma de mover una forma en un lienzo es borrar el lienzo (o la parte del lienzo alrededor de la forma) y volver a dibujarlo con la forma en una nueva posición. +La segunda alternativa se llama _((lienzo))_ (o _canvas_). Un lienzo es un elemento del DOM que encapsula una ((imagen)). Proporciona una interfaz de programación para dibujar ((forma))s en el espacio ocupado por el nodo. La principal diferencia entre un lienzo y una imagen SVG es que en SVG se conserva la descripción original de las formas para que puedan moverse o redimensionarse en cualquier momento. Un lienzo, por otro lado, convierte las formas en ((píxel))s (puntos de color en una cuadrícula) en cuanto se dibujan y no recuerda qué representan estos píxeles. La única forma de mover una forma en un lienzo es borrar el lienzo (o la parte del lienzo alrededor de la forma) y volver a dibujarlo con la forma en una nueva posición. ## SVG -Este libro no se adentrará en detalles sobre ((SVG)), pero explicaré brevemente cómo funciona. Al [final del capítulo](canvas#tradeoffs_graficos), volveré a los compromisos que debes considerar al decidir qué mecanismo de ((dibujo)) es adecuado para una aplicación determinada. +Este libro no ahondará en detalles sobre ((SVG)), pero explicaré brevemente cómo funciona. Al [final del capítulo](canvas#tradeoffs_graficos), volveré a los compromisos que debes considerar al decidir qué mecanismo de ((dibujo)) es adecuado para una aplicación determinada. Este es un documento HTML con una sencilla imagen SVG en él: @@ -56,8 +56,8 @@ if}} Estas etiquetas crean elementos del DOM, al igual que las etiquetas HTML, con las que los scripts pueden interactuar. Por ejemplo, esto cambia el elemento `<circle>` para que se coloree de cian: ```{sandbox: "svg"} -let circle = document.querySelector("circle"); -circle.setAttribute("fill", "cyan"); +let círculo = document.querySelector("circle"); +círculo.setAttribute("fill", "cyan"); ``` ## El elemento canvas @@ -74,7 +74,7 @@ La etiqueta `<canvas>` está destinada a permitir diferentes estilos de ((dibujo {{index renderizado, "gráficos", eficiencia}} -Este libro no discutirá WebGL ni WebGPU—nos mantendremos en dos dimensiones. Pero si estás interesado en gráficos tridimensionales, te animo a investigar sobre WebGPU. Proporciona una interfaz directa al hardware gráfico y te permite renderizar escenas incluso complicadas de manera eficiente, utilizando JavaScript. +Este libro no discutirá WebGL ni WebGPU —nos mantendremos en dos dimensiones. Pero si estás interesado en gráficos tridimensionales, te animo a investigar sobre WebGPU. Proporciona una interfaz directa al hardware gráfico y te permite incluso renderizar escenas complicadas de manera eficiente, utilizando JavaScript. {{index "método getContext", [canvas, contexto]}} @@ -85,10 +85,10 @@ Creas un ((contexto)) con el método `getContext` en el elemento DOM `<canvas>`. <canvas width="120" height="60"></canvas> <p>Después del lienzo.</p> <script> - let canvas = document.querySelector("canvas"); - let context = canvas.getContext("2d"); - context.fillStyle = "red"; - context.fillRect(10, 10, 100, 50); + let lienzo = document.querySelector("canvas"); + let contexto = lienzo.getContext("2d"); + contexto.fillStyle = "red"; + contexto.fillRect(10, 10, 100, 50); </script> ``` @@ -110,11 +110,11 @@ Al igual que en HTML (y SVG), el sistema de coordenadas que utiliza el lienzo si {{index relleno, trazado, dibujo, SVG}} -En la interfaz de ((lienzo)), una forma puede ser _rellenada_, lo que significa que su área recibe un color o patrón determinado, o puede ser _trazada_, lo que significa que se dibuja una ((línea)) a lo largo de su borde. La misma terminología se utiliza en SVG. +En la interfaz de ((lienzo)), una forma puede ser _rellenada_, lo que significa que su área recibe un color o patrón determinado, o puede ser _trazada_, lo que significa que se dibuja una ((línea)) a lo largo de su borde. En SVG se utiliza la misma terminología. {{index "fillRect method", "strokeRect method"}} -El método `fillRect` rellena un ((rectángulo)). Primero toma las ((coordenadas)) x e y de la esquina superior izquierda del rectángulo, luego su ancho y finalmente su altura. Un método similar llamado `strokeRect` dibuja el ((contorno)) de un rectángulo. +El método `fillRect` rellena un ((rectángulo)). Primero toma las ((coordenadas)) x e y de la esquina superior izquierda del rectángulo, luego su ancho y finalmente su altura. Un método similar, llamado `strokeRect` dibuja el ((contorno)) de un rectángulo. {{index [state, "of canvas"]}} @@ -126,7 +126,7 @@ La propiedad `fillStyle` controla la forma en que se rellenan las formas. Puede {{index stroking, "line width", "strokeStyle property", "lineWidth property", canvas}} -La propiedad `strokeStyle` funciona de manera similar, pero determina el color utilizado para una línea contorneada. El ancho de esa línea se determina mediante la propiedad `lineWidth`, que puede contener cualquier número positivo. +La propiedad `strokeStyle` funciona de manera similar, pero determina el color utilizado para una línea de contorno. El ancho de esa línea se determina mediante la propiedad `lineWidth`, que puede contener cualquier número positivo. ```{lang: html} <canvas></canvas> @@ -155,7 +155,7 @@ Cuando no se especifica ningún atributo `width` o `height`, como en el ejemplo, {{index [path, canvas], [interface, design], [canvas, path]}} -Un camino es una secuencia de ((línea))s. La interfaz del canvas 2D toma un enfoque peculiar para describir un camino. Se realiza completamente a través de ((efecto secundario))s. Los caminos no son valores que se puedan almacenar y pasar. En su lugar, si deseas hacer algo con un camino, haces una secuencia de llamadas a métodos para describir su forma. +Un camino es una secuencia de ((línea))s. La interfaz del canvas 2D toma un enfoque peculiar para describir un camino. Se realiza completamente a través de ((efectos secundarios)). Los caminos no son valores que se puedan almacenar y pasar. En vez de eso, si deseas hacer algo con un camino, haces una secuencia de llamadas a métodos para describir su forma. ```{lang: html} <canvas></canvas> @@ -176,15 +176,15 @@ Este ejemplo crea un camino con varios segmentos horizontales de ((línea)) y lu {{if book -El camino descrito por el programa anterior se ve así: +El camino descrito por el programa anterior tiene esta pinta: -{{figure {url: "img/canvas_path.png", alt: "Captura de pantalla que muestra varias líneas verticales", width: "2.1cm"}}} +{{figure {url: "img/canvas_path.png", alt: "Captura de pantalla que muestra varias líneas horizontales", width: "2.1cm"}}} if}} {{index [path, canvas], filling, [path, closing], "fill method"}} -Cuando se rellena un camino (usando el método `fill`), cada ((forma)) se llena por separado. Un camino puede contener múltiples formas—cada movimiento de `moveTo` inicia una nueva forma. Pero el camino necesita estar _cerrado_ (significando que su inicio y final están en la misma posición) antes de poder ser rellenado. Si el camino aún no está cerrado, se agrega una línea desde su final hasta su inicio, y se rellena la forma encerrada por el camino completado. +Cuando se rellena un camino (usando el método `fill`), cada ((forma)) se llena por separado. Un camino puede contener múltiples formas —cada movimiento de `moveTo` inicia una nueva forma. Pero el camino necesita estar _cerrado_ (significando que su inicio y final están en la misma posición) antes de poder ser rellenado. Si el camino aún no está cerrado, se agrega una línea desde su final hasta su inicio, y se rellena la forma encerrada por el camino completado. ```{lang: html} <canvas></canvas> @@ -198,7 +198,7 @@ Cuando se rellena un camino (usando el método `fill`), cada ((forma)) se llena </script> ``` -Este ejemplo dibuja un triángulo relleno. Ten en cuenta que solo se dibujan explícitamente dos de los lados del triángulo. El tercero, desde la esquina inferior derecha de regreso a la parte superior, se da por implícito y no estaría allí cuando se traze el recorrido. +Este ejemplo dibuja un triángulo relleno. Ten en cuenta que solo se dibujan explícitamente dos de los lados del triángulo. El tercero, desde la esquina inferior derecha de regreso a la parte superior, se da por implícito y no estaría allí si trazaras el camino. {{if book @@ -208,7 +208,7 @@ if}} {{index "método stroke", "método closePath", [recorrido, cierre], lienzo}} -También puedes usar el método `closePath` para cerrar explícitamente un recorrido agregando un segmento real ((line)) de vuelta al inicio del recorrido. Este segmento _se_ dibuja cuando se traza el recorrido. +También puedes usar el método `closePath` para cerrar explícitamente un recorrido agregando un segmento real (_((line))_) de vuelta al inicio del recorrido. Este segmento _se dibuja_ cuando se traza el recorrido. ## Curvas @@ -218,7 +218,7 @@ Un recorrido también puede contener ((líneas)) curvadas. Lamentablemente, esta {{index "método quadraticCurveTo"}} -El método `quadraticCurveTo` dibuja una curva hacia un punto dado. Para determinar la curvatura de la línea, el método recibe un ((punto de control)) así como un punto de destino. Imagina este punto de control como _atrayendo_ la línea, dándole su curva. La línea no pasará por el punto de control, pero su dirección en los puntos de inicio y fin será tal que una línea recta en esa dirección apuntaría hacia el punto de control. El siguiente ejemplo ilustra esto: +El método `quadraticCurveTo` dibuja una curva hacia un punto dado. Para determinar la curvatura de la línea, el método recibe un ((punto de control)) así como un punto de destino. Imagina este punto de control como _atrayendo_ la línea, dándole su curvatura. La línea no pasará por el punto de control, pero su dirección en los puntos de inicio y fin será tal que una línea recta en esa dirección apuntaría hacia el punto de control. El siguiente ejemplo ilustra esto: ```{lang: html} <canvas></canvas> @@ -244,11 +244,11 @@ if}} {{index "método stroke"}} -Dibujamos una ((curva cuadrática)) de izquierda a derecha, con (60,10) como punto de control, y luego dibujamos dos segmentos ((line)) que pasan por ese punto de control y vuelven al inicio de la línea. El resultado se asemeja a un emblema de _((Star Trek))_. Puedes ver el efecto del punto de control: las líneas que salen de las esquinas inferiores comienzan en la dirección del punto de control y luego se curvan hacia su objetivo. +Dibujamos una ((curva cuadrática)) de izquierda a derecha, con (60,10) como punto de control, y luego dibujamos dos segmentos ((line)) que pasan por ese punto de control y vuelven al inicio de la línea. El resultado se parece a un emblema de _((Star Trek))_. Puedes ver el efecto del punto de control: las líneas que salen de las esquinas inferiores comienzan en la dirección del punto de control y luego se curvan hacia su objetivo. {{index lienzo, "método bezierCurveTo"}} -El método `bezierCurveTo` dibuja un tipo de curva similar. En lugar de un único ((punto de control)), este tiene dos—uno para cada uno de los extremos de la ((línea)). Aquí hay un boceto similar para ilustrar el comportamiento de dicha curva: +El método `bezierCurveTo` dibuja un tipo de curva parecida. En lugar de un único ((punto de control)), este tiene dos —uno para cada uno de los extremos de la ((línea)). Aquí hay un boceto similar para ilustrar el comportamiento de dicha curva: ```{lang: html} <canvas></canvas> @@ -265,17 +265,17 @@ El método `bezierCurveTo` dibuja un tipo de curva similar. En lugar de un únic </script> ``` -Los dos puntos de control especifican la dirección en ambos extremos de la curva. Cuanto más separados estén de su punto correspondiente, más la curva "abultará" en esa dirección. +Los dos puntos de control especifican la dirección en ambos extremos de la curva. Cuanto más separados estén de su punto correspondiente, más se "abultará" la curva en esa dirección. {{if book -{{figure {url: "img/canvas_beziercurve.png", alt: "Captura de pantalla de una curva de Bezier", width: "2.2cm"}}} +{{figure {url: "img/canvas_beziercurve.png", alt: "Captura de pantalla de una curva de Bézier", width: "2.2cm"}}} if}} {{index "prueba y error"}} -((curve))s como estas pueden ser difíciles de trabajar, no siempre es claro cómo encontrar los ((control point))s que proporcionan la ((forma)) que estás buscando. A veces puedes calcularlos y a veces simplemente tendrás que encontrar un valor adecuado mediante prueba y error. +((Curva))s como estas pueden ser difíciles de trabajar, no siempre es claro cómo encontrar los ((puntos de control)) que proporcionan la ((forma)) que estás buscando. A veces puedes calcularlos y a veces simplemente tendrás que encontrar un valor adecuado mediante prueba y error. {{index "método de arco", arc}} @@ -300,7 +300,7 @@ Estos últimos dos parámetros permiten dibujar solo parte del círculo. Los (( {{index "método moveTo", "método arc", [path, " lienzo"]}} -La imagen resultante contiene una ((línea)) desde la derecha del círculo completo (primer llamado a `arc`) hasta la derecha del cuarto del ((círculo)) (segundo llamado). Al igual que otros métodos de dibujo de trayectos, una línea dibujada con `arc` está conectada al segmento de trayecto anterior. Puedes llamar a `moveTo` o comenzar un nuevo trayecto para evitar esto. +La imagen resultante contiene una ((línea)) desde la derecha del círculo completo (primera llamada a `arc`) hasta la derecha del cuarto del ((círculo)) (segunda llamada). Al igual que otros métodos de dibujo de trayectos, una línea dibujada con `arc` está conectada al segmento de trayecto anterior. Puedes llamar a `moveTo` o comenzar un nuevo trayecto para evitar esto. {{if book @@ -319,7 +319,7 @@ Imagina que acabas de aceptar un ((trabajo)) en EconomiCorp, Inc., y tu primera El enlace `results` contiene una matriz de objetos que representan las respuestas de la encuesta. ```{sandbox: "pie", includeCode: true} -const results = [ +const resultados = [ {name: "Satisfecho", count: 1043, color: "lightblue"}, {name: "Neutral", count: 563, color: "lightgreen"}, {name: "Insatisfecho", count: 510, color: "pink"}, @@ -335,12 +335,12 @@ Para dibujar un diagrama de sectores, dibujamos una serie de sectores circulares <canvas width="200" height="200"></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); - let total = results + let total = resultados .reduce((sum, {count}) => sum + count, 0); // Comenzar en la parte superior let currentAngle = -0.5 * Math.PI; - for (let result of results) { - let sliceAngle = (result.count / total) * 2 * Math.PI; + for (let resultado of resultados) { + let sliceAngle = (resultado.count / total) * 2 * Math.PI; cx.beginPath(); // centro=100,100, radio=100 // desde el ángulo actual, en sentido horario por el ángulo del sector @@ -348,7 +348,7 @@ Para dibujar un diagrama de sectores, dibujamos una serie de sectores circulares currentAngle, currentAngle + sliceAngle); currentAngle += sliceAngle; cx.lineTo(100, 100); - cx.fillStyle = result.color; + cx.fillStyle = resultado.color; cx.fill(); } </script> @@ -371,7 +371,7 @@ Pero un gráfico que no nos dice qué significan las porciones no es muy útil. Un contexto de dibujo en lienzo 2D proporciona los métodos `fillText` y `strokeText`. Este último puede ser útil para contornear letras, pero generalmente `fillText` es lo que necesitas. Este llenará el contorno del ((texto)) dado con el `fillStyle` actual. ```{lang: html} -<canvas></canvas> +<canvas width="420" height="60"></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; @@ -380,11 +380,11 @@ Un contexto de dibujo en lienzo 2D proporciona los métodos `fillText` y `stroke </script> ``` -Puedes especificar el tamaño, estilo y ((fuente)) del texto con la propiedad `font`. Este ejemplo solo da un tamaño de fuente y un nombre de familia. También es posible agregar `italic` o `bold` al comienzo de la cadena para seleccionar un estilo. +Puedes especificar el tamaño, estilo y ((fuente)) del texto con la propiedad `font`. En este ejemplo solo se da un tamaño de fuente y un nombre de familia. También es posible agregar `italic` o `bold` al comienzo de la cadena para seleccionar un estilo. {{index "método fillText", "método strokeText", "propiedad textAlign", "propiedad textBaseline"}} -Los dos últimos argumentos de `fillText` y `strokeText` proporcionan la posición en la que se dibuja la fuente. Por defecto, indican la posición del inicio de la línea alfabética del texto, que es la línea en la que las letras "se paran", sin contar las partes colgantes en letras como la _j_ o la _p_. Puedes cambiar la posición horizontal configurando la propiedad `textAlign` en `"end"` o `"center"` y la posición vertical configurando `textBaseline` en `"top"`, `"middle"` o `"bottom"`. +Los dos últimos argumentos de `fillText` y `strokeText` proporcionan la posición en la que se dibuja la fuente. Por defecto, indican la posición del inicio de la línea base alfabética del texto, que es la línea sobre la que las letras se "apoyan", sin contar las partes colgantes en letras como la _j_ o la _p_. Puedes cambiar la posición horizontal configurando la propiedad `textAlign` en `"end"` o `"center"` y la posición vertical configurando `textBaseline` en `"top"`, `"middle"` o `"bottom"`. {{index "ejemplo de gráfico circular"}} @@ -394,7 +394,7 @@ Volveremos a nuestro gráfico circular y al problema de ((etiquetar)) las porcio {{index "gráficos vectoriales", "gráficos de mapa de bits"}} -En gráficos por computadora, a menudo se hace una distinción entre gráficos _vectoriales_ y gráficos _de mapa de bits_. El primero es lo que hemos estado haciendo hasta ahora en este capítulo: especificar una imagen dando una descripción lógica de las ((forma))s. Los gráficos de mapa de bits, por otro lado, no especifican formas reales, sino que trabajan con datos de ((píxel)) (rasteros de puntos de colores). +En gráficos por computadora, a menudo se hace una distinción entre gráficos _vectoriales_ y gráficos _de mapa de bits_. El primero es lo que hemos estado haciendo hasta ahora en este capítulo: especificar una imagen dando una descripción lógica de las ((forma))s. Los gráficos de mapa de bits, por otro lado, no especifican formas reales, sino que trabajan con datos de ((píxel)) (matrices de puntos de colores). {{index "evento load", "manejo de eventos", "img (etiqueta HTML)", "método drawImage"}} @@ -416,7 +416,7 @@ El método `drawImage` nos permite dibujar datos ((de píxel)) en un ((canvas)). {{index "método drawImage", escalado}} -Por defecto, `drawImage` dibujará la imagen a su tamaño original. También se le pueden proporcionar dos argumentos adicionales para establecer un ancho y alto diferente. +Por defecto, `drawImage` dibujará la imagen a su tamaño original. También se le pueden proporcionar dos argumentos adicionales para establecer un ancho y alto diferentes. Cuando se utilizan _nueve_ argumentos en `drawImage`, se puede usar para dibujar solo un fragmento de una imagen. Los argumentos segundo a quinto indican el rectángulo (x, y, ancho y alto) en la imagen de origen que se debería copiar, y los argumentos sexto a noveno indican el rectángulo (en el lienzo) en el cual se debería copiar. @@ -424,7 +424,7 @@ Cuando se utilizan _nueve_ argumentos en `drawImage`, se puede usar para dibujar Esto se puede utilizar para empaquetar varios _((sprites))_ (elementos de imagen) en un único archivo de imagen y luego dibujar solo la parte que se necesita. Por ejemplo, tenemos esta imagen que contiene un personaje de juego en múltiples ((poses)): -{{figure {url: "img/player_big.png", alt: "Arte de píxeles mostrando un personaje de videojuego en 10 poses diferentes. Las primeras 8 forman su ciclo de animación de carrera, la novena tiene al personaje parado, y la décima lo muestra saltando.", width: "6cm"}}} +{{figure {url: "img/player_big.png", alt: "Píxel art mostrando un personaje de videojuego en 10 poses diferentes. Las primeras 8 forman su ciclo de animación de carrera, la novena tiene al personaje parado, y la décima lo muestra saltando.", width: "6cm"}}} {{index ["animación", "juego de plataforma"]}} @@ -462,7 +462,7 @@ Sabemos que cada _((sprite))_, cada subimagen, tiene un ancho de 24 ((píxeles)) {{index "operador de resto", "operador %", ["animación", "juego de plataforma"]}} -El enlace `ciclo` sigue nuestra posición en la animación. En cada ((frame)), se incrementa y luego se recorta de nuevo al rango de 0 a 7 usando el operador de resto. Este enlace se utiliza luego para calcular la coordenada x que tiene el sprite para la pose actual en la imagen. +La variable `ciclo` sigue nuestra posición en la animación. En cada ((frame)), se incrementa y luego se recorta de nuevo al rango de 0 a 7 usando el operador de resto. Esta variable se utiliza luego para calcular la coordenada x que tiene el sprite para la pose actual en la imagen. ## Transformación @@ -470,7 +470,7 @@ El enlace `ciclo` sigue nuestra posición en la animación. En cada ((frame)), s {{indexsee voltear, espejado}} -Pero, ¿qué pasa si queremos que nuestro personaje camine hacia la izquierda en lugar de hacia la derecha? Podríamos dibujar otro conjunto de sprites, por supuesto. Pero también podemos instruir al ((lienzo)) para que dibuje la imagen en sentido contrario. +Pero, ¿qué pasa si queremos que nuestro personaje camine hacia la izquierda en lugar de hacia la derecha? Podríamos dibujar otro conjunto de sprites, por supuesto. Pero también podemos decirle al ((lienzo)) que dibuje la imagen en sentido contrario. {{index "método scale", escalado}} @@ -498,7 +498,7 @@ if}} {{index mirroring}} -Escalar hará que todo en la imagen dibujada, incluyendo el ((grosor de línea)), se estire o se comprima como se especifique. Escalar por una cantidad negativa volteará la imagen. La volteadura ocurre alrededor del punto (0,0), lo que significa que también volteará la dirección del sistema de coordenadas. Cuando se aplica una escala horizontal de -1, una forma dibujada en la posición x 100 terminará en lo que solía ser la posición -100. +Escalar hará que todo en la imagen dibujada, incluyendo el ((grosor de línea)), se estire o se comprima como se especifique. Escalar por una cantidad negativa volteará la imagen. Esta transformación ocurre alrededor del punto (0,0), lo que significa que también volteará la dirección del sistema de coordenadas. Cuando se aplica una escala horizontal de -1, una forma dibujada en la posición x 100 terminará en lo que solía ser la posición -100. {{index "drawImage method"}} @@ -506,27 +506,27 @@ Así que para voltear una imagen, no podemos simplemente agregar `cx.scale(-1, 1 {{index "rotate method", "translate method", transformation}} -Hay varios otros métodos además de `scale` que influyen en el sistema de coordenadas de un ((lienzo)). Puedes rotar formas dibujadas posteriormente con el método `rotate` y moverlas con el método `translate`. Lo interesante—y confuso—es que estas transformaciones _se apilan_, lo que significa que cada una ocurre relativa a las transformaciones anteriores. +Hay varios otros métodos además de `scale` que influyen en el sistema de coordenadas de un ((lienzo)). Puedes rotar formas dibujadas posteriormente con el método `rotate` y moverlas con el método `translate`. Lo interesante —y confuso— es que estas transformaciones _se apilan_, lo que significa que cada una ocurre relativa a las transformaciones anteriores. {{index "rotate method", "translate method"}} -Entonces, si traducimos por 10 píxeles horizontales dos veces, todo se dibujará 20 píxeles a la derecha. Si primero movemos el centro del sistema de coordenadas a (50,50) y luego rotamos por 20 ((grados)) (aproximadamente 0.1π ((radianes))), esa rotación ocurrirá _alrededor_ del punto (50,50). +Entonces, si trasladamos por 10 píxeles horizontales dos veces, todo se dibujará 20 píxeles a la derecha. Si primero movemos el centro del sistema de coordenadas a (50,50) y luego rotamos por 20 ((grados)) (aproximadamente 0.1π ((radianes))), esa rotación ocurrirá _alrededor_ del punto (50,50). -{{figure {url: "img/transform.svg", alt: "Diagrama que muestra el resultado de apilar transformaciones. El primer diagrama traduce y luego rota, causando que la traducción ocurra normalmente y la rotación alrededor del objetivo de la traducción. El segundo diagrama primero rota y luego traduce, causando que la rotación ocurra alrededor del origen y la dirección de traducción se incline por esa rotación.", width: "9cm"}}} +{{figure {url: "img/transform.svg", alt: "Diagrama que muestra el resultado de apilar transformaciones. El primer diagrama traslada y luego rota, causando que la traslación ocurra normalmente y la rotación alrededor del objetivo de la traslación. El segundo diagrama primero rota y luego traslada, causando que la rotación ocurra alrededor del origen y la dirección de traslación se incline por esa rotación.", width: "9cm"}}} {{index coordinates}} -Pero si _primero_ rotamos 20 grados y _luego_ traducimos por (50,50), la traducción ocurrirá en el sistema de coordenadas rotado y producirá una orientación diferente. El orden en el que se aplican las transformaciones es importante. +Pero si _primero_ rotamos 20 grados y _luego_ trasladamos por (50,50), la traslación ocurrirá en el sistema de coordenadas rotado y producirá una orientación diferente. El orden en el que se aplican las transformaciones es importante. {{index axis, mirroring}} Para voltear una imagen alrededor de la línea vertical en una posición x dada, podemos hacer lo siguiente: ```{includeCode: true} -function flipHorizontally(context, around) { - context.translate(around, 0); - context.scale(-1, 1); - context.translate(-around, 0); +function flipHorizontally(contexto, alrededorDe) { + contexto.translate(alrededorDe, 0); + contexto.scale(-1, 1); + contexto.translate(-alrededorDe, 0); } ``` @@ -538,16 +538,16 @@ Movemos el eje y a donde queremos que esté nuestro ((espejo)), aplicamos el efe {{index "método translate", "método scale", "transformación", lienzo}} -Esto muestra los sistemas de coordenadas antes y después del espejo a través de la línea central. Los triángulos están numerados para ilustrar cada paso. Si dibujamos un triángulo en una posición x positiva, por defecto estaría en el lugar donde se encuentra el triángulo 1. Una llamada a `flipHorizontally` primero realiza una traslación a la derecha, lo que nos lleva al triángulo 2. Luego escala, volteando el triángulo a la posición 3. Esto no es donde debería estar, si estuviera reflejado en la línea dada. La segunda llamada a `translate` corrige esto, "cancela" la traslación inicial y hace que el triángulo 4 aparezca exactamente donde debería. +Esto muestra los sistemas de coordenadas antes y después del espejo a través de la línea central. Los triángulos están numerados para ilustrar cada paso. Si dibujamos un triángulo en una posición x positiva, por defecto estaría en el lugar donde se encuentra el triángulo 1. Una llamada a `flipHorizontally` primero realiza una traslación a la derecha, lo que nos lleva al triángulo 2. Luego escala, volteando el triángulo a la posición 3. Aquí no es donde debería estar, si estuviera reflejado en la línea dada. La segunda llamada a `translate` corrige esto, "cancela" la traslación inicial y hace que el triángulo 4 aparezca exactamente donde debería. -Ahora podemos dibujar un personaje espejado en la posición (100,0) volteando el mundo alrededor del centro vertical del personaje. +Ahora podemos dibujar un personaje reflejado en la posición (100,0) volteando el mundo alrededor del centro vertical del personaje. ```{lang: html} <canvas></canvas> <script> let cx = document.querySelector("canvas").getContext("2d"); let img = document.createElement("img"); - img.src = "img/jugador.png"; + img.src = "img/player.png"; let spriteW = 24, spriteH = 30; img.addEventListener("load", () => { flipHorizontally(cx, 100 + spriteW / 2); @@ -571,7 +571,9 @@ Los métodos `save` y `restore` en el contexto 2D del lienzo hacen este manejo d {{index "recursión de ramificación", "ejemplo de fractal", "recursión"}} -La función `branch` en el siguiente ejemplo ilustra lo que puedes hacer con una función que cambia la transformación y luego llama a una función (en este caso a sí misma), que continúa dibujando con la transformación dada.Esta función dibuja una forma parecida a un árbol dibujando una línea, moviendo el centro del sistema de coordenadas al final de la línea, y llamándose a sí misma dos veces, primero rotada a la izquierda y luego rotada a la derecha. Cada llamada reduce la longitud de la rama dibujada, y la recursividad se detiene cuando la longitud desciende por debajo de 8. +La función `branch` en el siguiente ejemplo ilustra lo que puedes hacer con una función que cambia la transformación y luego llama a una función (en este caso a sí misma), que continúa dibujando con la transformación dada. + +Esta función dibuja una forma parecida a un árbol dibujando una línea, moviendo el centro del sistema de coordenadas al final de la línea, y llamándose a sí misma dos veces, primero rotada a la izquierda y luego rotada a la derecha. Cada llamada reduce la longitud de la rama dibujada, y la recursividad se detiene cuando la longitud desciende por debajo de 8. ```{lang: html} <canvas width="600" height="300"></canvas> @@ -603,7 +605,7 @@ if}} {{index "método save", "método restore", canvas, "método rotate"}} -Si las llamadas a `save` y `restore` no estuvieran allí, la segunda llamada recursiva a `branch` terminaría con la posición y rotación creadas por la primera llamada. No estaría conectada a la rama actual sino más bien a la rama más interna y a la derecha dibujada por la primera llamada. La forma resultante podría ser interesante, pero definitivamente no sería un árbol. +Si las llamadas a `save` y `restore` no estuvieran allí, la segunda llamada recursiva a `branch` terminaría con la posición y rotación creadas por la primera llamada. No estaría conectada a la rama actual sino más bien a la rama más interna y a la derecha dibujada por la primera llamada. La forma resultante podría ser interesante, pero, desde luego, no sería un árbol. {{id canvasdisplay}} @@ -611,7 +613,7 @@ Si las llamadas a `save` y `restore` no estuvieran allí, la segunda llamada rec {{index "método drawImage"}} -Ahora sabemos lo suficiente sobre el dibujo en ((canvas)) para empezar a trabajar en un sistema de ((display)) basado en ((canvas)) para el ((juego)) del [capítulo anterior](game). El nuevo display ya no mostrará solo cajas de colores. En su lugar, usaremos `drawImage` para dibujar imágenes que representen los elementos del juego. +Ahora sabemos lo suficiente sobre el dibujo en el ((canvas)) para empezar a trabajar en un sistema de ((display)) basado en ((canvas)) para el ((juego)) del [capítulo anterior](game). El nuevo display ya no mostrará solo cajas de colores. En su lugar, usaremos `drawImage` para dibujar imágenes que representen los elementos del juego. {{index "clase CanvasDisplay", "clase DOMDisplay", [interfaz, objeto]}} @@ -619,7 +621,7 @@ Definimos otro tipo de objeto de display llamado `CanvasDisplay`, que soporta la {{index [estado, "en objetos"]}} -Este objeto mantiene un poco más de información que `DOMDisplay`. En lugar de utilizar la posición de desplazamiento de su elemento DOM, realiza un seguimiento de su propio ((viewport)), que nos indica qué parte del nivel estamos viendo actualmente. Por último, mantiene una propiedad `flipPlayer` para que incluso cuando el jugador esté quieto, siga mirando en la dirección en la que se movió por última vez. +Este objeto mantiene un poco más de información que `DOMDisplay`. En lugar de utilizar la posición de desplazamiento de su elemento del DOM, realiza un seguimiento de su propio ((viewport)), que nos indica qué parte del nivel estamos viendo actualmente. Por último, mantiene una propiedad `flipPlayer` para que incluso cuando el jugador esté quieto, siga mirando en la dirección en la que se movió por última vez. ```{sandbox: "game", includeCode: true} class CanvasDisplay { @@ -690,7 +692,7 @@ CanvasDisplay.prototype.updateViewport = function(state) { Las llamadas a `Math.max` y `Math.min` aseguran que el **viewport** no termine mostrando espacio fuera del nivel. `Math.max(x, 0)` se asegura de que el número resultante no sea menor que cero. `Math.min` garantiza de manera similar que un valor se mantenga por debajo de un límite dado. -Al **limpiar** la visualización, usaremos un color ligeramente diferente según si el juego se ha ganado (más brillante) o perdido (más oscuro). +Al **limpiar** la visualización, usaremos un color ligeramente diferente según si el juego termina con éxito (más brillante) o no (más oscuro). ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.clearDisplay = function(status) { @@ -740,19 +742,19 @@ CanvasDisplay.prototype.drawBackground = function(level) { Las casillas que no están vacías se dibujan con `drawImage`. La imagen `otherSprites` contiene las imágenes utilizadas para elementos que no son el jugador. Contiene, de izquierda a derecha, la casilla de pared, la casilla de lava y el sprite de una moneda. -{{figure {url: "img/sprites_big.png", alt: "Arte pixelado que muestra tres sprites: una pieza de pared, hecha de pequeñas piedras blancas, un cuadrado de lava naranja y una moneda redonda.", width: "1.4cm"}}} +{{figure {url: "img/sprites_big.png", alt: "Píxel art que muestra tres sprites: una pieza de pared, hecha de pequeñas piedras blancas, un cuadrado de lava naranja y una moneda redonda.", width: "1.4cm"}}} {{index escalado}} -Las casillas de fondo son de 20 por 20 píxeles ya que usaremos la misma escala que en `DOMDisplay`. Por lo tanto, el desplazamiento para las casillas de lava es 20 (el valor del enlace `scale`), y el desplazamiento para las paredes es 0. +Las casillas de fondo son de 20 por 20 píxeles ya que usaremos la misma escala que en `DOMDisplay`. Por lo tanto, el desplazamiento para las casillas de lava es 20 (el valor de la variable `scale`), y el desplazamiento para las paredes es 0. {{index dibujo, "evento load", "método drawImage"}} -No nos molesta esperar a que se cargue la imagen del sprite. Llamar a `drawImage` con una imagen que aún no se ha cargado simplemente no hará nada. Por lo tanto, podríamos no dibujar correctamente el juego durante los primeros ((cuadro))s, mientras la imagen aún se está cargando, pero eso no es un problema grave. Dado que seguimos actualizando la pantalla, la escena correcta aparecerá tan pronto como termine la carga. +No nos importa esperar a que se cargue la imagen del sprite. Llamar a `drawImage` con una imagen que aún no se ha cargado simplemente no hará nada. Por lo tanto, podríamos no dibujar correctamente el juego durante los primeros ((frame))s, mientras la imagen aún se está cargando, pero eso no es un problema grave. Como seguimos actualizando la pantalla, la escena correcta aparecerá tan pronto como termine la carga. {{index "jugador", ["animación", "juego de plataformas"], dibujo}} -El personaje de movimiento que se mostró anteriormente se utilizará para representar al jugador. El código que lo dibuja necesita seleccionar el ((sprite)) adecuado y la dirección basándose en el movimiento actual del jugador. Los primeros ocho sprites contienen una animación de caminar. Cuando el jugador se está moviendo a lo largo de una superficie, los recorremos según el tiempo actual. Queremos cambiar de fotogramas cada 60 milisegundos, por lo que primero dividimos el ((tiempo)) por 60. Cuando el jugador está quieto, dibujamos el noveno sprite. Durante los saltos, que se reconocen por el hecho de que la velocidad vertical no es cero, usamos el décimo sprite de la derecha. +El personaje caminando que se mostró anteriormente se utilizará para representar al jugador. El código que lo dibuja necesita seleccionar el ((sprite)) adecuado y la dirección basándose en el movimiento actual del jugador. Los primeros ocho sprites contienen una animación de caminar. Cuando el jugador se está moviendo a lo largo de una superficie, los recorremos según el tiempo actual. Queremos cambiar de fotogramas cada 60 milisegundos, por lo que primero dividimos el ((tiempo)) por 60. Cuando el jugador está quieto, dibujamos el noveno sprite. Durante los saltos, que se reconocen por el hecho de que la velocidad vertical no es cero, usamos el décimo sprite de la derecha. {{index "función flipHorizontally", "clase CanvasDisplay"}} @@ -810,7 +812,7 @@ CanvasDisplay.prototype.drawActors = function(actors) { }; ``` -Cuando se está dibujando algo que no es el jugador, miramos su tipo para encontrar el desplazamiento del sprite correcto. El tile de lava se encuentra en el desplazamiento 20, y el sprite de la moneda se encuentra en 40 (dos veces `scale`). +Cuando se está dibujando algo que no es el jugador, miramos su tipo para encontrar el desplazamiento del sprite correcto. El dibujo de la lava se encuentra en el desplazamiento 20, y el sprite de la moneda se encuentra en 40 (dos veces `scale`). {{index viewport}} @@ -834,7 +836,7 @@ if}} {{index [game, screenshot], [game, "with canvas"]}} -Eso concluye el nuevo sistema de ((display)). El juego resultante se ve algo así: +Eso concluye el nuevo sistema de ((display)). El juego resultante tiene esta pinta: {{figure {url: "img/canvas_game.png", alt: "Captura de pantalla del juego mostrado en canvas", width: "8cm"}}} @@ -844,7 +846,7 @@ if}} ## Elección de una interfaz gráfica -Por lo tanto, cuando necesitas generar gráficos en el navegador, puedes elegir entre HTML simple, ((SVG)) y ((canvas)). No hay un enfoque único _mejor_ que funcione en todas las situaciones. Cada opción tiene sus fortalezas y debilidades. +Así que, cuando necesitas generar gráficos en el navegador, puedes elegir entre HTML simple, ((SVG)) y ((canvas)). No hay un único _mejor_ enfoque que funcione en todas las situaciones. Cada opción tiene sus fortalezas y debilidades. {{index "text wrapping"}} @@ -864,7 +866,7 @@ Pero el enfoque orientado a píxeles de ((canvas)) puede ser una ventaja al dibu {{index "ray tracer"}} -También hay efectos, como renderizar una escena píxel por píxel (por ejemplo, usando un ray tracer) o procesar una imagen con JavaScript (desenfocarla o distorsionarla), que solo son prácticos con un elemento canvas. +También hay efectos, como renderizar una escena píxel por píxel (por ejemplo, usando un _ray tracer_) o procesar una imagen con JavaScript (desenfocarla o distorsionarla), que solo son prácticos con un elemento canvas. En algunos casos, puede que desees combinar varias de estas técnicas. Por ejemplo, podrías dibujar un ((gráfico)) con ((SVG)) o ((canvas)) pero mostrar información ((text))ual posicionando un elemento HTML encima de la imagen. @@ -880,7 +882,7 @@ Un nodo canvas representa un área en un documento en la que nuestro programa pu La interfaz de dibujo 2D nos permite rellenar y trazar varias formas. La propiedad `fillStyle` del contexto determina cómo se rellenan las formas. Las propiedades `strokeStyle` y `lineWidth` controlan la forma en que se dibujan las líneas. -Los rectángulos y trozos de texto se pueden dibujar con una sola llamada a método. Los métodos `fillRect` y `strokeRect` dibujan rectángulos, y los métodos `fillText` y `strokeText` dibujan texto. Para crear formas personalizadas, primero debemos construir un camino. +Los rectángulos y trozos de texto se pueden dibujar con una sola llamada a un método. Los métodos `fillRect` y `strokeRect` dibujan rectángulos, y los métodos `fillText` y `strokeText` dibujan texto. Para crear formas personalizadas, primero debemos construir un camino. {{index trazado, relleno}} @@ -941,7 +943,7 @@ El ((trapecio)) (1) es más fácil de dibujar usando un recorrido. Elige coorden {{index "función flipHorizontally", rotación}} -El diamante ((diamond)) (2) se puede dibujar de forma directa, con un recorrido, o de forma interesante, con una ((transformación)) de `rotación`. Para usar la rotación, tendrás que aplicar un truco similar al que hicimos en la función `flipHorizontally`. Debido a que quieres rotar alrededor del centro de tu rectángulo y no alrededor del punto (0,0), primero debes `translate` allí, luego rotar, y luego volver a trasladar. +El diamante ((diamond)) (2) se puede dibujar de forma directa, con un recorrido, o de forma interesante, con una ((transformación)) de `rotación`. Para usar la rotación, tendrás que aplicar un truco similar al que hicimos en la función `flipHorizontally`. Como tienes que rotar alrededor del centro de tu rectángulo y no alrededor del punto (0,0), primero debes hacer `translate` hasta allí, luego rotar, y luego volver a trasladar. Asegúrate de restablecer la transformación después de dibujar cualquier forma que la cree. @@ -953,7 +955,7 @@ También necesitarás un bucle para la espiral (4). Si dibujas una serie de punt {{index "método quadraticCurveTo"}} -La estrella (5) representada está construida con líneas `quadraticCurveTo`. También podrías dibujar una con líneas rectas. Divide un círculo en ocho piezas para una estrella con ocho puntas, o cuantas piezas desees. Dibuja líneas entre estos puntos, haciéndolas curvar hacia el centro de la estrella. Con `quadraticCurveTo`, puedes usar el centro como punto de control. +La estrella (5) representada está construida con líneas `quadraticCurveTo`. También podrías dibujar una con líneas rectas. Divide un círculo en ocho piezas para una estrella con ocho puntas, o cuantas piezas desees. Dibuja líneas entre estos puntos, haciéndolas curvarse hacia el centro de la estrella. Con `quadraticCurveTo`, puedes usar el centro como punto de control. hint}} @@ -1067,7 +1069,7 @@ hint}} {{index "optimización", "gráficos de mapa de bits", espejo}} -Una desventaja de las ((transformaciones)) es que ralentizan el dibujo de mapas de bits. La posición y el tamaño de cada ((píxel)) deben ser transformados, y aunque es posible que los ((navegadores)) se vuelvan más inteligentes sobre las transformaciones en el ((futuro)), actualmente causan un aumento medible en el tiempo que lleva dibujar un mapa de bits. +Una desventaja de las ((transformaciones)) es que ralentizan el dibujo de mapas de bits. La posición y el tamaño de cada ((píxel)) deben ser transformados, y aunque es posible que los ((navegadores)) se vuelvan más inteligentes en cuanto a las transformaciones en el ((futuro)), actualmente causan un aumento medible en el tiempo que lleva dibujar un mapa de bits. En un juego como el nuestro, en el que solo estamos dibujando un sprite transformado, esto no es un problema. Pero imagina que necesitamos dibujar cientos de personajes o miles de partículas giratorias de una explosión. diff --git a/18_http.md b/18_http.md index 7c527a32..1f47d50d 100644 --- a/18_http.md +++ b/18_http.md @@ -2,7 +2,7 @@ {{quote {author: "Tim Berners-Lee", chapter: true} -Lo que a menudo resultaba difícil para las personas entender sobre el diseño era que no había nada más allá de las URL, HTTP y HTML. No había una computadora central "controlando" la Web, no existía una sola red en la que funcionaran estos protocolos, ni siquiera una organización en algún lugar que "dirigiera" la Web. La Web no era una "cosa" física que existía en un cierto "lugar". Era un "espacio" en el que la información podía existir. +Lo que más le costaba a la gente entender sobre el diseño era que no había nada más allá de las URL, HTTP y HTML. No había una computadora central "controlando" la Web, no existía una sola red en la que funcionaran estos protocolos, ni siquiera una organización en algún lugar que "dirigiera" la Web. La Web no era una "cosa" física que existía en un cierto "lugar". Era un "espacio" en el que la información podía existir. quote}} @@ -10,13 +10,13 @@ quote}} {{figure {url: "img/chapter_picture_18.jpg", alt: "Ilustración mostrando un formulario de registro en la web en un pergamino", chapter: "framed"}}} -El _Protocolo de Transferencia de Hipertexto_, mencionado anteriormente en el [Capítulo ?](browser#web), es el mecanismo a través del cual se solicita y proporciona datos en la ((World Wide Web)). Este capítulo describe el ((protocolo)) con más detalle y explica la forma en que JavaScript del navegador tiene acceso a él. +El _Protocolo de Transferencia de Hipertexto_, mencionado anteriormente en el [Capítulo ?](browser#web), es el mecanismo a través del cual se solicita y proporciona la información en la ((World Wide Web)). Este capítulo describe el ((protocolo)) con más detalle y explica la forma en que el JavaScript del navegador tiene acceso a él. ## El protocolo Si escribes _eloquentjavascript.net/18_http.html_ en la barra de direcciones de tu navegador, el ((navegador)) primero busca la ((dirección)) del servidor asociado con _eloquentjavascript.net_ e intenta abrir una ((conexión)) ((TCP)) con él en el ((puerto)) 80, el puerto predeterminado para el tráfico ((HTTP)). Si el ((servidor)) existe y acepta la conexión, el navegador podría enviar algo como esto: -```http +```{lang: http} GET /18_http.html HTTP/1.1 Host: eloquentjavascript.net User-Agent: Nombre de tu navegador @@ -24,7 +24,7 @@ User-Agent: Nombre de tu navegador Luego el servidor responde, a través de esa misma conexión. -```http +```{lang: http} HTTP/1.1 200 OK Content-Length: 87320 Content-Type: text/html @@ -34,19 +34,21 @@ Last-Modified: Vie, 13 Oct 2023 10:05:41 GMT ... el resto del documento ``` -El navegador toma la parte de la ((respuesta)) después de la línea en blanco, su _cuerpo_ (no confundir con la etiqueta HTML `<body>`), y lo muestra como un documento ((HTML)). +El navegador toma la parte de la ((respuesta)) después de la línea en blanco —su _cuerpo_ (no confundir con la etiqueta HTML `<body>`)— y lo muestra como un documento ((HTML)). La información enviada por el cliente se llama la _((solicitud))_. Comienza con esta línea: -```http +```{lang: http} GET /18_http.html HTTP/1.1 ``` -La primera palabra es el _método_ de la ((solicitud)). `GET` significa que queremos _obtener_ el recurso especificado. Otros métodos comunes son `DELETE` para eliminar un recurso, `PUT` para crearlo o reemplazarlo, y `POST` para enviar información a él. Cabe destacar que el ((servidor)) no está obligado a llevar a cabo cada solicitud que recibe. Si te acercas a un sitio web aleatorio y le dices que `DELETE` su página principal, probablemente se negará. +La primera palabra es el _método_ de la ((solicitud)). `GET` significa que queremos _obtener_ el recurso especificado. Otros métodos comunes son `DELETE` para eliminar un recurso, `PUT` para crearlo o reemplazarlo, y `POST` para enviarle información. Cabe destacar que el ((servidor)) no está obligado a llevar a cabo cada solicitud que recibe. Si vas a un sitio web aleatorio y le dices que haga `DELETE` de su página principal, probablemente se negará. + +La parte después del nombre del método es la ruta del _((recurso))_ al que aplica la solicitud. En el caso más simple, un recurso es simplemente un archivo en el ((servidor)), pero el protocolo no lo requiere así. Un recurso puede ser cualquier cosa que pueda transferirse _como si fuera_ un archivo. Muchos servidores generan las respuestas que producen al vuelo. Por ejemplo, si abres [_https://github.com/marijnh_](https://github.com/marijnh), el servidor buscará en su base de datos un usuario llamado "marijnh" y, si lo encuentra, generará una página de perfil para ese usuario. -La parte después del nombre del método es la ruta del _((recurso))_ al que aplica la solicitud. En el caso más simple, un recurso es simplemente un archivo en el ((servidor)), pero el protocolo no lo requiere así. Un recurso puede ser cualquier cosa que pueda transferirse _como si fuera_ un archivo. Muchos servidores generan las respuestas que producen al vuelo. Por ejemplo, si abres [_https://github.com/marijnh_](https://github.com/marijnh), el servidor buscará en su base de datos un usuario llamado "marijnh", y si lo encuentra, generará una página de perfil para ese usuario.Después de la ruta del recurso, la primera línea de la solicitud menciona `HTTP/1.1` para indicar la versión del protocolo HTTP que está utilizando. +Después de la ruta del recurso, la primera línea de la solicitud dice `HTTP/1.1` para indicar la versión del protocolo HTTP que está utilizando. -En la práctica, muchos sitios utilizan la versión 2 de HTTP, que soporta los mismos conceptos que la versión 1.1 pero es mucho más complicada para que pueda ser más rápida. Los navegadores cambiarán automáticamente a la versión de protocolo adecuada al comunicarse con un servidor dado, y el resultado de una solicitud es el mismo independientemente de la versión utilizada. Dado que la versión 1.1 es más directa y más fácil de entender, la usaremos para ilustrar el protocolo. +En la práctica, muchos sitios utilizan la versión 2 de HTTP, que soporta los mismos conceptos que la versión 1.1 pero es mucho más complicada para que pueda ser más rápida. Los navegadores cambiarán automáticamente a la versión de protocolo adecuada al comunicarse con un servidor dado, y el resultado de una solicitud es el mismo independientemente de la versión utilizada. Dado que la versión 1.1 es más directa y más fácil de entender, es la que usaremos para ilustrar el protocolo. {{index "código de estado"}} @@ -58,13 +60,13 @@ HTTP/1.1 200 OK {{index "200 (código de estado de HTTP)", "respuesta de error", "404 (código de estado de HTTP)"}} -Los códigos de estado que comienzan con 2 indican que la solicitud tuvo éxito. Los códigos que comienzan con 4 significan que hubo un problema con la solicitud. El 404 es probablemente el código de estado de HTTP más famoso, lo que significa que el recurso no se pudo encontrar. Los códigos que comienzan con 5 indican que ocurrió un error en el servidor y la solicitud no es la responsable. +Los códigos de estado que comienzan con 2 indican que la solicitud tuvo éxito. Los códigos que comienzan con 4 significan que hubo un problema con la solicitud. El 404 es probablemente el código de estado de HTTP más famoso, y significa que el recurso no se pudo encontrar. Los códigos que comienzan con 5 indican que ocurrió un error en el servidor y la solicitud no es la responsable. {{index HTTP}} {{id headers}} -La primera línea de una solicitud o respuesta puede ir seguida de cualquier número de _cabeceras_. Estas son líneas en la forma `nombre: valor` que especifican información adicional sobre la solicitud o respuesta. Estas cabeceras eran parte del ejemplo de respuesta: +La primera línea de una solicitud o respuesta puede ir seguida de cualquier número de _cabeceras_. Estas son líneas en la forma `nombre: valor` que especifican información adicional sobre la solicitud o respuesta. Estas cabeceras eran parte del ejemplo de respuesta de antes: ```{lang: null} Content-Length: 87320 @@ -74,9 +76,9 @@ Last-Modified: Fri, 13 Oct 2023 10:05:41 GMT {{index "cabecera Content-Length", "cabecera Content-Type", "cabecera Last-Modified"}} -Esto nos indica el tamaño y tipo del documento de respuesta. En este caso, es un documento HTML de 87,320 bytes. También nos dice cuándo se modificó por última vez ese documento. +Esto nos indica el tamaño y tipo del documento de respuesta. En este caso, es un documento HTML de 87320 bytes. También nos dice cuándo se modificó por última vez ese documento. -El cliente y servidor son libres de decidir qué cabeceras incluir en sus solicitudes o respuestas. Sin embargo, algunas de ellas son necesarias para que todo funcione. Por ejemplo, sin la cabecera `Content-Type` en la respuesta, el navegador no sabrá cómo mostrar el documento. +El cliente y el servidor son libres de decidir qué cabeceras incluir en sus solicitudes o respuestas. Sin embargo, algunas de ellas son necesarias para que todo funcione. Por ejemplo, sin la cabecera `Content-Type` en la respuesta, el navegador no sabrá cómo mostrar el documento. {{index "método GET", "método DELETE", "método PUT", "método POST", "cuerpo (HTTP)"}} @@ -90,7 +92,9 @@ Como vimos, un navegador hará una solicitud cuando introducimos una URL en la b {{index paralelismo, "método GET"}} -Un sitio web moderadamente complicado puede incluir fácilmente entre 10 y 200 recursos. Para poder obtenerlos rápidamente, los navegadores harán varias solicitudes `GET` simultáneamente en lugar de esperar las respuestas una por una.Las páginas HTML pueden incluir formularios, que permiten al usuario completar información y enviarla al servidor. A continuación se muestra un ejemplo de un formulario: +Un sitio web moderadamente complicado puede incluir fácilmente entre 10 y 200 recursos. Para poder obtenerlos rápidamente, los navegadores harán varias solicitudes `GET` simultáneamente en lugar de esperar las respuestas una por una. + +Las páginas HTML pueden incluir formularios, que permiten al usuario rellenar información y enviarla al servidor. A continuación se muestra un ejemplo de un formulario: ```{lang: html} <form method="GET" action="example/message.html"> @@ -112,7 +116,7 @@ GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1 {{index "carácter ampersand"}} -El signo de interrogación indica el final de la parte de la ruta de la URL y el inicio de la consulta. Le siguen pares de nombres y valores, correspondientes al atributo `name` en los elementos del campo del formulario y al contenido de esos elementos, respectivamente. Un carácter ampersand (`&`) se utiliza para separar los pares. +El signo de interrogación indica el final de la parte de la ruta de la URL y el inicio de la consulta. Le siguen pares de nombres y valores, correspondientes al atributo `name` en los elementos del campo del formulario y al contenido de esos elementos, respectivamente. Se usa un carácter ampersand (`&`) para separar los pares. {{index [escaping, "en URLs"], "número hexadecimal", "función encodeURIComponent", "función decodeURIComponent"}} @@ -137,7 +141,9 @@ Content-type: application/x-www-form-urlencoded name=Jean&message=Yes%3F ``` -Las solicitudes `GET` deben utilizarse para solicitudes que no tengan efectos secundarios, sino simplemente para solicitar información. Las solicitudes que cambian algo en el servidor, como por ejemplo crear una nueva cuenta o publicar un mensaje, deben expresarse con otros métodos, como `POST`. El software del lado del cliente, como un navegador, sabe que no debe hacer solicitudes `POST` a ciegas, pero a menudo implícitamente realiza solicitudes `GET`, por ejemplo, para precargar un recurso que cree que pronto el usuario necesitará.Volveremos a hablar de formularios y cómo interactuar con ellos desde JavaScript [más adelante en el capítulo](http#forms). +Las solicitudes `GET` deben utilizarse para solicitudes que no tengan efectos secundarios, sino simplemente para solicitar información. Las solicitudes que cambian algo en el servidor, como por ejemplo crear una nueva cuenta o publicar un mensaje, deben expresarse con otros métodos, como `POST`. El software del lado del cliente, como un navegador, sabe que no debe hacer solicitudes `POST` a ciegas, pero a menudo implícitamente realiza solicitudes `GET`, por ejemplo, para precargar un recurso que cree que pronto el usuario necesitará. + +Volveremos a hablar de formularios y cómo interactuar con ellos desde JavaScript [más adelante en el capítulo](http#formularios). {{id fetch}} @@ -145,20 +151,20 @@ Las solicitudes `GET` deben utilizarse para solicitudes que no tengan efectos se {{index "función fetch", "clase Promise", [interfaz, "módulo"]}} -La interfaz a través de la cual JavaScript del navegador puede hacer solicitudes HTTP se llama `fetch`. +La interfaz a través de la cual el JavaScript del navegador puede hacer solicitudes HTTP se llama `fetch`. ```{test: no} -fetch("ejemplo/datos.txt").then(response => { - console.log(response.status); +fetch("example/data.txt").then(respuesta => { + console.log(respuesta.status); // → 200 - console.log(response.headers.get("Content-Type")); + console.log(respuesta.headers.get("Content-Type")); // → text/plain }); ``` {{index "clase Response", "propiedad status", "propiedad headers"}} -Llamar a `fetch` devuelve una promesa que se resuelve en un objeto `Response` que contiene información sobre la respuesta del servidor, como su código de estado y sus encabezados. Los encabezados están envueltos en un objeto similar a un `Map` que trata sus claves (los nombres de los encabezados) como insensibles a mayúsculas y minúsculas porque los nombres de los encabezados no deben ser sensibles a mayúsculas y minúsculas. Esto significa que `headers.get("Content-Type")` y `headers.get("content-TYPE")` devolverán el mismo valor. +Llamar a `fetch` devuelve una promesa que se resuelve en un objeto `Response` que contiene información sobre la respuesta del servidor, como su código de estado y sus encabezados. Los encabezados están envueltos en un objeto similar a un `Map` que trata sus claves (los nombres de los encabezados) como insensibles a mayúsculas y minúsculas porque los nombres de los encabezados no deben serlo. Esto significa que `headers.get("Content-Type")` y `headers.get("content-TYPE")` devolverán el mismo valor. Ten en cuenta que la promesa devuelta por `fetch` se resuelve con éxito incluso si el servidor responde con un código de error. También puede ser rechazada si hay un error de red o si el ((servidor)) al que se dirige la solicitud no se puede encontrar. @@ -168,7 +174,7 @@ El primer argumento de `fetch` es la URL que se debe solicitar. Cuando esa ((URL {{index "método text", "cuerpo (HTTP)", "clase Promise"}} -Para acceder al contenido real de una respuesta, puedes usar su método `text`. Debido a que la promesa inicial se resuelve tan pronto como se han recibido los encabezados de la respuesta y porque leer el cuerpo de la respuesta podría llevar un poco más de tiempo, esto devuelve nuevamente una promesa. +Para acceder al contenido en sí de una respuesta, puedes usar su método `text`. Como la promesa inicial se resuelve tan pronto como se reciben los encabezados de la respuesta y dado que leer el cuerpo de la respuesta podría llevar un poco más de tiempo, esto devuelve nuevamente una promesa. ```{test: no} fetch("ejemplo/datos.txt") @@ -183,10 +189,10 @@ Un método similar, llamado `json`, devuelve una promesa que se resuelve al valo {{index "método GET", "cuerpo (HTTP)", "método DELETE", "propiedad método"}} -Por defecto, `fetch` utiliza el método `GET` para realizar su solicitud y no incluye un cuerpo de solicitud. Puedes configurarlo de manera diferente pasando un objeto con opciones adicionales como segundo argumento. Por ejemplo, esta solicitud intenta eliminar `ejemplo/datos.txt`: +Por defecto, `fetch` utiliza el método `GET` para realizar su solicitud y no incluye un cuerpo de solicitud. Puedes configurarlo de manera diferente pasando un objeto con opciones adicionales como segundo argumento. Por ejemplo, esta solicitud intenta eliminar `example/data.txt`: ```{test: no} -fetch("ejemplo/datos.txt", {method: "DELETE"}).then(resp => { +fetch("example/data.txt", {method: "DELETE"}).then(resp => { console.log(resp.status); // → 405 }); @@ -201,13 +207,13 @@ El código de estado 405 significa "método no permitido", la forma en que un se Para agregar un cuerpo de solicitud, puedes incluir una opción `body`. Para establecer cabeceras, está la opción `headers`. Por ejemplo, esta solicitud incluye una cabecera `Range`, que indica al servidor que devuelva solo una parte de un documento. ```{test: no} -fetch("ejemplo/datos.txt", {headers: {Range: "bytes=8-19"}}) +fetch("example/data.txt", {headers: {Range: "bytes=8-19"}}) .then(resp => resp.text()) .then(console.log); -// → el contenido +// → the content ``` -El navegador automáticamente añadirá algunas cabeceras de solicitud, como "Host" y aquellas necesarias para que el servidor pueda determinar el tamaño del cuerpo. Sin embargo, añadir tus propias cabeceras es muchas veces útil para incluir cosas como información de autenticación o para indicar al servidor en qué formato de archivo te gustaría recibir. +El navegador automáticamente añadirá algunas cabeceras de solicitud, como "Host" y aquellas necesarias para que el servidor pueda determinar el tamaño del cuerpo. Sin embargo, añadir tus propias cabeceras es muchas veces útil para incluir cosas como información de autenticación o para indicar al servidor qué formato de archivo te gustaría recibir. {{id http_sandbox}} @@ -215,7 +221,7 @@ El navegador automáticamente añadirá algunas cabeceras de solicitud, como "Ho {{index sandbox, [navegador, seguridad]}} -Realizar solicitudes ((HTTP)) en scripts de páginas web plantea nuevamente preocupaciones sobre ((seguridad)). La persona que controla el script puede no tener los mismos intereses que la persona en cuya computadora se está ejecutando. Específicamente, si visito _themafia.org_, no quiero que sus scripts puedan hacer una solicitud a _mybank.com_, utilizando información de identificación de mi navegador, con instrucciones para transferir todo mi dinero. +Realizar solicitudes ((HTTP)) en scripts de páginas web plantea preocupaciones sobre ((seguridad)). La persona que controla el script puede no tener los mismos intereses que la persona en cuya computadora se está ejecutando. Específicamente, si visito _themafia.org_, no quiero que sus scripts puedan hacer una solicitud a _mybank.com_, utilizando información de identificación de mi navegador, con instrucciones para transferir todo mi dinero. Por esta razón, los navegadores nos protegen al impedir que los scripts hagan solicitudes HTTP a otros ((dominios)) (nombres como _themafia.org_ y _mybank.com_). @@ -235,13 +241,15 @@ Cuando se construye un sistema que requiere ((comunicación)) entre un programa {{index [red, "abstracción"], "abstracción"}} -Un modelo comúnmente utilizado es el de las _llamadas de procedimiento remoto_. En este modelo, la comunicación sigue los patrones de llamadas de función normales, excepto que la función en realidad se está ejecutando en otra máquina. Llamarla implica hacer una solicitud al servidor que incluye el nombre de la función y sus argumentos. La respuesta a esa solicitud contiene el valor devuelto. +Un modelo comúnmente utilizado es el de las _llamadas de procedimiento remoto_. En este modelo, la comunicación sigue los patrones de llamadas de función normales, excepto por que la función en realidad se está ejecutando en otra máquina. Llamarla implica hacer una solicitud al servidor que incluye el nombre de la función y sus argumentos. La respuesta a esa solicitud contiene el valor devuelto. Cuando se piensa en términos de llamadas de procedimiento remoto, HTTP es simplemente un vehículo de comunicación, y es muy probable que escribas una capa de abstracción que lo oculte por completo. {{index "tipo de medio", "formato del documento", ["método", HTTP]}} -Otro enfoque es construir tu comunicación en torno al concepto de ((recurso))s y métodos ((HTTP)). En lugar de un procedimiento remoto llamado `addUser`, usas una solicitud `PUT` a `/usuarios/larry`. En lugar de codificar las propiedades de ese usuario en argumentos de función, defines un formato de documento JSON (o utilizas un formato existente) que represente a un usuario. El cuerpo de la solicitud `PUT` para crear un nuevo recurso es entonces dicho documento. Se obtiene un recurso realizando una solicitud `GET` a la URL del recurso (por ejemplo, `/usuario/larry`), que de nuevo devuelve el documento que representa al recurso.Este segundo enfoque facilita el uso de algunas de las características que proporciona HTTP, como el soporte para la caché de recursos (mantener una copia de un recurso en el cliente para un acceso rápido). Los conceptos utilizados en HTTP, que están bien diseñados, pueden proporcionar un conjunto útil de principios para diseñar la interfaz de tu servidor. +Otro enfoque es construir tu comunicación en torno al concepto de ((recurso))s y métodos ((HTTP)). En lugar de un procedimiento remoto llamado `addUser`, usas una solicitud `PUT` a `/usuarios/larry`. En lugar de codificar las propiedades de ese usuario en argumentos de función, defines un formato de documento JSON (o utilizas un formato existente) que represente a un usuario. El cuerpo de la solicitud `PUT` para crear un nuevo recurso es entonces dicho documento. Se obtiene un recurso realizando una solicitud `GET` a la URL del recurso (por ejemplo, `/usuario/larry`), que de nuevo devuelve el documento que representa al recurso. + +Este segundo enfoque facilita el uso de algunas de las características que proporciona HTTP, como el soporte para la caché de recursos (mantener una copia de un recurso en el cliente para un acceso rápido). Los conceptos utilizados en HTTP, que están bien diseñados, pueden proporcionar un conjunto útil de principios para diseñar la interfaz de tu servidor. ## Seguridad y HTTPS @@ -251,25 +259,25 @@ Los datos que viajan por Internet tienden a seguir un largo y peligroso camino. {{index "manipulación"}} -Si es importante que algo se mantenga en secreto, como la ((contraseña)) de tu cuenta de ((correo electrónico)), o que llegue a su destino sin modificaciones, como el número de cuenta al que transfieres dinero a través del sitio web de tu banco, HTTP simple no es suficiente. +Si es importante que algo se mantenga en secreto, como la ((contraseña)) de tu cuenta de ((correo electrónico)), o que llegue a su destino sin modificaciones, como el número de cuenta al que transfieres dinero a través del sitio web de tu banco, el HTTP simple no es suficiente. {{index "criptografía", cifrado}} {{indexsee "HTTP seguro", HTTPS, [navegador, seguridad]}} -El protocolo seguro ((HTTP)), utilizado para ((URL))s que comienzan con _https://_, envuelve el tráfico HTTP de una manera que dificulta su lectura y manipulación. Antes de intercambiar datos, el cliente verifica que el servidor sea quien dice ser, solicitándole que demuestre que tiene un ((certificado)) criptográfico emitido por una autoridad de certificación que el navegador reconoce. Luego, todos los datos que pasan por la ((conexión)) están encriptados de una manera que debería evitar el espionaje y la manipulación. +El protocolo seguro de ((HTTP)), utilizado para ((URL))s que comienzan con _https://_, envuelve el tráfico HTTP de una manera que dificulta su lectura y manipulación. Antes de intercambiar datos, el cliente verifica que el servidor sea quien dice ser, solicitándole que demuestre que tiene un ((certificado)) criptográfico emitido por una autoridad de certificación que el navegador reconoce. Luego, todos los datos que pasan por la ((conexión)) están encriptados de una manera que debería evitar el espionaje y la manipulación. -Así, cuando funciona correctamente, ((HTTPS)) evita que otras personas se hagan pasar por el sitio web con el que estás intentando comunicarte _y_ que espíen tu comunicación. No es perfecto, y ha habido varios incidentes en los que HTTPS falló debido a certificados falsificados o robados y software defectuoso, pero es _mucho_ más seguro que el simple HTTP. +Así, cuando funciona correctamente, ((HTTPS)) evita que otras personas se hagan pasar por el sitio web con el que estás intentando comunicarte _y_ que espíen tu comunicación. No es perfecto, y ha habido varios incidentes en los que HTTPS falló debido a certificados falsificados o robados y software defectuoso, pero es _mucho_ más seguro que el HTTP simple. {{id formularios}} ## Campos de formulario -Los formularios fueron diseñados originalmente para la Web pre-JavaScript para permitir que los sitios web envíen información enviada por el usuario en una solicitud HTTP. Este diseño asume que la interacción con el servidor siempre ocurre navegando a una nueva página. +Los formularios fueron diseñados originalmente para la Web pre-JavaScript para permitir que los sitios web envíen información del usuario en una solicitud HTTP. Este diseño asume que la interacción con el servidor siempre ocurre navegando a una nueva página. {{index [DOM, campos]}} -Pero sus elementos son parte del DOM al igual que el resto de la página, y los elementos DOM que representan los campos de formulario admiten una serie de propiedades y eventos que no están presentes en otros elementos. Esto hace posible inspeccionar y controlar dichos campos de entrada con programas JavaScript y hacer cosas como agregar nueva funcionalidad a un formulario o utilizar formularios y campos como bloques de construcción en una aplicación JavaScript. +Pero sus elementos son parte del DOM al igual que el resto de la página, y los elementos del DOM que representan los campos de formulario admiten una serie de propiedades y eventos que no están presentes en otros elementos. Esto hace posible inspeccionar y controlar dichos campos de entrada con programas JavaScript y hacer cosas como agregar nueva funcionalidad a un formulario o utilizar formularios y campos como bloques de construcción en una aplicación JavaScript. {{index "formulario (etiqueta HTML)"}} @@ -283,33 +291,33 @@ Muchos tipos de campos utilizan la etiqueta `<input>`. El atributo `type` de est {{table {cols: [1,5]}}} -| `texto` | Un campo de una línea ((campo de texto)) -| `contraseña` | Igual que `texto` pero oculta el texto que se escribe -| `casilla de verificación` | Un interruptor de encendido/apagado +| `text` | Un campo de una línea ((campo de texto)) +| `password` | Igual que `text` pero oculta el texto que se escribe +| `checkbox` | Un interruptor de encendido/apagado | `color` | Un color -| `fecha` | Una fecha de calendario +| `date` | Una fecha de calendario | `radio` | (Parte de) un campo de ((opción múltiple)) -| `archivo` | Permite al usuario elegir un archivo de su computadora +| `file` | Permite al usuario elegir un archivo de su computadora {{index "atributo valor", "atributo marcado", "formulario (etiqueta HTML)"}} Los campos de formulario no necesariamente tienen que aparecer en una etiqueta `<form>`. Puedes ponerlos en cualquier parte de una página. Campos sin formulario no pueden ser ((enviado))s (solo un formulario en su totalidad puede), pero al responder a la entrada con JavaScript, a menudo no queremos enviar nuestros campos de forma normal de todos modos. ```{lang: html} -<p><input type="texto" value="abc"> (texto)</p> -<p><input type="contraseña" value="abc"> (contraseña)</p> -<p><input type="casilla de verificación" checked> (casilla de verificación)</p> +<p><input type="text" value="abc"> (texto)</p> +<p><input type="password" value="abc"> (contraseña)</p> +<p><input type="checkbox" checked> (casilla de verificación)</p> <p><input type="color" value="naranja"> (color)</p> -<p><input type="fecha" value="2023-10-13"> (fecha)</p> +<p><input type="date" value="2023-10-13"> (fecha)</p> <p><input type="radio" value="A" name="elección"> <input type="radio" value="B" name="elección" checked> <input type="radio" value="C" name="elección"> (radio)</p> -<p><input type="archivo"> (archivo)</p> +<p><input type="file"> (archivo)</p> ``` {{if book -Los campos creados con este código HTML lucen así: +Los campos creados con este código HTML tienen este aspecto: {{figure {url: "img/form_fields.png", alt: "Captura de pantalla que muestra varios tipos de etiquetas de entrada", width: "4cm"}}} @@ -319,7 +327,7 @@ La interfaz de JavaScript para estos elementos difiere según el tipo de element {{index "área de texto (etiqueta HTML)", "campo de texto"}} -Los campos de texto de varias líneas tienen su propia etiqueta, `<textarea>`, principalmente porque sería incómodo utilizar un atributo para especificar un valor de inicio de varias líneas. La etiqueta `<textarea>` requiere una etiqueta de cierre `</textarea>` coincidente y utiliza el texto entre esas dos etiquetas, en lugar del atributo `valor`, como texto de inicio. +Los campos de texto de varias líneas tienen su propia etiqueta, `<textarea>`, principalmente porque sería incómodo utilizar un atributo para especificar un valor de inicio de varias líneas. La etiqueta `<textarea>` requiere una etiqueta de cierre `</textarea>` y utiliza el texto entre esas dos etiquetas, en lugar del atributo `valor`, como texto de inicio. ```{lang: html} <textarea> @@ -343,7 +351,7 @@ Finalmente, la etiqueta `<select>` se usa para crear un campo que permite al usu {{if book -Dicho campo luce así: +Dicho campo tiene esta pinta: {{figure {url: "img/form_select.png", alt: "Captura de pantalla que muestra un campo de selección", width: "4cm"}}} @@ -351,7 +359,7 @@ if}} {{index "evento de cambio"}} -Cada vez que cambia el valor de un campo de formulario, se desencadenará un evento `"cambio"`. +Cada vez que cambia el valor de un campo de formulario, se desencadenará un evento "`change`". ## Enfoque @@ -359,7 +367,7 @@ Cada vez que cambia el valor de un campo de formulario, se desencadenará un eve {{indexsee "enfoque del teclado", enfoque}} -A diferencia de la mayoría de elementos en documentos HTML, los campos de formulario pueden obtener _((enfoque)) de teclado_. Cuando se hace clic, se mueve con la tecla [tab]{keyname}, o se activa de alguna otra manera, se convierten en el elemento activo actual y en el receptor de la ((entrada)) de teclado. +A diferencia de la mayoría de elementos en documentos HTML, los campos de formulario pueden obtener _((enfoque)) de teclado_. Cuando se hace clic en ellos, se utiliza la tecla [tab]{keyname} para navegar hasta ellos, o se activan de alguna otra manera, se convierten en el elemento activo actual y en el receptor de la ((entrada)) de teclado. {{index "option (etiqueta HTML)", "select (etiqueta HTML)"}} @@ -409,7 +417,7 @@ Todos los campos de ((formulario)) pueden ser _deshabilitados_ a través de su a <button disabled>Estoy fuera</button> ``` -Los campos deshabilitados no pueden ser ((enfocar))dos ni modificados, y los navegadores los muestran de color gris y atenuados. +Los campos deshabilitados no pueden se pueden ((enfocar)) ni modificar, y los navegadores los muestran de color gris y atenuados. {{if book @@ -419,20 +427,20 @@ if}} {{index "experiencia del usuario"}} -Cuando un programa está en proceso de manejar una acción provocada por algún ((botón)) u otro control que podría requerir comunicación con el servidor y por lo tanto llevar un tiempo, puede ser una buena idea deshabilitar el control hasta que la acción haya terminado. De esta forma, cuando el usuario se impaciente y hace clic nuevamente, no repiten accidentalmente su acción. +Cuando un programa está en proceso de manejar una acción provocada por algún ((botón)) u otro control que podría requerir comunicación con el servidor y por lo tanto llevar un tiempo, puede ser una buena idea deshabilitar el control hasta que la acción haya terminado. De esta forma, cuando el usuario se impaciente y haga clic nuevamente, no repetirán accidentalmente su acción. ## El formulario en su totalidad {{index "objeto similar a un array", "formulario (etiqueta HTML)", "propiedad formulario", "propiedad elementos"}} -Cuando un ((field)) está contenido en un elemento `<form>`, su elemento DOM tendrá una propiedad `form` que enlaza de vuelta al elemento DOM del formulario. El elemento `<form>`, a su vez, tiene una propiedad llamada `elements` que contiene una colección similar a un array de los campos dentro de él. +Cuando un ((field)) está contenido en un elemento `<form>`, su elemento del DOM tendrá una propiedad `form` que enlaza de vuelta al elemento del DOM del formulario. El elemento `<form>`, a su vez, tiene una propiedad llamada `elements` que contiene una colección parecida a un array de los campos dentro de él. {{index "elements property", "name attribute"}} -El atributo `name` de un campo de formulario determina la forma en que se identificará su valor cuando se ((submit))ee el formulario. También se puede utilizar como nombre de propiedad al acceder a la propiedad `elements` del formulario, la cual actúa tanto como un objeto similar a un array (accesible por número) como un ((mapa)) (accesible por nombre). +El atributo `name` de un campo de formulario determina la forma en que se identificará su valor cuando se ((entrega)) el formulario. También se puede utilizar como nombre de propiedad al acceder a la propiedad `elements` del formulario, la cual actúa como un objeto parecido a un array (es decir, accesible por número) y a su vez como un ((mapa)) (accesible por nombre). ```{lang: html} -<form action="ejemplo/enviar.html"> +<form action="example/submit.html"> Nombre: <input type="text" name="nombre"><br> Contraseña: <input type="password" name="contraseña"><br> <button type="submit">Ingresar</button> @@ -450,11 +458,11 @@ El atributo `name` de un campo de formulario determina la forma en que se identi {{index "button (HTML tag)", "type attribute", submit, "enter key"}} -Un botón con un atributo `type` de `submit` hará que, al presionarlo, se submita el formulario. Presionar [enter]{keyname} cuando un campo de formulario está enfocado tendrá el mismo efecto. +Un botón con un atributo `type` de `submit` hará que, al presionarlo, se entregue el formulario. Presionar [enter]{keyname} cuando un campo de formulario está enfocado tendrá el mismo efecto. {{index "submit event", "event handling", "preventDefault method", "page reload", "GET method", "POST method"}} -Enviar un ((form))ulario normalmente significa que el ((navegador)) se dirige a la página indicada por el atributo `action` del formulario, utilizando ya sea una solicitud `GET` o `POST`. Pero antes de que eso ocurra, se dispara un evento `"submit"`. Puedes manejar este evento con JavaScript y prevenir este comportamiento por defecto llamando a `preventDefault` en el objeto de evento. +Enviar un ((form))ulario normalmente significa que el ((navegador)) se dirige a la página indicada por el atributo `action` del formulario, utilizando ya sea una solicitud `GET` o `POST`. Pero antes de que eso ocurra, se dispara un evento `"submit"`. Puedes manejar este evento con JavaScript y evitar este comportamiento por defecto llamando a `preventDefault` en el objeto de evento. ```{lang: html} <form> @@ -472,13 +480,13 @@ Enviar un ((form))ulario normalmente significa que el ((navegador)) se dirige a {{index "submit event", validation}} -Interceptar los eventos `"submit"` en JavaScript tiene varios usos. Podemos escribir código para verificar que los valores ingresados por el usuario tengan sentido y mostrar inmediatamente un mensaje de error en lugar de enviar el formulario. O podemos deshabilitar completamente la forma regular de enviar el formulario, como en el ejemplo, y hacer que nuestro programa maneje la entrada, posiblemente utilizando `fetch` para enviarla a un servidor sin recargar la página. +Interceptar los eventos `"submit"` en JavaScript tiene varios usos. Podemos escribir código para verificar que los valores ingresados por el usuario tengan sentido y mostrar inmediatamente un mensaje de error en lugar de enviar el formulario. O podemos deshabilitar completamente la forma usual de enviar el formulario, como en el ejemplo, y hacer que nuestro programa maneje la entrada, posiblemente utilizando `fetch` para enviarla a un servidor sin recargar la página. ## Campos de texto {{index "value attribute", "input (HTML tag)", "text field", "textarea (HTML tag)", [DOM, fields], [interface, object]}} -Los campos creados por etiquetas `<textarea>`, o etiquetas `<input>` con un tipo de `text` o `password`, comparten una interfaz común. Sus elementos DOM tienen una propiedad `value` que contiene su contenido actual como un valor de cadena. Establecer esta propiedad a otra cadena cambia el contenido del campo. +Los campos creados por etiquetas `<textarea>`, o etiquetas `<input>` con un tipo de `text` o `password`, comparten una interfaz común. Sus elementos del DOM tienen una propiedad `value` que contiene su contenido actual como un valor de cadena. Establecer esta propiedad a otra cadena cambia el contenido del campo. {{index "selectionStart property", "selectionEnd property"}} @@ -491,20 +499,20 @@ Imagina que estás escribiendo un artículo sobre Khasekhemwy pero tienes proble ```{lang: html} <textarea></textarea> <script> - let textarea = document.querySelector("textarea"); - textarea.addEventListener("keydown", event => { - if (event.key == "F2") { - replaceSelection(textarea, "Khasekhemwy"); - event.preventDefault(); + let áreaDeTexto = document.querySelector("textarea"); + áreaDeTexto.addEventListener("keydown", evento => { + if (evento.key == "F2") { + replaceSelection(áreaDeTexto, "Khasekhemwy"); + evento.preventDefault(); } }); - function replaceSelection(field, word) { - let from = field.selectionStart, to = field.selectionEnd; - field.value = field.value.slice(0, from) + word + - field.value.slice(to); + function replaceSelection(campo, palabra) { + let desde = campo.selectionStart, hasta = campo.selectionEnd; + campo.value = campo.value.slice(0, desde) + palabra + + campo.value.slice(hasta); // Coloca el cursor después de la palabra - field.selectionStart = from + word.length; - field.selectionEnd = from + word.length; + campo.selectionStart = desde + palabra.length; + campo.selectionEnd = desde + palabra.length; } </script> ``` @@ -515,7 +523,7 @@ La función `replaceSelection` reemplaza la parte actualmente seleccionada del c {{index "evento change", "evento input"}} -El evento `"change"` para un ((campo de texto)) no se activa cada vez que se escribe algo. En cambio, se activa cuando el campo pierde el ((enfoque)) después de que su contenido haya cambiado. Para responder de inmediato a los cambios en un campo de texto, se debe registrar un controlador para el evento `"input"`, que se activa cada vez que el usuario escribe un carácter, elimina texto o de otra manera manipula el contenido del campo. +El evento `"change"` para un ((campo de texto)) no se activa cada vez que se escribe algo. En cambio, se activa cuando el campo pierde el ((enfoque)) después de que su contenido haya cambiado. Para responder de inmediato a los cambios en un campo de texto, se debe registrar un controlador para el evento `"input"`, que se activa cada vez que el usuario escribe un carácter, elimina texto o manipula de otra manera el contenido del campo. El siguiente ejemplo muestra un campo de texto y un contador que muestra la longitud actual del texto en el campo: @@ -551,7 +559,7 @@ Un campo de ((casilla de verificación)) es un interruptor binario. Su valor se {{index "for attribute", "id attribute", focus, "label (HTML tag)", labeling}} -La etiqueta `<label>` asocia un fragmento de documento con un campo de entrada. Hacer clic en cualquier parte de la etiqueta activará el campo, lo enfocará e invertirá su valor cuando sea un casilla de verificación o un botón de radio. +La etiqueta `<label>` asocia un fragmento de documento con un campo de entrada. Hacer clic en cualquier parte de la etiqueta activará el campo, lo enfocará e invertirá su valor cuando sea una casilla de verificación o un botón de radio. {{index "input (HTML tag)", "multiple-choice"}} @@ -569,10 +577,10 @@ Color: <input type="radio" name="color" value="lightblue"> Azul claro </label> <script> - let buttons = document.querySelectorAll("[name=color]"); - for (let button of Array.from(buttons)) { - button.addEventListener("change", () => { - document.body.style.background = button.value; + let botones = document.querySelectorAll("[name=color]"); + for (let botón of Array.from(botones)) { + botón.addEventListener("change", () => { + document.body.style.background = botón.value; }); } </script> @@ -590,7 +598,7 @@ Los campos de selección son conceptualmente similares a los botones de radio, y {{index "multiple attribute", "drop-down menu"}} -Los campos de selección también tienen una variante que se asemeja más a una lista de casillas de verificación que a botones de radio. Cuando se le otorga el atributo `multiple`, una etiqueta `<select>` permitirá al usuario seleccionar cualquier número de opciones, en lugar de una sola opción. Mientras que un campo de selección regular se muestra como un control de _lista desplegable_, que muestra las opciones inactivas solo cuando lo abres, un campo con `multiple` habilitado muestra múltiples opciones al mismo tiempo, permitiendo al usuario habilitar o deshabilitarlas individualmente. +Los campos de selección también tienen una variante que se asemeja más a una lista de casillas de verificación que a botones de radio. Cuando se le otorga el atributo `multiple`, una etiqueta `<select>` permitirá al usuario seleccionar cualquier número de opciones, en lugar de una sola opción. Mientras que un campo de selección normal se muestra como un control de _lista desplegable_, que muestra las opciones inactivas solo cuando lo abres, un campo con `multiple` habilitado muestra múltiples opciones al mismo tiempo, permitiendo al usuario habilitarlas o deshabilitarlas individualmente. {{index "option (HTML tag)", "value attribute"}} @@ -610,18 +618,18 @@ Este ejemplo extrae los valores seleccionados de un campo de selección `multipl <option value="2">0010</option> <option value="4">0100</option> <option value="8">1000</option> -</select> = <span id="output">0</span> +</select> = <span id="salida">0</span> <script> let select = document.querySelector("select"); - let output = document.querySelector("#output"); + let salida = document.querySelector("#salida"); select.addEventListener("change", () => { - let number = 0; - for (let option of Array.from(select.options)) { - if (option.selected) { - number += Number(option.value); + let número = 0; + for (let opción of Array.from(select.options)) { + if (opción.selected) { + número += Number(opción.value); } } - output.textContent = number; + salida.textContent = número; }); </script> ``` @@ -640,9 +648,9 @@ Un campo de archivo suele parecerse a un botón etiquetado con algo como "elegir let input = document.querySelector("input"); input.addEventListener("change", () => { if (input.files.length > 0) { - let file = input.files[0]; - console.log("Has elegido", file.name); - if (file.type) console.log("Tiene tipo", file.type); + let archivo = input.files[0]; + console.log("Has elegido", archivo.name); + if (archivo.type) console.log("Tiene tipo", archivo.type); } }); </script> @@ -650,7 +658,7 @@ Un campo de archivo suele parecerse a un botón etiquetado con algo como "elegir {{index "multiple attribute", "files property"}} -La propiedad `files` de un elemento ((campo de archivo)) es un objeto similar a un arreglo (una vez más, no es un arreglo real) que contiene los archivos elegidos en el campo. Inicialmente está vacío. La razón por la que no hay simplemente una propiedad `file` es que los campos de archivo también admiten un atributo `multiple`, lo que permite seleccionar varios archivos al mismo tiempo. +La propiedad `files` de un elemento ((campo de archivo)) es un objeto similar a un array (una vez más, no es un array de verdad) que contiene los archivos elegidos en el campo. Inicialmente está vacío. La razón por la que no hay simplemente una propiedad `file` es que los campos de archivo también admiten un atributo `multiple`, lo que permite seleccionar varios archivos al mismo tiempo. {{index "File type"}} @@ -660,20 +668,20 @@ Los objetos en `files` tienen propiedades como `name` (el nombre de archivo), `s {{id filereader}} -Lo que no tiene es una propiedad que contenga el contenido del archivo. Acceder a eso es un poco más complicado. Dado que leer un archivo desde el disco puede llevar tiempo, la interfaz es asíncrona para evitar que se congele la ventana. +Lo que no tiene es una propiedad que contenga el contenido del archivo. Acceder a eso es un poco más complicado. Como leer un archivo desde el disco puede llevar tiempo, la interfaz es asíncrona para evitar que se congele la ventana. ```{lang: html} <input type="file" multiple> <script> let input = document.querySelector("input"); input.addEventListener("change", () => { - for (let file of Array.from(input.files)) { - let reader = new FileReader(); - reader.addEventListener("load", () => { - console.log("El archivo", file.name, "comienza con", - reader.result.slice(0, 20)); + for (let archivo of Array.from(input.files)) { + let lector = new FileReader(); + lector.addEventListener("load", () => { + console.log("El archivo", archivo.name, "comienza con", + lector.result.slice(0, 20)); }); - reader.readAsText(file); + lector.readAsText(archivo); } }); </script> @@ -704,11 +712,11 @@ function readFileText(file) { {{index "aplicación web"}} -Páginas simples de ((HTML)) con un poco de JavaScript pueden ser un gran formato para "((mini aplicaciones))" - pequeños programas auxiliares que automatizan tareas básicas. Conectando unos cuantos campos de formulario con controladores de eventos, puedes hacer desde convertir entre centímetros y pulgadas hasta calcular contraseñas a partir de una contraseña maestra y un nombre de sitio web. +Páginas simples de ((HTML)) con un poco de JavaScript pueden ser un gran formato para "((mini aplicaciones))" —pequeños programas auxiliares que automatizan tareas básicas. Conectando unos cuantos campos de formulario con controladores de eventos, puedes hacer desde un conversor entre centímetros y pulgadas hasta calcular contraseñas a partir de una contraseña maestra y un nombre de sitio web. {{index persistencia, ["vinculación", "como estado"], [navegador, almacenamiento]}} -Cuando una aplicación así necesita recordar algo entre sesiones, no puedes usar las vinculaciones de JavaScript, ya que estas se descartan cada vez que se cierra la página. Podrías configurar un servidor, conectarlo a Internet y hacer que tu aplicación almacene algo allí. Veremos cómo hacerlo en el [Capítulo ?](node). Pero eso implica mucho trabajo extra y complejidad. A veces es suficiente con mantener los datos en el ((navegador)). +Cuando una aplicación así necesita recordar algo entre sesiones, no puedes usar las asociaciones (o variables) de JavaScript, ya que estas se descartan cada vez que se cierra la página. Podrías configurar un servidor, conectarlo a Internet y hacer que tu aplicación almacene algo allí. Veremos cómo hacerlo en el [Capítulo ?](node). Pero eso implica mucho trabajo extra y complejidad. A veces es suficiente con mantener los datos en el ((navegador)). {{index "objeto localStorage", "método setItem", "método getItem", "método removeItem"}} @@ -742,35 +750,35 @@ Notas: <select></select> <button>Añadir</button><br> <textarea style="width: 100%"></textarea> <script> - let list = document.querySelector("select"); - let note = document.querySelector("textarea"); + let lista = document.querySelector("select"); + let nota = document.querySelector("textarea"); - let state; + let estado; function setState(nuevoEstado) { - list.textContent = ""; + lista.textContent = ""; for (let nombre of Object.keys(nuevoEstado.notes)) { - let option = document.createElement("option"); - option.textContent = nombre; - if (nuevoEstado.selected == nombre) option.selected = true; - list.appendChild(option); + let opción = document.createElement("option"); + opción.textContent = nombre; + if (nuevoEstado.selected == nombre) opción.selected = true; + lista.appendChild(opción); } - note.value = nuevoEstado.notes[nuevoEstado.selected]; + nota.value = nuevoEstado.notes[nuevoEstado.selected]; localStorage.setItem("Notas", JSON.stringify(nuevoEstado)); - state = nuevoEstado; + estado = nuevoEstado; } setState(JSON.parse(localStorage.getItem("Notas")) ?? { notes: {"lista de compras": "Zanahorias\nPasas"}, selected: "lista de compras" }); - list.addEventListener("change", () => { - setState({notes: state.notes, selected: list.value}); + lista.addEventListener("change", () => { + setState({notes: estado.notes, selected: lista.value}); }); - note.addEventListener("change", () => { - let {selected} = state; + nota.addEventListener("change", () => { + let {selected} = estado; setState({ - notes: {...state.notes, [selected]: note.value}, + notes: {...estado.notes, [selected]: nota.value}, selected }); }); @@ -778,7 +786,7 @@ Notas: <select></select> <button>Añadir</button><br> .addEventListener("click", () => { let nombre = prompt("Nombre de la nota"); if (nombre) setState({ - notes: {...state.notes, [nombre]: ""}, + notes: {...estado.notes, [nombre]: ""}, selected: nombre }); }); @@ -787,13 +795,13 @@ Notas: <select></select> <button>Añadir</button><br> {{index "método getItem", JSON, "operador ??", "valor predeterminado"}} -El script obtiene su estado inicial del valor `"Notas"` almacenado en `localStorage` o, si está ausente, crea un estado de ejemplo que solo contiene una lista de compras. Leer un campo que no existe en `localStorage` devolverá `null`. Pasar `null` a `JSON.parse` hará que analice la cadena `"null"` y devuelva `null`. Por tanto, en una situación como esta se puede utilizar el operador `??` para proporcionar un valor predeterminado. +El script obtiene su estado inicial del valor `"Notas"` almacenado en `localStorage` o, si este no existe, crea un estado de ejemplo que solo contiene una lista de compras. Leer un campo que no existe en `localStorage` devolverá `null`. Pasar `null` a `JSON.parse` hará que analice la cadena `"null"` y devuelva `null`. Por tanto, en una situación como esta se puede utilizar el operador `??` para proporcionar un valor predeterminado. El método `setState` se asegura de que el DOM muestre un estado dado y almacena el nuevo estado en `localStorage`. Los controladores de eventos llaman a esta función para moverse a un nuevo estado. {{index [objeto, "creación"], propiedad, "propiedad computada"}} -La sintaxis `...` en el ejemplo se utiliza para crear un nuevo objeto que es un clon del antiguo `state.notes`, pero con una propiedad añadida o sobrescrita. Utiliza la sintaxis ((spread)) para primero añadir las propiedades del objeto antiguo y luego establecer una nueva propiedad. La notación de ((corchetes cuadrados)) en el literal del objeto se utiliza para crear una propiedad cuyo nombre se basa en algún valor dinámico. +La sintaxis `...` en el ejemplo se utiliza para crear un nuevo objeto que es un clon del antiguo `estado.notes`, pero con una propiedad añadida o sobrescrita. Utiliza la sintaxis de ((expansión)) para primero añadir las propiedades del objeto antiguo y luego establecer una nueva propiedad. La notación de ((corchetes cuadrados)) en el literal del objeto se utiliza para crear una propiedad cuyo nombre se basa en algún valor dinámico. {{index "objeto sessionStorage", [navegador, almacenamiento]}} @@ -801,11 +809,13 @@ Existe otro objeto, similar a `localStorage`, llamado `sessionStorage`. La difer ## Resumen -En este capítulo, discutimos cómo funciona el protocolo HTTP. Un _cliente_ envía una solicitud, que contiene un método (generalmente `GET`) y una ruta que identifica un recurso. El _servidor_ luego decide qué hacer con la solicitud y responde con un código de estado y un cuerpo de respuesta. Tanto las solicitudes como las respuestas pueden contener encabezados que proporcionan información adicional.La interfaz a través de la cual JavaScript del navegador puede realizar solicitudes HTTP se llama `fetch`. Realizar una solicitud se ve así: +En este capítulo, discutimos cómo funciona el protocolo HTTP. Un _cliente_ envía una solicitud, que contiene un método (generalmente `GET`) y una ruta que identifica un recurso. El _servidor_ luego decide qué hacer con la solicitud y responde con un código de estado y un cuerpo de respuesta. Tanto las solicitudes como las respuestas pueden contener encabezados que proporcionan información adicional. + +La interfaz a través de la cual el JavaScript del navegador puede realizar solicitudes HTTP se llama `fetch`. Este es el aspecto que tiene una tal solicitud: ```js -fetch("/18_http.html").then(r => r.text()).then(text => { - console.log(`La página comienza con ${text.slice(0, 15)}`); +fetch("/18_http.html").then(r => r.text()).then(texto => { + console.log(`La página comienza con ${texto.slice(0, 15)}`); }); ``` @@ -851,11 +861,11 @@ if}} {{index "negociación de contenido (ejercicio)"}} -Basate en los ejemplos de `fetch` [anteriores en el capítulo](http#fetch). +Básate en los ejemplos de `fetch` [que vimos antes en el capítulo](http#fetch). {{index "406 (código de estado HTTP)", "encabezado Accept"}} -Al solicitar un tipo de medio falso devolverá una respuesta con el código 406, "No aceptable", que es el código que un servidor debería devolver cuando no puede cumplir con el encabezado `Accept`. +Solicitar un tipo de medio falso devolverá una respuesta con el código 406, "No aceptable", que es el código que un servidor debería devolver cuando no puede cumplir con el encabezado `Accept`. hint}} @@ -895,7 +905,7 @@ Asegúrate de envolver tanto la llamada a `Function` como la llamada a su result {{index "propiedad textContent", output, texto, "método createTextNode", "carácter de nueva línea"}} -La propiedad `textContent` del elemento de salida se puede utilizar para llenarlo con un mensaje de cadena. O, si deseas mantener el contenido anterior, crea un nuevo nodo de texto utilizando `document.createTextNode` y apéndelo al elemento. Recuerda agregar un carácter de nueva línea al final para que no aparezca toda la salida en una sola línea. +La propiedad `textContent` del elemento de salida se puede utilizar para llenarlo con un mensaje de cadena. O, si deseas mantener el contenido anterior, crea un nuevo nodo de texto utilizando `document.createTextNode` y añádelo al elemento. Recuerda agregar un carácter de nueva línea al final para que no aparezca toda la salida en una sola línea. hint}} @@ -903,19 +913,19 @@ hint}} {{index "juego de la vida (ejercicio)", "vida artificial", "Juego de la vida de Conway"}} -El Juego de la vida de Conway es una ((simulación)) simple que crea "vida" artificial en una ((rejilla)), donde cada celda puede estar viva o no. En cada ((generación)) (turno), se aplican las siguientes reglas: +El Juego de la vida de Conway es una ((simulación)) simple que crea "vida" artificial en una ((rejilla)), donde cada célula puede estar viva o no. En cada ((generación)) (turno), se aplican las siguientes reglas: -* Cualquier celda viva con menos de dos o más de tres vecinos vivos muere. +* Cualquier célula viva con menos de dos o más de tres vecinos vivos muere. -* Cualquier celda viva con dos o tres vecinos vivos sigue viva en la siguiente generación. +* Cualquier célula viva con dos o tres vecinos vivos sigue viva en la siguiente generación. -* Cualquier celda muerta con exactamente tres vecinos vivos se convierte en una celda viva. +* Cualquier célula muerta con exactamente tres vecinos vivos se convierte en una célula viva. -Un _vecino_ se define como cualquier celda adyacente, incluidas las células adyacentes en diagonal. +Un _vecino_ se define como cualquier célula adyacente, incluidas las célula adyacentes en diagonal. {{index "función pura"}} -Ten en cuenta que estas reglas se aplican a toda la rejilla de una vez, no cuadrado por cuadrado. Eso significa que el recuento de vecinos se basa en la situación al comienzo de la generación, y los cambios que ocurran en las células vecinas durante esta generación no deberían influir en el nuevo estado de una celda dada. +Ten en cuenta que estas reglas se aplican a toda la rejilla a la vez, no cuadrado por cuadrado. Eso significa que el recuento de vecinos se basa en la situación al comienzo de la generación, y los cambios que ocurran en las células vecinas durante esta generación no deberían influir en el nuevo estado de una célula dada. {{index "función Math.random"}} @@ -940,16 +950,16 @@ if}} Para resolver el problema de que los cambios ocurran conceptualmente al mismo tiempo, intenta ver la computación de una ((generación)) como una ((función pura)), la cual toma un ((grid)) y produce un nuevo grid que representa el siguiente turno. -La representación de la matriz se puede hacer con un solo array de elementos de ancho × alto, almacenando valores fila por fila, por lo que, por ejemplo, el tercer elemento en la quinta fila se almacena en la posición 4 × _ancho_ + 2 (usando indexación basada en cero). Puedes contar los ((vecinos)) vivos con dos bucles anidados, recorriendo coordenadas adyacentes en ambas dimensiones. Asegúrate de no contar celdas fuera del campo e ignorar la celda en el centro, cuyos vecinos estamos contando. +La representación de la matriz se puede hacer con un solo array de elementos de ancho × alto, almacenando valores fila por fila, por lo que, por ejemplo, el tercer elemento en la quinta fila se almacena en la posición 4 × _ancho_ + 2 (usando indexación basada en cero). Puedes contar los ((vecinos)) vivos con dos bucles anidados, recorriendo coordenadas adyacentes en ambas dimensiones. Asegúrate de no contar célula fuera del campo e ignorar la célula en el centro, cuyos vecinos estamos contando. {{index "manejo de eventos", "evento de cambio"}} -Asegurarse de que los cambios en los ((checkbox)) tengan efecto en la siguiente generación se puede hacer de dos maneras. Un manejador de eventos podría notar estos cambios y actualizar el grid actual para reflejarlos, o podrías generar un grid nuevo a partir de los valores de los checkboxes antes de calcular el siguiente turno. +Asegurarse de que los cambios en los ((checkbox)) tengan efecto en la siguiente generación es algo que se puede hacer de dos maneras. Un manejador de eventos podría notar estos cambios y actualizar el grid actual para reflejarlos, o podrías generar un grid nuevo a partir de los valores de los checkboxes antes de calcular el siguiente turno. -Si decides utilizar manejadores de eventos, es posible que desees adjuntar ((atributo))s que identifiquen la posición a la que corresponde cada checkbox para que sea fácil saber qué celda cambiar. +Si decides utilizar manejadores de eventos, es posible que desees adjuntar ((atributo))s que identifiquen la posición a la que corresponde cada checkbox para que sea fácil saber qué célula cambiar. {{index dibujo, "tabla (etiqueta HTML)", "br (etiqueta HTML)"}} -Para dibujar el grid de checkboxes, puedes usar un elemento `<table>` (ver [Capítulo ?](dom#ejercicio_table)) o simplemente colocar todos en el mismo elemento y poner elementos `<br>` (salto de línea) entre las filas. +Para dibujar el grid de checkboxes, puedes usar un elemento `<table>` (ver [Capítulo ?](dom#exercise_table)) o simplemente colocar todos en el mismo elemento y poner elementos `<br>` (salto de línea) entre las filas. hint}} \ No newline at end of file diff --git a/19_paint.md b/19_paint.md index b0423f0a..f7ee34bc 100644 --- a/19_paint.md +++ b/19_paint.md @@ -1,14 +1,14 @@ {{meta {load_files: ["code/chapter/19_paint.js"], zip: "html", include: ["css/paint.css"]}}} -# Proyecto: Editor de Arte Pixelado +# Proyecto: Editor de Píxel Art -{{quote {author: "Joan Miro", chapter: true} +{{quote {author: "Joan Miró", chapter: true} -Observo los muchos colores ante mí. Observo mi lienzo en blanco. Luego, intento aplicar colores como palabras que conforman poemas, como notas que conforman música. +Observo todos los colores que tengo ante mí. Observo mi lienzo en blanco. Luego, intento aplicar colores como palabras que conforman poemas, como notas que conforman música. quote}} -{{index "Miro, Joan", "ejemplo de programa de dibujo", "capítulo del proyecto"}} +{{index "Miró, 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"}}} @@ -18,7 +18,7 @@ El material de los capítulos anteriores te brinda todos los elementos que neces Nuestra ((aplicación)) será un programa de ((dibujo)) de pixeles, donde puedes modificar una imagen píxel por píxel manipulando una vista ampliada de la misma, mostrada como una rejilla de cuadros de colores. Puedes utilizar el programa para abrir archivos de imagen, garabatear en ellos con tu ratón u otro dispositivo señalador, y guardarlos. Así es cómo se verá: -{{figure {url: "img/pixel_editor.png", alt: "Captura de pantalla de la interfaz del editor de píxeles, con una rejilla de píxeles de colores en la parte superior y una serie de controles, en forma de campos y botones HTML, debajo de eso", width: "8cm"}}} +{{figure {url: "img/pixel_editor.png", alt: "Captura de pantalla de la interfaz del editor de píxeles, con una rejilla de píxeles de colores en la parte superior y una serie de controles, en forma de campos y botones HTML, debajo", width: "8cm"}}} Pintar en una computadora es genial. No necesitas preocuparte por materiales, ((habilidad)) o talento. Simplemente comienzas a manchar y ves hacia dónde llegas. @@ -36,7 +36,7 @@ Estructuraremos la interfaz del editor como un conjunto de _((componente))s_, ob El estado de la aplicación consiste en la imagen actual, la herramienta seleccionada y el color seleccionado. Organizaremos las cosas de manera que el estado resida en un único valor, y los componentes de la interfaz siempre se basen en el estado actual para verse. -Para entender por qué esto es importante, consideremos la alternativa: distribuir piezas de estado a lo largo de la interfaz. Hasta cierto punto, esto es más fácil de programar. Podemos simplemente agregar un ((campo de color)) y leer su valor cuando necesitemos saber el color actual. +Para entender por qué esto es importante, consideremos la otra alternativa: distribuir partes de estado a lo largo de la interfaz. Hasta cierto punto, esto es más fácil de programar. Podemos simplemente agregar un ((campo de color)) y leer su valor cuando necesitemos saber el color actual. Pero luego agregamos el ((selector de colores)) —una herramienta que te permite hacer clic en la imagen para seleccionar el color de un píxel determinado. Para mantener el campo de color mostrando el color correcto, esa herramienta tendría que saber que el campo de color existe y actualizarlo cada vez que elige un nuevo color. Si alguna vez añades otro lugar que muestre el color (quizás el cursor del ratón podría mostrarlo), tendrías que actualizar tu código de cambio de color para mantener eso sincronizado también. @@ -44,15 +44,15 @@ Pero luego agregamos el ((selector de colores)) —una herramienta que te permit De hecho, esto crea un problema en el que cada parte de la interfaz necesita saber acerca de todas las demás partes, lo cual no es muy modular. Para aplicaciones pequeñas como la de este capítulo, eso puede no ser un problema. Para proyectos más grandes, puede convertirse en una verdadera pesadilla. -Para evitar esta pesadilla en principio, vamos a ser estrictos acerca del _((flujo de datos))_. Hay un estado, y la interfaz se dibuja basada en ese estado. Un componente de la interfaz puede responder a las acciones del usuario actualizando el estado, momento en el cual los componentes tienen la oportunidad de sincronizarse con este nuevo estado. +Para evitar esta pesadilla de entrada, vamos a ser estrictos acerca del _((flujo de datos))_. Hay un estado, y la interfaz se dibuja en base a ese estado. Un componente de la interfaz puede responder a las acciones del usuario actualizando el estado, momento en el cual los componentes tienen la oportunidad de sincronizarse con este nuevo estado. {{index biblioteca, marco de trabajo}} -En la práctica, cada ((componente)) se configura para que, cuando reciba un nuevo estado, también notifique a sus componentes hijos, en la medida en que estos necesiten ser actualizados. Configurar esto es un poco tedioso. Hacer que esto sea más conveniente es el principal punto de venta de muchas bibliotecas de programación para el navegador. Pero para una aplicación pequeña como esta, podemos hacerlo sin dicha infraestructura. +En la práctica, cada ((componente)) se configura para que, cuando reciba un nuevo estado, también notifique a sus componentes hijos, en la medida en que estos necesiten ser actualizados. Configurar esto es un poco tedioso. Hacer que esto sea más cómodo es el principal atractivo de muchas bibliotecas de programación para el navegador. Pero para una aplicación pequeña como esta, podemos hacerlo sin dicha infraestructura. {{index [estado, transiciones]}} -Las actualizaciones al estado se representan como objetos, a los que llamaremos _((acciones))_. Los componentes pueden crear tales acciones y _((despachar))_ (enviarlos) a una función central de gestión de estado. Esa función calcula el próximo estado, tras lo cual los componentes de la interfaz se actualizan a este nuevo estado. +Las actualizaciones al estado se representan como objetos, a los que llamaremos _((acciones))_. Los componentes pueden crear tales acciones y _((despachar))_los (enviarlos) a una función central de gestión de estado. Esa función calcula el próximo estado, tras lo cual los componentes de la interfaz se actualizan a este nuevo estado. {{index [DOM, componentes]}} @@ -64,7 +64,7 @@ Hay _muchas_ variantes de este enfoque, cada una con sus propios beneficios y pr {{index "propiedad dom", [interfaz, objeto]}} -Nuestros ((componente))s serán ((clases)) que cumplan con una interfaz. Su constructor recibe un estado, que puede ser el estado de toda la aplicación o algún valor más pequeño si no necesita acceso a todo, y lo utiliza para construir una propiedad `dom`. Este es el elemento DOM que representa el componente. La mayoría de los constructores también tomarán otros valores que no cambiarán con el tiempo, como la función que pueden utilizar para ((despachar)) una acción. +Nuestros ((componente))s serán ((clases)) que cumplan con una interfaz. Su constructor recibe un estado, que puede ser el estado de toda la aplicación o algún valor más pequeño si no necesita acceso a todo, y lo utiliza para construir una propiedad `dom`. Este es el elemento del DOM que representa el componente. La mayoría de los constructores también tomarán otros valores que no cambiarán con el tiempo, como la función que pueden utilizar para ((despachar)) una acción. {{index "método syncState"}} @@ -102,7 +102,7 @@ class Picture { {{index "side effect", "persistent data structure"}} -Queremos poder tratar una imagen como un valor ((inmutable)) por razones que revisaremos más adelante en el capítulo. Pero a veces necesitamos actualizar todo un conjunto de píxeles a la vez. Para poder hacerlo, la clase tiene un método `draw` que espera un array de píxeles actualizados, objetos con propiedades `x`, `y` y `color`, y crea una nueva imagen con esos píxeles sobrescritos. Este método utiliza `slice` sin argumentos para copiar todo el array de píxeles - el inicio de la rebanada predetermina a 0, y el final predetermina a la longitud del array. +Queremos poder tratar una imagen como un valor ((inmutable)) por razones que revisaremos más adelante en el capítulo. Pero a veces necesitamos actualizar todo un conjunto de píxeles a la vez. Para poder hacerlo, la clase tiene un método `draw` que espera un array de píxeles actualizados, objetos con propiedades `x`, `y` y `color`, y crea una nueva imagen con esos píxeles sobrescritos. Este método utiliza `slice` sin argumentos para copiar todo el array de píxeles - el inicio de este corte está predeterminado a 0, y el final está predeterminado a la longitud del array. {{index "Array constructor", "fill method", ["length property", "for array"], [array, creation]}} @@ -110,7 +110,7 @@ El método `empty` utiliza dos funcionalidades de array que no hemos visto antes {{index "número hexadecimal", "componente de color", "campo de color", "propiedad fillStyle"}} -Los colores se almacenan como cadenas que contienen códigos de colores CSS tradicionales compuestos por un ((signo de almohadilla)) (`#`) seguido de seis dígitos hexadecimales (base-16) - dos para el componente ((rojo)), dos para el componente ((verde)) y dos para el componente ((azul)). Esta es una forma algo críptica e incómoda de escribir colores, pero es el formato que utiliza el campo de entrada de color HTML, y se puede usar en la propiedad `fillStyle` de un contexto de dibujo de lienzo, por lo que para las formas en que usaremos colores en este programa, es lo bastante práctico. +Los colores se almacenan como cadenas que contienen códigos de colores CSS tradicionales, compuestos por un ((signo de almohadilla)) (`#`) seguido de seis dígitos hexadecimales (base-16) - dos para el componente ((rojo)), dos para el componente ((verde)) y dos para el componente ((azul)). Esta es una forma algo críptica e incómoda de escribir colores, pero es el formato que utiliza el campo de entrada de color HTML, y se puede usar en la propiedad `fillStyle` de un contexto de dibujo de lienzo, por lo que para las formas en que vamos a usar colores en este programa, es lo suficientemente práctico. {{index negro}} @@ -130,13 +130,13 @@ function updateState(state, action) { {{index "punto"}} -Este patrón, en el que el operador de ((spread)) de objetos se utiliza primero para agregar las propiedades de un objeto existente y luego para anular algunas de ellas, es común en el código de JavaScript que utiliza objetos ((inmutables)). +Este patrón, en el que el operador de ((expansión)) de objetos se utiliza primero para agregar las propiedades de un objeto existente y luego para anular algunas de ellas, es común en el código de JavaScript que utiliza objetos ((inmutables)). ## Construcción del DOM {{index "método `createElement`", "función `elt`", [DOM, "construcción"]}} -Una de las principales funciones que cumplen los componentes de la interfaz es crear una estructura DOM. Nuevamente, no queremos utilizar directamente los métodos verbosos del DOM para eso, así que aquí tienes una versión ligeramente ampliada de la función `elt`: +Una de las principales funciones que cumplen los componentes de la interfaz es crear una estructura DOM. De nuevo, no queremos utilizar directamente los métodos verbosos del DOM para eso, así que aquí tienes una versión ligeramente ampliada de la función `elt`: ```{includeCode: true} function elt(type, props, ...children) { @@ -152,7 +152,7 @@ function elt(type, props, ...children) { {{index "método `setAttribute`", "atributo", "propiedad `onclick`", "evento de clic", "manejo de eventos"}} -La diferencia principal entre esta versión y la que usamos en el [Capítulo ?](game#domdisplay) es que asigna _propiedades_ a los nodos del DOM, no _atributos_. Esto significa que no podemos usarlo para establecer atributos arbitrarios, pero _sí_ podemos usarlo para configurar propiedades cuyo valor no es una cadena, como `onclick`, que se puede establecer como una función para registrar un controlador de eventos de clic. +La diferencia principal entre esta versión y la que usamos en el [Capítulo ?](game#domdisplay) es que esta asigna _propiedades_ a los nodos del DOM, no _atributos_. Esto significa que no podemos usarlo para establecer atributos arbitrarios, pero _sí_ podemos usarlo para configurar propiedades cuyo valor no es una cadena, como `onclick`, que se puede establecer como una función para registrar un controlador de eventos de clic. {{index "botón (etiqueta HTML)"}} @@ -170,11 +170,11 @@ Esto permite este estilo conveniente para registrar manejadores de eventos: ## El lienzo -El primer componente que definiremos es la parte de la interfaz que muestra la imagen como una cuadrícula de cuadros coloreados. Este componente es responsable de dos cosas: mostrar una imagen y comunicar ((evento de puntero))s en esa imagen al resto de la aplicación. +El primer componente que definiremos es la parte de la interfaz que muestra la imagen como una cuadrícula de cuadros coloreados. Este componente es responsable de dos cosas: mostrar una imagen y comunicar ((eventos de puntero)) en esa imagen al resto de la aplicación. {{index "clase `PictureCanvas`", "función de devolución de llamada", "constante `scale`", "lienzo (etiqueta HTML)", "evento de mousedown", "evento de touchstart", [estado, "de la aplicación"]}} -Como tal, podemos definirlo como un componente que solo conoce la imagen actual, no todo el estado de la aplicación. Dado que no sabe cómo funciona la aplicación en su totalidad, no puede despachar ((acción))es directamente. Más bien, al responder a eventos de puntero, llama a una función de devolución de llamada proporcionada por el código que lo creó, que se encargará de las partes específicas de la aplicación. +Como tal, podemos definirlo como un componente que solo conoce la imagen actual, no todo el estado de la aplicación. Como no sabe cómo funciona la aplicación en su totalidad, no puede despachar ((acción))es directamente. Más bien, al responder a eventos de puntero, llama a una función de callback proporcionada por el código que lo creó, que se encargará de las partes específicas de la aplicación. ```{includeCode: true} const scale = 10; @@ -285,9 +285,9 @@ Para eventos táctiles, `clientX` y `clientY` no están disponibles directamente ## La aplicación -Para hacer posible construir la aplicación pieza por pieza, implementaremos el componente principal como una cáscara alrededor de un lienzo de imagen y un conjunto dinámico de ((tool))s y ((control))s que pasamos a su constructor. +Para hacer posible construir la aplicación pieza por pieza, implementaremos el componente principal como una cáscara alrededor de un lienzo de imagen y un conjunto dinámico de ((herramienta))s y ((control))es que pasamos a su constructor. -Los _controles_ son los elementos de interfaz que aparecen debajo de la imagen. Se proporcionarán como un array de constructores de ((component)). +Los _controles_ son los elementos de la interfaz que aparecen debajo de la imagen. Se proporcionarán como un array de constructores de ((component))es. {{index "br (etiqueta HTML)", "flood fill", "select (etiqueta HTML)", "PixelEditor clase", dispatch}} @@ -322,7 +322,7 @@ El manejador de puntero dado a `PictureCanvas` llama a la herramienta actualment {{index "reduce method", "map method", [whitespace, "in HTML"], "syncState method"}} -Todos los controles se construyen y almacenan en `this.controls` para que puedan actualizarse cuando cambie el estado de la aplicación. La llamada a `reduce` introduce espacios entre los elementos DOM de los controles. De esa manera, no se ven tan juntos. +Todos los controles se construyen y almacenan en `this.controls` para que puedan actualizarse cuando cambie el estado de la aplicación. La llamada a `reduce` introduce espacios entre los elementos del DOM de los controles. De esa manera, no se ven tan juntos. {{index "select (etiqueta HTML)", "change event", "ToolSelect clase", "syncState method"}} @@ -352,7 +352,7 @@ También necesitamos poder cambiar el color, así que agreguemos un control para {{if book -Dependiendo del navegador, el selector de color puede lucir así: +Dependiendo del navegador, el selector de color puede tener un aspecto como este: {{figure {url: "img/color-field.png", alt: "Captura de pantalla del campo de color", width: "6cm"}}} @@ -382,7 +382,7 @@ Antes de poder dibujar algo, necesitamos implementar las ((herramienta))s que co {{index "función de dibujo"}} -La herramienta más básica es la herramienta de dibujo, que cambia cualquier ((píxel)) en el que hagas clic o toques al color seleccionado actualmente. Envía una acción que actualiza la imagen a una versión en la que el píxel señalado recibe el color seleccionado actualmente. +La herramienta más básica es la herramienta de dibujo, que cambia el colo de cualquier ((píxel)) en el que hagas clic o toques al color seleccionado actualmente. Envía una acción que actualiza la imagen a una versión en la que el píxel señalado recibe el color seleccionado actualmente. ```{includeCode: true} function draw(pos, state, dispatch) { @@ -434,26 +434,26 @@ Implementar el ((relleno por inundación)) es algo más complejo. Se trata de un Curiosamente, la forma en que lo haremos se parece un poco al código de ((búsqueda de caminos)) del [Capítulo ?](robot). Mientras que ese código buscaba a través de un grafo para encontrar una ruta, este código busca a través de una cuadrícula para encontrar todos los píxeles "conectados". El problema de llevar un conjunto ramificado de rutas posibles es similar. ```{includeCode: true} -const alrededor = [{dx: -1, dy: 0}, {dx: 1, dy: 0}, +const around = [{dx: -1, dy: 0}, {dx: 1, dy: 0}, {dx: 0, dy: -1}, {dx: 0, dy: 1}]; -function rellenar({x, y}, estado, despachar) { - let colorObjetivo = estado.imagen.pixel(x, y); - let dibujados = [{x, y, color: estado.color}]; - let visitados = new Set(); - for (let hecho = 0; hecho < dibujados.length; hecho++) { - for (let {dx, dy} of alrededor) { - let x = dibujados[hecho].x + dx, y = dibujados[hecho].y + dy; - if (x >= 0 && x < estado.imagen.ancho && - y >= 0 && y < estado.imagen.alto && - !visitados.has(x + "," + y) && - estado.imagen.pixel(x, y) == colorObjetivo) { - dibujados.push({x, y, color: estado.color}); - visitados.add(x + "," + y); +function fill({x, y}, state, dispatch) { + let targetColor = state.picture.pixel(x, y); + let drawn = [{x, y, color: state.color}]; + let visited = new Set(); + for (let done = 0; done < drawn.length; done++) { + for (let {dx, dy} of around) { + let x = drawn[done].x + dx, y = drawn[done].y + dy; + if (x >= 0 && x < state.picture.width && + y >= 0 && y < state.picture.height && + !visited.has(x + "," + y) && + state.picture.pixel(x, y) == targetColor) { + drawn.push({x, y, color: state.color}); + visited.add(x + "," + y); } } } - despachar({imagen: estado.imagen.dibujar(dibujados)}); + dispatch({picture: state.picture.draw(drawn)}); } ``` @@ -464,12 +464,40 @@ El array de píxeles dibujados funciona como la ((lista de trabajo)) de la funci La última ((herramienta)) es un ((selector de color)), que te permite apuntar a un color en la imagen para usarlo como color de dibujo actual. ```{includeCode: true} -function seleccionar(pos, estado, despachar) { - despachar({color: estado.imagen.pixel(pos.x, pos.y)}); +function pick(pos, state, dispatch) { + dispatch({color: state.picture.pixel(pos.x, pos.y)}); } -```## Guardar y cargar +``` + +{{if interactive -Cuando hemos dibujado nuestra obra maestra, querríamos guardarla para más tarde. Deberíamos añadir un botón para descargar la imagen actual como un archivo de imagen. Este control proporciona ese botón: +¡Ahora podemos probar nuestra aplicación! + +```{lang: html} +<div></div> +<script> + let state = { + tool: "draw", + color: "#000000", + picture: Picture.empty(60, 30, "#f0f0f0") + }; + let app = new PixelEditor(state, { + tools: {draw, fill, rectangle, pick}, + controls: [ToolSelect, ColorSelect], + dispatch(action) { + state = updateState(state, action); + app.syncState(state); + } + }); + document.querySelector("div").appendChild(app.dom); +</script> +``` + +if}} + +## Guardar y cargar + +Una vez dibujada nuestra obra maestra, querríamos guardarla para más tarde. Deberíamos añadir un botón para descargar la imagen actual como un archivo de imagen. Este control proporciona ese botón: ```{includeCode: true} class SaveButton { @@ -578,7 +606,7 @@ La propiedad `data` del objeto devuelto por `getImageData` es un array de compon Los dos dígitos hexadecimales por componente, como se usa en nuestra notación de color, corresponden precisamente al rango del 0 al 255: dos dígitos en base 16 pueden expresar 16^2^ = 256 números diferentes. El método `toString` de los números puede recibir como argumento una base, por lo que `n.toString(16)` producirá una representación en cadena en base 16. Debemos asegurarnos de que cada número tenga dos dígitos, por lo que la función auxiliar `hex` llama a `padStart` para agregar un cero inicial cuando sea necesario. -¡Ya podemos cargar y guardar! Eso deja una característica más antes de que hayamos terminado. +¡Ya podemos cargar y guardar! Eso significa que solo nos queda una característica más antes de que hayamos terminado. ## Historial de deshacer @@ -586,11 +614,11 @@ La mitad del proceso de edición consiste en cometer pequeños errores y corregi {{index "estructura de datos persistente", [estado, "de la aplicación"]}} -Para poder deshacer cambios, necesitamos almacenar versiones anteriores de la imagen. Dado que es un valor ((inmutable)), eso es fácil. Pero sí requiere un campo adicional en el estado de la aplicación. +Para poder deshacer cambios, necesitamos almacenar versiones anteriores de la imagen. Como es un valor ((inmutable)), eso es fácil. Pero sí requiere un campo adicional en el estado de la aplicación. {{index "propiedad done"}} -Agregaremos una matriz `done` para mantener versiones anteriores de la ((imagen)). Mantener esta propiedad requiere una función de actualización de estado más complicada que añade imágenes a la matriz. +Agregaremos una matriz `done` para mantener versiones anteriores de la ((imagen)). Mantener esta propiedad requiere una función de actualización de estado más complicada que añade imágenes al array. {{index "propiedad doneAt", "función historyUpdateState", "función Date.now"}} @@ -644,11 +672,11 @@ class UndoButton { } ``` -## Vamos a dibujar +## Dibujemos {{index "clase PixelEditor", "constante startState", "constante baseTools", "constante baseControls", "función startPixelEditor"}} -Para configurar la aplicación, necesitamos crear un estado, un conjunto de ((herramienta))s, un conjunto de ((control))es y una función ((despachar)). Podemos pasarlos al constructor `PixelEditor` para crear el componente principal. Dado que necesitaremos crear varios editores en los ejercicios, primero definimos algunos enlaces. +Para configurar la aplicación, necesitamos crear un estado, un conjunto de ((herramienta))s, un conjunto de ((control))es y una función ((despachar)). Podemos pasarlos al constructor `PixelEditor` para crear el componente principal. Dado que necesitaremos crear varios editores en los ejercicios, primero definimos algunas variables. ```{includeCode: true} const startState = { @@ -682,7 +710,9 @@ function startPixelEditor({state = startState, {{index "enlaces destructurantes", "operador =", [propiedad, acceso]}} -Cuando desestructuras un objeto o un array, puedes usar `=` después de un nombre de enlace para darle al enlace un ((valor predeterminado)), que se usa cuando la propiedad está ausente o tiene `undefined`. La función `startPixelEditor` hace uso de esto para aceptar un objeto con varias propiedades opcionales como argumento. Si, por ejemplo, no proporcionas una propiedad `tools`, entonces `tools` estará vinculado a `baseTools`.Así es como obtenemos un editor real en la pantalla: +Cuando desestructuras un objeto o un array, puedes usar `=` después de un nombre de variable para darle a la variable un ((valor predeterminado)), que se usa cuando la propiedad está ausente o tiene el valor `undefined`. La función `startPixelEditor` hace uso de esto para aceptar un objeto con varias propiedades opcionales como argumento. Si, por ejemplo, no proporcionas una propiedad `tools`, entonces `tools` estará vinculado a `baseTools`. + +Así es como obtenemos un editor real en la pantalla: ```{lang: html, startCode: true} <div></div> @@ -694,7 +724,7 @@ Cuando desestructuras un objeto o un array, puedes usar `=` después de un nombr {{if interactive -Adelante y dibuja algo. +Adelante, ¡dibuja algo! if}} @@ -702,7 +732,7 @@ if}} La tecnología del navegador es asombrosa. Proporciona un poderoso conjunto de bloques de construcción de interfaz, formas de diseñar y manipularlos, y herramientas para inspeccionar y depurar tus aplicaciones. El software que escribes para el ((navegador)) puede ejecutarse en casi todas las computadoras y teléfonos del planeta. -Al mismo tiempo, la tecnología del navegador es ridícula. Tienes que aprender una gran cantidad de trucos tontos y hechos oscuros para dominarla, y el modelo de programación predeterminado que ofrece es tan problemático que la mayoría de los programadores prefieren cubrirlo con varias capas de ((abstracción)) en lugar de lidiar con él directamente. +Al mismo tiempo, la tecnología del navegador es ridícula. Tienes que aprender una gran cantidad de trucos estúpidos para dominarla, y el modelo de programación predeterminado que ofrece es tan problemático que la mayoría de los programadores prefieren cubrirlo con varias capas de ((abstracción)) en lugar de lidiar con él directamente. {{index "estándar", "evolución"}} @@ -712,7 +742,7 @@ Y aunque la situación definitivamente está mejorando, en su mayoría lo hace e La tecnología nunca existe en un vacío; estamos limitados por nuestras herramientas y los factores sociales, económicos e históricos que las produjeron. Esto puede ser molesto, pero generalmente es más productivo tratar de construir una buena comprensión de cómo funciona la realidad técnica _existente_ y por qué es como es, que luchar contra ella o esperar otra realidad. -Nuevas ((abstracciones)) _pueden_ ser útiles. El modelo de componente y la convención de flujo de ((datos)) que utilicé en este capítulo es una forma rudimentaria de eso. Como se mencionó, hay bibliotecas que intentan hacer la programación de interfaces de usuario más agradable. En el momento de escribir esto, [React](https://reactjs.org/) y [Svelte](https://svelte.dev/) son opciones populares, pero hay toda una industria de tales marcos. Si estás interesado en programar aplicaciones web, recomiendo investigar algunos de ellos para comprender cómo funcionan y qué beneficios proporcionan. +Nuevas ((abstracciones)) _pueden_ resultar útiles. El modelo de componente y la convención de flujo de ((datos)) que utilicé en este capítulo es una forma rudimentaria de eso. Como se mencionó, hay bibliotecas que intentan hacer la programación de interfaces de usuario más agradable. En el momento de escribir esto, [React](https://reactjs.org/) y [Svelte](https://svelte.dev/) son opciones populares, pero hay toda una industria de tales _frameworks_. Si estás interesado en programar aplicaciones web, recomiendo investigar algunos de ellos para comprender cómo funcionan y qué beneficios proporcionan. ## Ejercicios @@ -782,7 +812,7 @@ Un controlador `"keydown"` puede inspeccionar su objeto de evento para ver si co {{index "método preventDefault"}} -Cuando el evento de tecla coincide con un atajo, llama a `preventDefault` en él y ((dispatch)) la acción apropiada. +Cuando el evento de tecla coincide con un atajo, llama a `preventDefault` en él y haz ((dispatch)) con la acción apropiada. hint}} @@ -881,7 +911,7 @@ if}} {{index "círculos (ejercicio)", "función rectángulo"}} -Puedes inspirarte en la herramienta `rectangle`. Como esa herramienta, querrás seguir dibujando en la imagen _inicial_, en lugar de la imagen actual, cuando el puntero se mueva. +Puedes inspirarte en la herramienta `rectangle`. Como con esa herramienta, tendrás seguir dibujando en la imagen _inicial_, en lugar de la imagen actual, cuando el puntero se mueva. Para averiguar qué píxeles colorear, puedes usar el ((teorema de Pitágoras)). Primero averigua la distancia entre la posición actual del puntero y la posición de inicio tomando la raíz cuadrada (`Math.sqrt`) de la suma del cuadrado (`x ** 2`) de la diferencia en las coordenadas x y el cuadrado de la diferencia en las coordenadas y. Luego, recorre una cuadrícula de píxeles alrededor de la posición de inicio, cuyos lados tienen al menos el doble del ((radio)), y colorea aquellos que estén dentro del radio del círculo, nuevamente usando la fórmula pitagórica para averiguar la ((distancia)) desde el centro. @@ -907,7 +937,7 @@ Una línea entre dos píxeles es una cadena conectada de píxeles, lo más recta {{figure {url: "img/line-grid.svg", alt: "Diagrama de dos líneas pixeladas, una clara, saltando píxeles diagonalmente, y otra más gruesa, con todos los píxeles conectados horizontal o verticalmente", width: "6cm"}}} -Finalmente, si tenemos código que dibuja una línea entre dos puntos arbitrarios, podríamos usarlo también para definir una `herramienta` de `línea`, que dibuja una línea recta entre el inicio y el final de un arrastre. +Finalmente, si tenemos código que dibuja una línea entre dos puntos arbitrarios, podríamos usar eso también para definir una `herramienta` de `línea`, que dibuja una línea recta entre el inicio y el final de un arrastre. {{if interactive @@ -947,7 +977,7 @@ Pero tan pronto como tu pendiente cruce los 45 grados, necesitas cambiar la form No necesitas realmente escribir cuatro bucles. Dado que dibujar una línea de _A_ a _B_ es lo mismo que dibujar una línea de _B_ a _A_, puedes intercambiar las posiciones de inicio y fin para las líneas que van de derecha a izquierda y tratarlas como si fueran de izquierda a derecha. -Por lo tanto, necesitas dos bucles diferentes. Lo primero que debería hacer tu función de dibujo de líneas es verificar si la diferencia entre las coordenadas x es mayor que la diferencia entre las coordenadas y. Si lo es, esta es una línea horizontal-ish y, si no, una línea vertical-ish. +Por lo tanto, necesitas dos bucles diferentes. Lo primero que debería hacer tu función de dibujo de líneas es verificar si la diferencia entre las coordenadas x es mayor que la diferencia entre las coordenadas y. Si lo es, entonces se trata de una línea más bien tirando a horizontal y, si no, de una línea más bien tirando a vertical. {{index "Math.abs function", "valor absoluto"}} diff --git a/20_node.md b/20_node.md index 621e39c3..9716348b 100644 --- a/20_node.md +++ b/20_node.md @@ -2,25 +2,25 @@ # Node.js -{{quote {author: "Maestro Yuan-Ma", title: "El Libro de la Programación", chapter: true} +{{quote {author: "Master Yuan-Ma", title: "The Book of Programming", chapter: true} -Un estudiante preguntó: "Los programadores de antaño solo usaban máquinas simples y ningún lenguaje de programación, sin embargo, creaban programas hermosos. ¿Por qué nosotros usamos máquinas complicadas y lenguajes de programación?". Fu-Tzu respondió: "Los constructores de antaño solo usaban palos y arcilla, sin embargo, creaban hermosas chozas." +Un estudiante preguntó: "Los programadores de antaño solo usaban máquinas simples y ningún lenguaje de programación, sin embargo, creaban programas hermosos. ¿Por qué nosotros usamos máquinas complicadas y lenguajes de programación?" Fu-Tzu respondió: "Los constructores de antaño solo usaban palos y arcilla y, sin embargo, creaban hermosas chozas." quote}} -{{index "Yuan-Ma", "Libro de la Programación"}} +{{index "Yuan-Ma", "Book of Programming"}} {{figure {url: "img/chapter_picture_20.jpg", alt: "Ilustración que muestra un poste telefónico con un enredo de cables en todas direcciones", chapter: "framed"}}} {{index "línea de comandos"}} -Hasta ahora, hemos utilizado el lenguaje JavaScript en un solo entorno: el navegador. Este capítulo y el [siguiente](skillsharing) introducirán brevemente ((Node.js)), un programa que te permite aplicar tus habilidades con JavaScript fuera del navegador. Con él, puedes construir desde pequeñas herramientas de línea de comandos hasta servidores HTTP ((server)) que alimentan ((sitios web)) dinámicos. +Hasta ahora, hemos utilizado el lenguaje JavaScript en un solo entorno: el navegador. Este capítulo y el [siguiente](skillsharing) introducirán brevemente ((Node.js)), un programa que te permite aplicar tus habilidades con JavaScript fuera del navegador. Con él, puedes construir desde pequeñas herramientas de línea de comandos hasta ((servidor))es HTTP para ((sitios web)) dinámicos. Estos capítulos tienen como objetivo enseñarte los conceptos principales que Node.js utiliza y darte información suficiente para escribir programas útiles para él. No intentan ser un tratamiento completo, ni siquiera exhaustivo, de la plataforma. {{if interactive -Mientras que podrías ejecutar el código en los capítulos anteriores directamente en estas páginas, ya sea JavaScript puro o escrito para el navegador, los ejemplos de código en este capítulo están escritos para Node y a menudo no se ejecutarán en el navegador. +Si bien podrías ejecutar el código en los capítulos anteriores directamente en estas páginas (por tratarse de JavaScript puro o escrito para el navegador), los ejemplos de código en este capítulo están escritos para Node y a menudo no se ejecutarán en el navegador. if}} @@ -30,15 +30,15 @@ Si deseas seguir y ejecutar el código en este capítulo, necesitarás instalar {{index responsividad, entrada, [red, velocidad]}} -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. +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 @@ -64,7 +64,7 @@ El método `console.log` en Node hace algo similar a lo que hace en el navegador {{index "programa node", "bucle de lectura-evaluación-impresión"}} -Si ejecutas `node` sin proporcionarle un archivo, te proporcionará un indicador en el que puedes escribir código JavaScript y ver inmediatamente el resultado. +Si ejecutas `node` sin proporcionarle un archivo, te proporcionará un indicador de línea de comandos (o _prompt_) en el que puedes escribir código JavaScript y ver inmediatamente el resultado. ```{lang: null} $ node @@ -78,7 +78,7 @@ $ {{index "objeto process", "ámbito global", [binding, global], "método exit", "código de estado"}} -El enlace `process`, al igual que el enlace `console`, está disponible globalmente en Node. Proporciona varias formas de inspeccionar y manipular el programa actual. El método `exit` finaliza el proceso y puede recibir un código de estado de salida, que le indica al programa que inició `node` (en este caso, la shell de línea de comandos) si el programa se completó correctamente (código cero) o si se encontró un error (cualquier otro código). +La asociación `process`, al igual que la asociación `console`, está disponible globalmente en Node. Proporciona varias formas de inspeccionar y manipular el programa actual. El método `exit` finaliza el proceso y puede recibir un código de estado de salida, que le indica al programa que inició `node` (en este caso, la shell de línea de comandos) si el programa se completó correctamente (código cero) o si se encontró un error (cualquier otro código). {{index "línea de comandos", "propiedad argv"}} @@ -91,38 +91,39 @@ $ node showargv.js one --and two {{index [binding, global]}} -Todos los enlaces globales de JavaScript estándar, como `Array`, `Math` y `JSON`, también están presentes en el entorno de Node. La funcionalidad relacionada con el navegador, como `document` o `prompt`, no lo está. +Todas las asociaciones globales de JavaScript estándar, como `Array`, `Math` y `JSON`, también están presentes en el entorno de Node. Las funcionalidades relacionadas con el navegador, como `document` o `prompt`, no lo están. ## Módulos {{index "Node.js", "ámbito global", "cargador de módulos"}} -Además de los enlaces que mencioné, como `console` y `process`, Node agrega pocos enlaces adicionales en el ámbito global. Si deseas acceder a funcionalidades integradas, debes solicitarlas al sistema de módulos. +Además de las asociaciones que mencioné, como `console` y `process`, Node agrega pocas asociaciones adicionales en el ámbito global. Si deseas acceder a funcionalidades integradas, debes solicitarlas al sistema de módulos. {{index "función require"}} -Node comenzó utilizando el sistema de módulos CommonJS, basado en la función `require`, que vimos en el [Capítulo ?](modules#commonjs). Aún utilizará este sistema de forma predeterminada cuando cargues un archivo `.js`. +Node comenzó utilizando el sistema de módulos CommonJS, basado en la función `require`, que vimos en el [Capítulo ?](modules#commonjs). Aún utilizará este sistema de forma predeterminada cuando cargas un archivo `.js`. {{index "palabra clave import", "módulos ES"}} -Pero también soporta el sistema de módulos ES más moderno. Cuando el nombre de un script termina en `.mjs`, se considera que es un módulo de este tipo, y puedes usar `import` y `export` en él (pero no `require`). Utilizaremos módulos ES en este capítulo. +Pero también soporta el sistema de módulos ES más moderno. Cuando el nombre de un script termina en `.mjs`, se considera que es un módulo de este tipo, y puedes usar `import` y `export` en él (en lugar de `require`). Utilizaremos módulos ES en este capítulo. {{index [path, "sistema de archivos"], "ruta relativa", "resolución"}} -Cuando se importa un módulo, ya sea con `require` o `import`, Node debe resolver la cadena proporcionada a un ((archivo)) real que pueda cargar. Los nombres que comienzan con `/`, `./` o `../` se resuelven como archivos, relativos a la ruta del módulo actual. Aquí, `.` representa el directorio actual, `../` para un directorio arriba, y `/` para la raíz del sistema de archivos. Por lo tanto, si solicitas `"./graph.mjs"` desde el archivo `/tmp/robot/robot.mjs`, Node intentará cargar el archivo `/tmp/robot/graph.mjs`. +Cuando se importa un módulo, ya sea con `require` o `import`, Node debe resolver la cadena proporcionada a un ((archivo)) real que pueda cargar. Los nombres que comienzan con `/`, `./` o `../` se resuelven como archivos, relativos a la ruta del módulo actual. Aquí, `.` representa el directorio actual, `../` para un directorio arriba, y `/` se usa para la raíz del sistema de archivos. Por lo tanto, si solicitas `"./graph.mjs"` desde el archivo `/tmp/robot/robot.mjs`, Node intentará cargar el archivo `/tmp/robot/graph.mjs`. {{index "directorio node_modules", directorio}} -Cuando se importa una cadena que no parece una ruta relativa o absoluta, se asume que se refiere a un módulo integrado o un módulo instalado en un directorio `node_modules`. Por ejemplo, importar desde `"node:fs"` te dará el módulo integrado del sistema de archivos de Node. E importar `"robot"` podría intentar cargar la biblioteca encontrada en `node_modules/robot/`. Una forma común de instalar estas bibliotecas es usando ((NPM)), a lo cual volveremos en un momento. +Cuando se importa una cadena que no parece una ruta relativa o absoluta, se asume que se refiere a un módulo integrado o un módulo instalado en un directorio `node_modules`. Por ejemplo, importar desde `"node:fs"` te dará el módulo integrado de sistema de archivos de Node. Importar `"robot"` podría intentar cargar la biblioteca encontrada en `node_modules/robot/`. Una forma común de instalar estas bibliotecas es usando ((NPM)), como volveremos en un momento. {{index "palabra clave import", "Node.js", "ejemplo confuso"}} Configuremos un proyecto pequeño que consta de dos archivos. El primero, llamado `main.mjs`, define un script que puede ser llamado desde la ((línea de comandos)) para revertir una cadena. -``` +```{lang:null} import {reverse} from "./reverse.mjs"; -// El índice 2 contiene el primer argumento real de la línea de comandos +// El índice 2 contiene el primer +// argumento real de la línea de comandos let argument = process.argv[2]; console.log(reverse(argument)); @@ -132,7 +133,7 @@ console.log(reverse(argument)); El archivo `reverse.mjs` define una biblioteca para revertir cadenas, que puede ser utilizada tanto por esta herramienta de línea de comandos como por otros scripts que necesiten acceso directo a una función para revertir cadenas. -``` +```{lang:null} export function reverse(string) { return Array.from(string).reverse().join(""); } @@ -140,7 +141,7 @@ export function reverse(string) { {{index "palabra clave export", "módulos ES", [interfaz, "módulo"]}} -Recuerda que `export` se utiliza para declarar que un enlace es parte de la interfaz del módulo. Eso permite que `main.mjs` importe y utilice la función. +Recuerda que `export` se utiliza para declarar que una asociación es parte de la interfaz del módulo. Eso permite que `main.mjs` importe y utilice la función. Ahora podemos llamar a nuestra herramienta de esta manera: @@ -157,7 +158,7 @@ NPM, que fue introducido en el [Capítulo ?](modules#modules_npm), es un reposit {{index "paquete ini"}} -El uso principal de NPM es ((descargar)) paquetes. Vimos el paquete `ini` en el [Capítulo ?](modules#modules_ini). Podemos usar NPM para buscar e instalar ese paquete en nuestra computadora. +El uso principal de NPM es ((descargar)) paquetes. Vimos el paquete `ini` en el [Capítulo ?](modules#modulos_ini). Podemos usar NPM para buscar e instalar ese paquete en nuestra computadora. ```{lang: null} $ npm install ini @@ -171,7 +172,9 @@ $ node {{index "función require", "directorio node_modules", "programa npm"}} -Después de ejecutar `npm install`, ((NPM)) habrá creado un directorio llamado `node_modules`. Dentro de ese directorio estará un directorio `ini` que contiene la ((biblioteca)). Puedes abrirlo y ver el código. Cuando importamos `"ini"`, esta biblioteca se carga, y podemos llamar a su propiedad `parse` para analizar un archivo de configuración.Por defecto, NPM instala paquetes en el directorio actual, en lugar de en un lugar centralizado. Si estás acostumbrado a otros gestores de paquetes, esto puede parecer inusual, pero tiene ventajas: pone a cada aplicación en control total de los paquetes que instala y facilita la gestión de versiones y limpieza al eliminar una aplicación. +Después de ejecutar `npm install`, ((NPM)) habrá creado un directorio llamado `node_modules`. Dentro de ese directorio estará un directorio `ini` que contiene la ((biblioteca)). Puedes abrirlo y ver el código. Cuando importamos `"ini"`, esta biblioteca se carga, y podemos llamar a su propiedad `parse` para analizar un archivo de configuración. + +Por defecto, NPM instala paquetes en el directorio actual, en lugar de en un lugar centralizado. Si estás acostumbrado a otros gestores de paquetes, esto puede parecer inusual, pero tiene ventajas: pone a cada aplicación en control total de los paquetes que instala y facilita la gestión de versiones y limpieza al eliminar una aplicación. ### Archivos de paquete @@ -179,7 +182,7 @@ Después de ejecutar `npm install`, ((NPM)) habrá creado un directorio llamado Después de ejecutar `npm install` para instalar algún paquete, encontrarás no solo un directorio `node_modules`, sino también un archivo llamado `package.json` en tu directorio actual. Se recomienda tener tal archivo para cada proyecto. Puedes crearlo manualmente o ejecutar `npm init`. Este archivo contiene información sobre el proyecto, como su nombre y ((versión)), y enumera sus dependencias. -La simulación del robot de [Capítulo ?](robot), modularizada en el ejercicio en el [Capítulo ?](modules#modular_robot), podría tener un archivo `package.json` como este: +La simulación del robot de [Capítulo ?](robot), modularizada en el ejercicio del [Capítulo ?](modules#modular_robot), podría tener un archivo `package.json` como este: ```{lang: "json"} { @@ -212,23 +215,25 @@ NPM exige que sus paquetes sigan un esquema llamado _((semantic versioning))_, q {{index "caret character"}} -Un carácter de intercalación (`^`) delante del número de versión para una dependencia en `package.json` indica que se puede instalar cualquier versión compatible con el número dado. Por ejemplo, `"^2.3.0"` significaría que se permite cualquier versión mayor o igual a 2.3.0 y menor que 3.0.0. +Un acento circunflejo (`^`) delante del número de versión para una dependencia en `package.json` indica que se puede instalar cualquier versión compatible con el número dado. Por ejemplo, `"^2.3.0"` significaría que se permite cualquier versión mayor o igual a 2.3.0 y menor que 3.0.0. {{index publishing}} -El comando `npm` también se utiliza para publicar nuevos paquetes o nuevas versiones de paquetes. Si ejecutas `npm publish` en un ((directorio)) que tiene un archivo `package.json`, se publicará un paquete con el nombre y versión listados en el archivo JSON en el registro. Cualquiera puede publicar paquetes en NPM, aunque solo bajo un nombre de paquete que aún no esté en uso, ya que no sería bueno que personas aleatorias pudieran actualizar paquetes existentes.Este libro no profundizará más en los detalles del uso de ((NPM)). Consulta [_https://npmjs.org_](https://npmjs.org) para obtener más documentación y una forma de buscar paquetes. +El comando `npm` también se utiliza para publicar nuevos paquetes o nuevas versiones de paquetes. Si ejecutas `npm publish` en un ((directorio)) que tiene un archivo `package.json`, se publicará en el registro un paquete con el nombre y versión listados en el archivo JSON. Cualquiera puede publicar paquetes en NPM, aunque solo bajo un nombre de paquete que aún no esté en uso, ya que no sería bueno que personas aleatorias pudieran actualizar paquetes existentes. + +Este libro no profundizará más en los detalles del uso de ((NPM)). Consulta [_https://npmjs.org_](https://npmjs.org) para obtener más documentación y una forma de buscar paquetes. ## El módulo del sistema de archivos {{index directorio, "node:fs", "Node.js", [archivo, acceso]}} -Uno de los módulos integrados más utilizados en Node es el módulo `node:fs`, que significa _((sistema de archivos))_. Exporta funciones para trabajar con archivos y directorios. +Uno de los módulos integrados más utilizados en Node es el módulo `node:fs`, que significa _((sistema de archivos))_ (en inglés, _file system_). Exporta funciones para trabajar con archivos y directorios. -{{index "función" "readFile", "función de devolución de llamada"}} +{{index "función" "readFile", "función de callback"}} -Por ejemplo, la función llamada `readFile` lee un archivo y luego llama a una función de devolución de llamada con el contenido del archivo. +Por ejemplo, la función llamada `readFile` lee un archivo y luego llama a una función de callback con el contenido del archivo. -``` +```{lang:null} import {readFile} from "node:fs"; readFile("archivo.txt", "utf8", (error, texto) => { if (error) throw error; @@ -240,7 +245,7 @@ readFile("archivo.txt", "utf8", (error, texto) => { El segundo argumento de `readFile` indica la _((codificación de caracteres))_ utilizada para decodificar el archivo en una cadena. Existen varias formas en las que el ((texto)) puede ser codificado en ((datos binarios)), pero la mayoría de los sistemas modernos utilizan ((UTF-8)). Entonces, a menos que tengas razones para creer que se utiliza otra codificación, pasa `"utf8"` al leer un archivo de texto. Si no pasas una codificación, Node asumirá que estás interesado en los datos binarios y te dará un objeto `Buffer` en lugar de una cadena. Este es un ((objeto similar a un array)) que contiene números que representan los bytes (trozos de datos de 8 bits) en los archivos. -``` +```{lang:null} import {readFile} from "node:fs"; readFile("archivo.txt", (error, buffer) => { if (error) throw error; @@ -253,7 +258,7 @@ readFile("archivo.txt", (error, buffer) => { Una función similar, `writeFile`, se utiliza para escribir un archivo en el disco. -``` +```{lang:null} import {writeFile} from "node:fs"; writeFile("graffiti.txt", "Node estuvo aquí", err => { if (err) console.log(`Error al escribir el archivo: ${err}`); @@ -267,27 +272,27 @@ Aquí no fue necesario especificar la codificación: `writeFile` asumirá que cu {{index "node:fs", "función" "readdir", "función" "stat", "función" "rename", "función" "unlink"}} -El módulo `node:fs` contiene muchas otras funciones útiles: `readdir` te dará los archivos en un ((directorio)) como un array de cadenas, `stat` recuperará información sobre un archivo, `rename` cambiará el nombre de un archivo, `unlink` lo eliminará, entre otros. Consulta la documentación en [_https://nodejs.org_](https://nodejs.org) para obtener detalles específicos. +El módulo `node:fs` contiene muchas otras funciones útiles: `readdir` te dará los archivos en un ((directorio)) como un array de cadenas, `stat` recuperará información sobre un archivo, `rename` cambiará el nombre de un archivo, `unlink` lo eliminará, etc. Consulta la documentación en [_https://nodejs.org_](https://nodejs.org) para obtener detalles específicos. {{index ["programación asíncrona", "en Node.js"], "Node.js", "manejo de errores", "función de devolución de llamada"}} -La mayoría de estas funciones toman una función de devolución de llamada como último parámetro, a la que llaman ya sea con un error (el primer argumento) o con un resultado exitoso (el segundo). Como vimos en el [Capítulo ?](async), hay desventajas en este estilo de programación, siendo la mayor que el manejo de errores se vuelve verboso y propenso a errores. +La mayoría de estas funciones toman una función de callback como último parámetro, a la que llaman ya sea con un error (el primer argumento) o con un resultado exitoso (el segundo). Como vimos en el [Capítulo ?](async), hay desventajas en este estilo de programación, siendo la mayor que el manejo de errores se vuelve verboso y propenso a errores. {{index "Clase Promise", "paquete node:fs/promises"}} -El módulo `node:fs/promises` exporta la mayoría de las mismas funciones que el antiguo módulo `node:fs`, pero utiliza promesas en lugar de funciones de devolución de llamada. +El módulo `node:fs/promises` exporta la mayoría de las mismas funciones que el antiguo módulo `node:fs`, pero utiliza promesas en lugar de funciones de callback. -``` +```{lang:null} import {readFile} from "node:fs/promises"; readFile("file.txt", "utf8") .then(text => console.log("El archivo contiene:", text)); ``` -{{index "programación síncrona", "paquete node:fs", "función readFileSync"}} +{{index "programación sincrónica", "paquete node:fs", "función readFileSync"}} -A veces no necesitas asincronía y simplemente te estorba. Muchas de las funciones en `node:fs` también tienen una variante síncrona, que tiene el mismo nombre con `Sync` agregado al final. Por ejemplo, la versión síncrona de `readFile` se llama `readFileSync`. +A veces no necesitas asincronía y simplemente te estorba. Muchas de las funciones en `node:fs` también tienen una variante sincrónica, que tiene el mismo nombre con `Sync` agregado al final. Por ejemplo, la versión sincrónica de `readFile` se llama `readFileSync`. -``` +```{lang:null} import {readFileSync} from "node:fs"; console.log("El archivo contiene:", readFileSync("file.txt", "utf8")); @@ -295,7 +300,7 @@ console.log("El archivo contiene:", {{index "optimización", rendimiento, bloqueo}} -Cabe destacar que mientras se realiza una operación síncrona de este tipo, tu programa se detiene por completo. Si debería estar respondiendo al usuario o a otras máquinas en la red, quedarse atrapado en una acción síncrona podría producir retrasos molestos. +Cabe destacar que mientras se realiza una operación sincrónica de este tipo, tu programa se detiene por completo. Si debería estar respondiendo al usuario o a otras máquinas en la red, quedarse atrapado en una acción sincrónica podría producir retrasos molestos. ## El módulo HTTP @@ -307,13 +312,13 @@ Otro módulo central se llama `node:http`. Proporciona funcionalidad para ejecut Esto es todo lo que se necesita para iniciar un servidor HTTP: -``` +```{lang:null} import {createServer} from "node:http"; let server = createServer((solicitud, respuesta) => { respuesta.writeHead(200, {"Content-Type": "text/html"}); respuesta.write(` <h1>¡Hola!</h1> - <p>Pediste <code>${solicitud.url}</code></p>`); + <p>Has pedido <code>${solicitud.url}</code></p>`); respuesta.end(); }); server.listen(8000); @@ -322,7 +327,7 @@ console.log("¡Escuchando! (puerto 8000)"); {{index puerto, localhost}} -Si ejecutas este script en tu propia máquina, puedes apuntar tu navegador web a [_http://localhost:8000/hola_](http://localhost:8000/hola) para hacer una solicitud a tu servidor. Responderá con una pequeña página HTML. +Si ejecutas este script en tu propia máquina, puedes apuntar tu navegador web a [_http://localhost:8000/hello](http://localhost:8000/hello) para hacer una solicitud a tu servidor. Responderá con una pequeña página HTML. {{index "función createServer", HTTP}} @@ -332,11 +337,11 @@ Así que, cuando abres esa página en tu navegador, envía una solicitud a tu pr {{index "200 (código de estado HTTP)", "encabezado Content-Type", "método writeHead"}} -Para enviar algo al cliente, llamas a métodos en el objeto `respuesta`. El primero, `writeHead`, escribirá los ((encabezados de respuesta)) (ver [Capítulo ?](http#encabezados)). Le das el código de estado (200 para "OK" en este caso) y un objeto que contiene valores de encabezado. El ejemplo establece el encabezado `Content-Type` para informar al cliente que estaremos enviando de vuelta un documento HTML. +Para enviar algo al cliente, llamas a métodos en el objeto `respuesta`. El primero, `writeHead`, escribirá los ((encabezados de respuesta)) (ver [Capítulo ?](http#headers)). Le das el código de estado (200 para "OK" en este caso) y un objeto que contiene valores de encabezado. El ejemplo establece el encabezado `Content-Type` para informar al cliente que estaremos enviando de vuelta un documento HTML. {{index "flujo de escritura", "cuerpo (HTTP)", stream, "método write", "método end"}} -A continuación, el cuerpo real de la respuesta (el documento en sí) se envía con `response.write`. Se permite llamar a este método varias veces si deseas enviar la respuesta pieza por pieza, por ejemplo para transmitir datos al cliente a medida que estén disponibles. Por último, `response.end` señala el fin de la respuesta. +A continuación, el cuerpo de la respuesta (el documento en sí) se envía con `response.write`. Se permite llamar a este método varias veces si deseas enviar la respuesta pieza por pieza, por ejemplo para transmitir datos al cliente a medida que estén disponibles. Por último, `response.end` señala el fin de la respuesta. {{index "método listen"}} @@ -348,17 +353,17 @@ Cuando ejecutas este script, el proceso se queda esperando. Cuando un script est {{index ["método", HTTP]}} -Un verdadero servidor web ((server)) usualmente hace más cosas que el ejemplo; examina el ((método)) de la solicitud (la propiedad `method`) para ver qué acción está intentando realizar el cliente y mira el ((URL)) de la solicitud para descubrir sobre qué recurso se está realizando esta acción. Veremos un servidor más avanzado [más adelante en este capítulo](node#file_server). +Un verdadero servidor web ((server)) usualmente hace más cosas que el del ejemplo; examina el ((método)) de la solicitud (la propiedad `method`) para ver qué acción está intentando realizar el cliente y mira la ((URL)) de la solicitud para descubrir sobre qué recurso se está realizando esta acción. Veremos un servidor más avanzado [más adelante en este capítulo](node#file_server). {{index "node:http package", "función request", "función fetch", [HTTP, cliente]}} -El módulo `node:http` también provee una función `request`, que se puede usar para hacer solicitudes HTTP. Sin embargo, es mucho más engorroso de usar que `fetch`, que vimos en el [Capítulo ?](http). Afortunadamente, `fetch` también está disponible en Node, como un enlace global. A menos que desees hacer algo muy específico, como procesar el documento de respuesta pieza por pieza a medida que llegan los datos a través de la red, recomiendo usar `fetch`. +El módulo `node:http` también provee una función `request`, que se puede usar para hacer solicitudes HTTP. Sin embargo, es mucho más engorroso de usar que `fetch`, que vimos en el [Capítulo ?](http). Afortunadamente, `fetch` también está disponible en Node, como una asociación global. A menos que desees hacer algo muy específico, como procesar el documento de respuesta pieza por pieza a medida que llegan los datos a través de la red, recomiendo usar `fetch`. ## 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. +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 función de callback como argumento adicional, que se llamará cuando la escritura o el cierre hayan finalizado. {{index "función createWriteStream", "función writeFile", [archivo, flujo]}} @@ -366,7 +371,7 @@ Es posible crear un flujo de escritura que apunte a un archivo con la función ` {{index "createServer function", "función request", "manejo de eventos", "flujo legible"}} -Los _flujos legibles_ son un poco más complejos. El argumento `request` para la devolución de llamada del servidor HTTP es un flujo legible. Leer de un flujo se hace utilizando manejadores de eventos, en lugar de métodos. +Los _flujos legibles_ son un poco más complejos. El argumento `request` para la función de callback del servidor HTTP es un flujo legible. Leer de un flujo se hace utilizando manejadores de eventos, en lugar de métodos. {{index "método on", "método addEventListener"}} @@ -374,13 +379,13 @@ Los objetos que emiten eventos en Node tienen un método llamado `on` que es sim {{index "función createReadStream", "evento data", "evento end", "flujo legible"}} -Los ((stream))s legibles tienen eventos `"data"` y `"end"`. El primero se dispara cada vez que llegan datos, y el segundo se llama cuando el flujo llega a su fin. Este modelo es más adecuado para datos de _streaming_ que pueden procesarse de inmediato, incluso cuando todo el documento aún no está disponible. Un archivo se puede leer como un flujo legible utilizando la función `createReadStream` de `node:fs`. +Los ((flujo))s legibles tienen eventos `"data"` y `"end"`. El primero se dispara cada vez que llegan datos, y el segundo se llama cuando el flujo llega a su fin. Este modelo es más adecuado para _flujos_ de datos que pueden procesarse de inmediato, incluso cuando todo el documento aún no está disponible. Un archivo se puede leer como un flujo legible utilizando la función `createReadStream` de `node:fs`. {{index "ejemplo de servidor de mayúsculas", "capitalización", "método toUpperCase"}} Este código crea un ((servidor)) que lee los cuerpos de las solicitudes y los reenvía al cliente como texto en mayúsculas: -``` +```{lang:null} import {createServer} from "node:http"; createServer((solicitud, respuesta) => { respuesta.writeHead(200, {"Content-Type": "text/plain"}); @@ -392,11 +397,11 @@ createServer((solicitud, respuesta) => { {{index "clase Buffer", "método toString"}} -El valor `chunk` pasado al manejador de datos será un `Buffer` binario. Podemos convertir esto a una cadena decodificándolo como caracteres codificados en UTF-8 con su método `toString`. +El valor `fragmento` pasado al manejador de datos será un `Buffer` binario. Podemos convertir esto a una cadena decodificándolo como caracteres codificados en UTF-8 con su método `toString`. El siguiente fragmento de código, cuando se ejecuta con el servidor de mayúsculas activo, enviará una solicitud a ese servidor y escribirá la respuesta que recibe: -``` +```{lang:null} fetch("http://localhost:8000/", { method: "POST", body: "Hola servidor" @@ -418,13 +423,13 @@ Cuando tratamos los archivos como ((recursos)) de HTTP, los métodos HTTP `GET`, {{index [ruta, "sistema de archivos"], "ruta relativa"}} -Probablemente no queramos compartir todo nuestro sistema de archivos, por lo que interpretaremos estas rutas como comenzando en el directorio de trabajo del servidor, que es el directorio en el que se inició. Si ejecuté el servidor desde `/tmp/public/` (o `C:\tmp\public\` en Windows), entonces una solicitud para `/file.txt` debería referirse a `/tmp/public/file.txt` (o `C:\tmp\public\file.txt`). +Probablemente no queramos compartir todo nuestro sistema de archivos, por lo que interpretaremos estas rutas como comenzando en el directorio de trabajo del servidor, que es el directorio en el que se inició. Si ejecuté el servidor desde `/tmp/public/` (o `C:\tmp\public\` en Windows), entonces una solicitud para `/file.txt` debería referirse a `/tmp/public/file.txt` (o `C:\tmp\public\file.txt`, respectivamente). {{index "ejemplo de servidor de archivos", "Node.js", "objeto methods", "clase Promise"}} Construiremos el programa paso a paso, utilizando un objeto llamado `methods` para almacenar las funciones que manejan los diferentes métodos HTTP. Los manejadores de métodos son funciones `async` que reciben el objeto de solicitud como argumento y devuelven una promesa que se resuelve a un objeto que describe la respuesta. -```{includeCode: ">code/file_server.mjs"} +```{includeCode: ">code/file_server.mjs", lang:null} import {createServer} from "node:http"; const methods = Object.create(null); @@ -463,13 +468,13 @@ El campo `status` de la descripción de la respuesta puede omitirse, en cuyo cas {{index "método end", "método pipe", flujo}} -Cuando el valor de `body` es un ((readable stream)), este tendrá un método `pipe` que se utiliza para reenviar todo el contenido de un flujo de lectura a un ((writable stream)). Si no es así, se asume que es `null` (sin cuerpo), una cadena o un búfer, y se pasa directamente al método `end` del ((response)). +Cuando el valor de `body` es un ((readable stream)), este tendrá un método `pipe` que se utiliza para reenviar todo el contenido de un flujo de lectura a un ((flujo de escritura)). Si no es así, se asume que es `null` (sin cuerpo), una cadena o un búfer, y se pasa directamente al método `end` del ((response)). {{index [ruta, URL], "función urlPath", "clase URL", "análisis", [escape, "en las URL"], "función decodeURIComponent", "método startsWith"}} Para determinar qué ruta de archivo corresponde a una URL de solicitud, la función `urlPath` utiliza la clase integrada `URL` (que también existe en el navegador) para analizar la URL. Este constructor espera una URL completa, no solo la parte que comienza con la barra diagonal que obtenemos de `request.url`, por lo que le proporcionamos un nombre de dominio falso para completar. Extrae su ruta, que será algo como `"/archivo.txt"`, la decodifica para eliminar los códigos de escape estilo `%20`, y la resuelve en relación con el directorio de trabajo del programa. -```{includeCode: ">code/file_server.mjs"} +```{includeCode: ">code/file_server.mjs", lang:null} import {parse} from "node:url"; import {resolve, sep} from "node:path"; @@ -496,11 +501,11 @@ Para evitar tales problemas, `urlPath` utiliza la función `resolve` del módulo {{index "ejemplo de servidor de archivos", "Node.js", "método GET", [archivo, recurso]}} -Configuraremos el método `GET` para devolver una lista de archivos al leer un ((directorio)) y para devolver el contenido del archivo al leer un archivo regular. +Configuraremos el método `GET` para devolver una lista de archivos al leer un ((directorio)) y para devolver el contenido del archivo al leer un archivo normal. {{index "tipo de medio", "encabezado Content-Type", "paquete mime-types"}} -Una pregunta complicada es qué tipo de encabezado `Content-Type` debemos establecer al devolver el contenido de un archivo. Dado que estos archivos podrían ser cualquier cosa, nuestro servidor no puede simplemente devolver el mismo tipo de contenido para todos ellos. ((npm)) puede ayudarnos nuevamente aquí. El paquete `mime-types` (los indicadores de tipo de contenido como `text/plain` también se llaman _((tipos MIME))_) conoce el tipo correcto para una gran cantidad de ((extensiones de archivo)). +Una pregunta complicada es qué tipo de encabezado `Content-Type` debemos establecer al devolver el contenido de un archivo. Dado que estos archivos podrían ser cualquier cosa, nuestro servidor no puede simplemente devolver el mismo tipo de contenido para todos ellos. El gestor ((npm)) puede ayudarnos nuevamente aquí. El paquete `mime-types` (los indicadores de tipo de contenido como `text/plain` también se llaman _((tipos MIME))_) conoce el tipo correcto para una gran cantidad de ((extensiones de archivo)). {{index "programa npm"}} @@ -514,7 +519,7 @@ $ npm install mime-types@2.1.0 Cuando un archivo solicitado no existe, el código de estado HTTP correcto a devolver es 404. Utilizaremos la función `stat`, que busca información sobre un archivo, para averiguar tanto si el archivo existe como si es un ((directorio)). -```{includeCode: ">code/file_server.mjs"} +```{includeCode: ">code/file_server.mjs", lang:null} import {createReadStream} from "node:fs"; import {stat, readdir} from "node:fs/promises"; import {lookup} from "mime-types"; @@ -539,23 +544,23 @@ methods.GET = async function(request) { {{index "función createReadStream", ["programación asíncrona", "en Node.js"], "manejo de errores","ENOENT (código de estado)","Tipo de error","herencia"}} -Debido a que debe acceder al disco y por lo tanto podría llevar algún tiempo, `stat` es asíncrono. Dado que estamos utilizando promesas en lugar del estilo de devolución de llamada, debe ser importado desde `node:fs/promises` en lugar de directamente desde `node:fs`. +Como debe acceder al disco, lo que podría llevar algún tiempo, `stat` es asíncrono. Dado que estamos utilizando promesas en lugar del estilo de funcinoes de callback, hay que importar desde `node:fs/promises` en lugar de directamente desde `node:fs`. -Cuando el archivo no existe, `stat` lanzará un objeto de error con una propiedad `code` de `"ENOENT"`. Estos códigos algo oscuros, inspirados en ((Unix)), son la forma en que se reconocen los tipos de error en Node. +Cuando el archivo no existe, `stat` lanzará un objeto de error con una propiedad `code` de `"ENOENT"`. Estos códigos extraños, inspirados en ((Unix)), son la forma en que se reconocen los tipos de error en Node. {{index "Tipo Stats", "función stat", "método isDirectory"}} -El objeto `stats` devuelto por `stat` nos indica varias cosas sobre un ((archivo)), como su tamaño (propiedad `size`) y su ((fecha de modificación)) (`mtime`). Aquí nos interesa saber si es un ((directorio)) o un archivo regular, lo cual nos dice el método `isDirectory`. +El objeto `stats` devuelto por `stat` nos indica varias cosas sobre un ((archivo)), como su tamaño (propiedad `size`) y su ((fecha de modificación)) (`mtime`). Aquí nos interesa saber si se trata de un ((directorio)) o de un archivo sin más. Esto nos lo dice el método `isDirectory`. {{index "función readdir"}} -Usamos `readdir` para leer la matriz de archivos en un ((directorio)) y devolverla al cliente. Para archivos normales, creamos un flujo de lectura con `createReadStream` y lo devolvemos como cuerpo, junto con el tipo de contenido que nos proporciona el paquete `mime` para el nombre del archivo. +Usamos `readdir` para leer el array de archivos en un ((directorio)) y devolverlo al cliente. Para archivos normales, creamos un flujo de lectura con `createReadStream` y lo devolvemos como cuerpo, junto con el tipo de contenido que nos proporciona el paquete `mime` para el nombre del archivo. {{index "Node.js", "ejemplo de servidor de archivos", "método DELETE", "función rmdir", "función unlink"}} El código para manejar las solicitudes `DELETE` es ligeramente más sencillo. -```{includeCode: ">code/file_server.mjs"} +```{includeCode: ">code/file_server.mjs", lang:null} import {rmdir, unlink} from "node:fs/promises"; methods.DELETE = async function(request) { @@ -575,17 +580,17 @@ methods.DELETE = async function(request) { {{index "204 (código de estado HTTP)", "cuerpo (HTTP)"}} -Cuando una ((respuesta HTTP)) no contiene datos, se puede usar el código de estado 204 ("sin contenido") para indicarlo. Dado que la respuesta a la eliminación no necesita transmitir ninguna información más allá de si la operación tuvo éxito, es sensato devolver eso aquí. +Cuando una ((respuesta HTTP)) no contiene datos, se puede usar el código de estado 204 ("sin contenido") para indicarlo. Dado que la respuesta a la eliminación no necesita transmitir ninguna información más allá de si la operación tuvo éxito, es sensato devolver eso en este caso. {{index idempotencia, "respuesta de error"}} -Es posible que te preguntes por qué intentar eliminar un archivo inexistente devuelve un código de estado de éxito en lugar de un error. Cuando el archivo que se está eliminando no está presente, se podría decir que el objetivo de la solicitud ya se ha cumplido. El estándar ((HTTP)) nos anima a hacer solicitudes _idempotentes_, lo que significa que hacer la misma solicitud varias veces produce el mismo resultado que hacerla una vez. De cierta manera, si intentas eliminar algo que ya no está, el efecto que intentabas lograr se ha alcanzado: la cosa ya no está allí. +Es posible que te preguntes por qué intentar eliminar un archivo inexistente devuelve un código de estado de éxito en lugar de un error. Cuando el archivo que se está eliminando no está presente, se podría decir que el objetivo de la solicitud ya se ha cumplido. El estándar ((HTTP)) nos anima a hacer solicitudes _idempotentes_, lo que significa que hacer la misma solicitud varias veces produce el mismo resultado que hacerla una vez. De cierta manera, si intentas eliminar una cosa que ya no está, el efecto que intentabas lograr se ha alcanzado: la cosa ya no está allí. {{index "ejemplo de servidor de archivos", "Node.js", "método PUT"}} Este es el manejador para las solicitudes `PUT`: -```{includeCode: ">code/file_server.mjs"} +```{includeCode: ">code/file_server.mjs", lang:null} import {createWriteStream} from "node:fs"; function pipeStream(from, to) { @@ -595,7 +600,9 @@ function pipeStream(from, to) { to.on("finish", resolve); from.pipe(to); }); -}```methods.PUT = async function(request) { +} + +methods.PUT = async function(request) { let path = urlPath(request.url); await pipeStream(request, createWriteStream(path)); return {status: 204}; @@ -604,15 +611,15 @@ function pipeStream(from, to) { {{index sobrescritura, "204 (código de estado HTTP)", "evento de error", "evento de finalización", "función createWriteStream", "método pipe", stream}} -Esta vez no necesitamos verificar si el archivo existe; si lo hace, simplemente lo sobrescribiremos. Nuevamente usamos `pipe` para mover datos de un flujo legible a uno escribible, en este caso del request al archivo. Pero como `pipe` no está diseñado para devolver una promesa, debemos escribir un contenedor, `pipeStream`, que cree una promesa alrededor del resultado de llamar a `pipe`. +Esta vez no necesitamos verificar si el archivo existe; si lo hace, simplemente lo sobrescribiremos. Nuevamente usamos `pipe` para mover datos de un flujo legible a uno escribible, en este caso de la request al archivo. Pero como `pipe` no está diseñado para devolver una promesa, debemos escribir un contenedor, `pipeStream`, que cree una promesa alrededor del resultado de llamar a `pipe`. {{index "evento de error", "evento de finalización"}} -Cuando algo sale mal al abrir el archivo, `createWriteStream` seguirá devolviendo un flujo, pero ese flujo lanzará un evento de `"error"`. El flujo del request también puede fallar, por ejemplo si la red falla. Por lo tanto, conectamos los eventos de `"error"` de ambos flujos para rechazar la promesa. Cuando `pipe` haya terminado, cerrará el flujo de salida, lo que hará que lance un evento de `"finalización"`. En ese momento podemos resolver la promesa con éxito (devolviendo nada). +Cuando algo sale mal al abrir el archivo, `createWriteStream` seguirá devolviendo un flujo, pero ese flujo lanzará un evento de `"error"`. El flujo de la request también puede fallar, por ejemplo si la red falla. Por lo tanto, conectamos los eventos de `"error"` de ambos flujos para rechazar la promesa. Cuando `pipe` haya terminado, cerrará el flujo de salida, lo que hará que lance un evento de `"finalización"`. En ese momento podemos resolver la promesa con éxito (devolviendo nada). {{index descarga, "ejemplo de servidor de archivos", "Node.js"}} -El script completo del servidor está disponible en [_https://eloquentjavascript.net/code/file_server.mjs_](https://eloquentjavascript.net/code/file_server.mjs). Puedes descargarlo y, después de instalar sus dependencias, ejecutarlo con Node para iniciar tu propio servidor de archivos. Y, por supuesto, puedes modificarlo y ampliarlo para resolver los ejercicios de este capítulo o para experimentar. +El script completo del servidor está disponible en el siguiente enlace: [_https://eloquentjavascript.net/code/file_server.mjs_](https://eloquentjavascript.net/code/file_server.mjs). Puedes descargarlo y, después de instalar sus dependencias, ejecutarlo con Node para iniciar tu propio servidor de archivos. Y, por supuesto, puedes modificarlo y ampliarlo para resolver los ejercicios de este capítulo o para experimentar. {{index "cuerpo (HTTP)", "programa curl", [HTTP, cliente], ["método", HTTP]}} @@ -629,15 +636,17 @@ $ curl http://localhost:8000/file.txt Archivo no encontrado ``` -La primera solicitud para `file.txt` falla ya que el archivo aún no existe. La solicitud `PUT` crea el archivo y, voilà, la siguiente solicitud lo recupera con éxito. Después de eliminarlo con una solicitud `DELETE`, el archivo vuelve a estar ausente. +La primera solicitud para `file.txt` falla ya que el archivo aún no existe. La solicitud `PUT` crea el archivo y, _voilà_, la siguiente solicitud lo recupera con éxito. Después de eliminarlo con una solicitud `DELETE`, el archivo vuelve a desaparecer. ## Resumen {{index "Node.js"}} -Node es un sistema pequeño interesante que nos permite ejecutar JavaScript en un contexto no de navegador. Originalmente fue diseñado para tareas de red para desempeñar el papel de un _nodo_ en una red. Sin embargo, se presta para todo tipo de tareas de script, y si disfrutas escribir JavaScript, automatizar tareas con Node funciona bien. +Node es un sistema pequeño interesante que nos permite ejecutar JavaScript en un contexto no de navegador. Originalmente fue diseñado para tareas de red para desempeñar el papel de un _nodo_ en una red. Sin embargo, se presta para todo tipo de tareas de script y, si disfrutas escribiendo en JavaScript, automatizar tareas con Node funciona bien. + +NPM proporciona paquetes para todo lo que puedas imaginar (y varias cosas que probablemente nunca se te ocurrirían), y te permite descargar e instalar esos paquetes con el programa `npm`. Node viene con varios módulos integrados, incluido el módulo `node:fs` para trabajar con el sistema de archivos y el módulo `node:http` para ejecutar servidores HTTP. -NPM proporciona paquetes para todo lo que puedas imaginar (y varias cosas que probablemente nunca se te ocurrirían), y te permite descargar e instalar esos paquetes con el programa `npm`. Node viene con varios módulos integrados, incluido el módulo `node:fs` para trabajar con el sistema de archivos y el módulo `node:http` para ejecutar servidores HTTP.Todo el input y output en Node se hace de forma asíncrona, a menos que uses explícitamente una variante síncrona de una función, como `readFileSync`. Originalmente, Node usaba devoluciones de llamada para funcionalidades asíncronas, pero el paquete `node:fs/promises` proporciona una interfaz basada en promesas para el sistema de archivos. +Todo input y output en Node se hace de forma asíncrona, a menos que uses explícitamente una variante sincrónica de una función, como `readFileSync`. Originalmente, Node usaba funciones de callback para funcionalidades asíncronas, pero el paquete `node:fs/promises` proporciona una interfaz basada en promesas para el sistema de archivos. ## Ejercicios @@ -653,7 +662,7 @@ Una vez que eso funcione, extiéndelo para que cuando uno de los argumentos sea {{index ["programación asíncrona", "en Node.js"], "programación síncrona"}} -Utiliza funciones asíncronas o síncronas del sistema de archivos según consideres adecuado. Configurar las cosas para que se soliciten múltiples acciones asíncronas al mismo tiempo podría acelerar un poco las cosas, pero no demasiado, ya que la mayoría de los sistemas de archivos solo pueden leer una cosa a la vez. +Utiliza funciones asíncronas o sincrónicas del sistema de archivos según consideres adecuado. Configurar las cosas para que se soliciten múltiples acciones asíncronas al mismo tiempo podría acelerar un poco las cosas, pero no demasiado, ya que la mayoría de los sistemas de archivos solo pueden leer una cosa a la vez. {{hint @@ -663,7 +672,7 @@ Tu primer argumento de línea de comandos, la ((expresión regular)), se puede e {{index "Función readFileSync"}} -Hacer esto de forma síncrona, con `readFileSync`, es más sencillo, pero si usas `node:fs/promises` para obtener funciones que devuelven promesas y escribes una función `async`, el código se ve similar. +Hacer esto de forma sincrónica, con `readFileSync`, es más sencillo, pero si usas `node:fs/promises` para obtener funciones que devuelven promesas y escribes una función `async`, el código será parecido. {{index "Función stat", "Función statSync", "Método isDirectory"}} @@ -671,7 +680,7 @@ Para averiguar si algo es un directorio, nuevamente puedes usar `stat` (o `statS {{index "Función readdir", "Función readdirSync"}} -Explorar un directorio es un proceso ramificado. Puedes hacerlo usando una función recursiva o manteniendo un array de tareas pendientes (archivos que aún deben ser explorados). Para encontrar los archivos en un directorio, puedes llamar a `readdir` o `readdirSync`. Observa la extraña capitalización: el nombrado de funciones del sistema de archivos de Node se basa vagamente en las funciones estándar de Unix, como `readdir`, que son todas en minúsculas, pero luego agrega `Sync` con una letra mayúscula. +Explorar un directorio es un proceso ramificado. Puedes hacerlo usando una función recursiva o manteniendo un array de tareas pendientes (archivos que aún deben ser explorados). Para encontrar los archivos en un directorio, puedes llamar a `readdir` o `readdirSync`. Observa la extraña manera de escribir estos nombres: el nombrado de funciones del sistema de archivos de Node se basa vagamente en las funciones estándar de Unix, como `readdir`, que son todas en minúsculas, pero luego agrega `Sync` con una letra mayúscula, como haríamos con una función de JavaScript. Para obtener el nombre completo de un archivo leído con `readdir`, debes combinarlo con el nombre del directorio, ya sea añadiendo `sep` de `node:path` entre ellos, o utilizando la función `join` de ese mismo paquete. @@ -721,7 +730,7 @@ Dado que el servidor de archivos sirve cualquier tipo de archivo e incluso inclu Escribe una página ((HTML)) básica que incluya un archivo JavaScript sencillo. Coloca los archivos en un directorio servido por el servidor de archivos y ábrelos en tu navegador. -Luego, como ejercicio avanzado o incluso como un ((proyecto de fin de semana)), combina todo el conocimiento que has adquirido de este libro para construir una interfaz más amigable para modificar el sitio web—desde _dentro_ del sitio web. +Luego, como ejercicio avanzado o incluso como un ((proyecto de fin de semana)), combina todo el conocimiento que has adquirido de este libro para construir una interfaz más amigable para modificar el sitio web —desde _dentro_ del sitio web. Utiliza un formulario ((HTML)) para editar el contenido de los archivos que conforman el sitio web, permitiendo al usuario actualizarlos en el servidor mediante solicitudes HTTP, como se describe en el [Capítulo ?](http). @@ -729,7 +738,7 @@ Comienza permitiendo que solo un archivo sea editable. Luego haz que el usuario {{index sobrescritura}} -No trabajes directamente en el código expuesto por el servidor de archivos ya que si cometes un error, es probable que dañes los archivos allí. En su lugar, mantén tu trabajo fuera del directorio accesible al público y cópialo allí al hacer pruebas. +No trabajes directamente en el código expuesto por el servidor de archivos ya que si cometes un error, es probable que dañes los archivos que hay allí. En vez de eso, mantén tu trabajo fuera del directorio accesible al público y cópialo allí al hacer pruebas. {{hint diff --git a/21_skillsharing.md b/21_skillsharing.md index 1f8ca5ea..e35eae0c 100644 --- a/21_skillsharing.md +++ b/21_skillsharing.md @@ -4,17 +4,17 @@ {{quote {author: "Margaret Fuller", chapter: true} -Si tienes conocimiento, permite que otros enciendan sus velas en él. +Si tienes conocimiento, permite que otros enciendan sus velas con é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. +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 pasarte y contarle 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). @@ -26,13 +26,13 @@ Este proyecto tiene una parte de _((servidor))_, escrita para ((Node.js)), y una {{index [HTTP, cliente]}} -El servidor mantiene la lista de ((charla))s propuestas para la próxima reunión, y el cliente muestra esta lista. Cada charla tiene un nombre de presentador, un título, un resumen y una matriz de ((comentario))s asociados. El cliente permite a los usuarios proponer nuevas charlas (agregándolas a la lista), eliminar charlas y comentar en charlas existentes. Cada vez que el usuario realiza un cambio de este tipo, el cliente realiza una solicitud HTTP para informar al servidor al respecto. +El servidor mantiene la lista de ((charla))s propuestas para la próxima reunión, y el cliente muestra esta lista. Cada charla tiene un nombre de presentador, un título, un resumen y un array de ((comentario))s asociados. El cliente permite a los usuarios proponer nuevas charlas (agregándolas a la lista), eliminar charlas y comentar en charlas existentes. Cada vez que el usuario realiza un cambio de este tipo, el cliente realiza una solicitud HTTP para informar al servidor al respecto. {{figure {url: "img/skillsharing.png", alt: "Captura de pantalla del sitio web de intercambio de habilidades", width: "10cm"}}} {{index "vista en vivo", "experiencia del usuario", "envío de datos", "conexión"}} -La ((aplicación)) se configurará para mostrar una vista _en vivo_ de las charlas propuestas actuales y sus comentarios. Cada vez que alguien, en algún lugar, envíe una nueva charla o agregue un comentario, todas las personas que tengan la página abierta en sus navegadores deberían ver el cambio de inmediato. Esto plantea un desafío—no hay forma de que un servidor web abra una conexión a un cliente, ni hay una buena forma de saber qué clientes están viendo actualmente un sitio web dado. +La ((aplicación)) se configurará para mostrar una vista _en vivo_ de las charlas propuestas actuales y sus comentarios. Cada vez que alguien, en algún lugar, envíe una nueva charla o agregue un comentario, todas las personas que tengan la página abierta en sus navegadores deberían ver el cambio de inmediato. Esto plantea un desafío —no hay forma de que un servidor web abra una conexión a un cliente, ni hay una buena forma de saber qué clientes están viendo actualmente un sitio web dado. {{index "Node.js"}} @@ -50,7 +50,9 @@ Podemos hacer que el cliente abra la conexión y la mantenga activa para que el Sin embargo, una solicitud ((HTTP)) permite solo un flujo simple de información: el cliente envía una solicitud, el servidor responde una sola vez, y eso es todo. Existe una tecnología llamada _((WebSockets))_ que permite abrir ((conexiones)) para el intercambio arbitrario de datos. Pero usarlas adecuadamente es algo complicado. -En este capítulo, utilizamos una técnica más sencilla—((long polling))—donde los clientes preguntan continuamente al servidor por nueva información mediante solicitudes HTTP regulares, y el servidor retiene su respuesta cuando no tiene nada nuevo que informar. +En este capítulo, utilizamos una técnica más sencilla —((long polling))— donde los clientes preguntan continuamente al servidor por nueva información mediante solicitudes HTTP normales, y el servidor retiene su respuesta cuando no tiene nada nuevo que informar. + +{{note "**N. del T.:** Como con otros muchos conceptos, elegimos en esta traducción no traducir el término _long polling_. Una traducción adecuada sería _sondeo prolongado_, así que la usaremos aquí, junto con otras posibles traducciones que se entenderán por el contexto."}} {{index "vista en vivo"}} @@ -62,7 +64,7 @@ Para evitar que las conexiones se agoten por tiempo (se aborten debido a una fal {{index "Node.js"}} -Un servidor ocupado que utiliza long polling puede tener miles de solicitudes en espera, y por lo tanto ((conexiones)) ((TCP)) abiertas. Node, que facilita la gestión de muchas conexiones sin crear un hilo de control separado para cada una, es ideal para este tipo de sistema. +Un servidor ocupado que utiliza long polling puede tener miles de solicitudes en espera, y, por lo tanto, de ((conexiones)) ((TCP)) abiertas. Node, que facilita la gestión de muchas conexiones sin crear un hilo de control separado para cada una, es ideal para este tipo de sistema. ## Interfaz HTTP @@ -72,7 +74,7 @@ Antes de comenzar a diseñar el servidor o el cliente, pensemos en el punto dond {{index [ruta, URL], ["método", HTTP]}} -Utilizaremos ((JSON)) como formato de nuestro cuerpo de solicitud y respuesta. Al igual que en el servidor de archivos del [Capítulo ?](node#file_server), intentaremos hacer un buen uso de los métodos y ((cabecera))s HTTP. La interfaz se centra en la ruta `/talks`. Las rutas que no comienzan con `/talks` se utilizarán para servir ((archivos estáticos))—el código HTML y JavaScript para el sistema del lado del cliente. +Utilizaremos ((JSON)) como formato de nuestro cuerpo de solicitud y respuesta. Al igual que en el servidor de archivos del [Capítulo ?](node#file_server), intentaremos hacer un buen uso de los métodos y ((cabecera))s HTTP. La interfaz se centra en la ruta `/talks`. Las rutas que no comienzan con `/talks` se utilizarán para servir ((archivos estáticos)) —el código HTML y JavaScript para el sistema del lado del cliente. {{index "Método GET"}} @@ -87,14 +89,15 @@ Una solicitud `GET` a `/talks` devuelve un documento JSON como este: {{index "Método PUT", URL}} -Crear una nueva charla se hace haciendo una solicitud `PUT` a una URL como `/talks/Unituning`, donde la parte después de la segunda barra es el título de la charla. El cuerpo de la solicitud `PUT` debe contener un objeto ((JSON)) que tenga propiedades `presenter` y `summary`. +Una nueva charla se creará haciendo una solicitud `PUT` a una URL como `/talks/Unituning`, donde la parte después de la segunda barra es el título de la charla. El cuerpo de la solicitud `PUT` debe contener un objeto ((JSON)) que tenga propiedades `presenter` y `summary`. {{index "Función encodeURIComponent", [escape, "en URLs"], [espacios en blanco, "en URLs"]}} Dado que los títulos de las charlas pueden contener espacios y otros caracteres que normalmente no aparecen en una URL, las cadenas de título deben ser codificadas con la función `encodeURIComponent` al construir una URL de ese tipo. ``` -console.log("/talks/" + encodeURIComponent("Cómo hacer el caballito")); +console.log("/talks/" + + encodeURIComponent("Cómo hacer el caballito")); // → /talks/Cómo%20hacer%20el%20caballito ``` @@ -126,7 +129,7 @@ Content-Length: 72 {{index "cadena de consulta", tiempo de espera, "encabezado ETag", "encabezado If-None-Match"}} -Para soportar ((encuestas prolongadas)), las solicitudes `GET` a `/talks` pueden incluir encabezados adicionales que informen al servidor para retrasar la respuesta si no hay nueva información disponible. Usaremos un par de encabezados normalmente destinados a gestionar el almacenamiento en caché: `ETag` y `If-None-Match`. +Para soportar ((long pollings)), las solicitudes `GET` a `/talks` pueden incluir encabezados adicionales que informen al servidor para retrasar la respuesta si no hay nueva información disponible. Usaremos un par de encabezados normalmente destinados a gestionar el almacenamiento en caché: `ETag` y `If-None-Match`. {{index "304 (código de estado HTTP)"}} @@ -134,7 +137,9 @@ Los servidores pueden incluir un encabezado `ETag` ("etiqueta de entidad") en un {{index "encabezado Prefer"}} -Necesitamos algo como esto, donde el cliente puede decirle al servidor qué versión de la lista de charlas tiene, y el servidor responde solo cuando esa lista ha cambiado. Pero en lugar de devolver inmediatamente una respuesta 304, el servidor debería demorar la respuesta y devolverla solo cuando haya algo nuevo disponible o haya transcurrido una cantidad de tiempo determinada. Para distinguir las solicitudes de encuestas prolongadas de las solicitudes condicionales normales, les damos otro encabezado, `Prefer: wait=90`, que le indica al servidor que el cliente está dispuesto a esperar hasta 90 segundos por la respuesta.El servidor mantendrá un número de versión que actualiza cada vez que cambian las charlas y lo utilizará como valor `ETag`. Los clientes pueden hacer solicitudes como esta para ser notificados cuando las charlas cambien: +Necesitamos algo como esto, donde el cliente puede decirle al servidor qué versión de la lista de charlas tiene, y el servidor responde solo cuando esa lista ha cambiado. Pero en lugar de devolver inmediatamente una respuesta 304, el servidor debería demorar la respuesta y devolverla solo cuando haya algo nuevo disponible o haya transcurrido una cantidad de tiempo determinada. Para distinguir las solicitudes de encuestas prolongadas de las solicitudes condicionales normales, les damos otro encabezado, `Prefer: wait=90`, que le indica al servidor que el cliente está dispuesto a esperar hasta 90 segundos por la respuesta. + +El servidor mantendrá un número de versión que actualiza cada vez que cambian las charlas y lo utilizará como valor `ETag`. Los clientes pueden hacer solicitudes como esta para ser notificados cuando las charlas cambien: ```{lang: null} GET /talks HTTP/1.1 @@ -153,7 +158,7 @@ Content-Length: 295 {{index seguridad}} -El protocolo descrito aquí no realiza ningún ((control de acceso)). Cualquiera puede comentar, modificar charlas e incluso eliminarlas. (Dado que Internet está lleno de ((matones)), poner un sistema en línea sin una protección adicional probablemente no terminaría bien). +El protocolo descrito aquí no realiza ningún ((control de acceso)). Cualquiera puede comentar, modificar charlas e incluso eliminarlas. Dado que Internet está lleno de ((matones)), poner un sistema en línea sin protección adicional probablemente no terminaría bien. ## El servidor @@ -171,13 +176,13 @@ Nuestro servidor utilizará `createServer` de Node para iniciar un servidor HTTP Un _((enrutador))_ es un componente que ayuda a despachar una solicitud a la función que puede manejarla. Puedes indicarle al enrutador, por ejemplo, que las solicitudes `PUT` con una ruta que coincida con la expresión regular `/^\/talks\/([^\/]+)$/` (`/talks/` seguido de un título de charla) pueden ser manejadas por una función dada. Además, puede ayudar a extraer las partes significativas de la ruta (en este caso el título de la charla), envueltas en paréntesis en la ((expresión regular)), y pasarlas a la función manejadora. -Hay varios paquetes de enrutadores buenos en ((NPM)), pero aquí escribiremos uno nosotros mismos para ilustrar el principio. +Hay varios paquetes de enrutadores buenos en ((NPM)), pero aquí escribiremos uno nosotros mismos para mostrar cómo funciona. {{index "palabra clave import", "clase Router", "módulo"}} Este es `router.mjs`, que luego `importaremos` desde nuestro módulo del servidor: -```{includeCode: ">code/skillsharing/router.mjs"} +```{includeCode: ">code/skillsharing/router.mjs", lang:null} export class Router { constructor() { this.routes = []; @@ -213,7 +218,7 @@ Cuando una solicitud no coincide con ninguno de los tipos de solicitud definidos Opté por `serve-static`. Este no es el único servidor de este tipo en NPM, pero funciona bien y se ajusta a nuestros propósitos. El paquete `serve-static` exporta una función que puede ser llamada con un directorio raíz para producir una función manipuladora de solicitudes. La función manipuladora acepta los argumentos `request` y `response` proporcionados por el servidor de `"node:http"`, y un tercer argumento, una función que se llamará si ningún archivo coincide con la solicitud. Queremos que nuestro servidor primero compruebe las solicitudes que deberíamos manejar de manera especial, según lo definido en el enrutador, por lo que lo envolvemos en otra función. -```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs", lang:null} import {createServer} from "node:http"; import serveStatic from "serve-static"; @@ -249,7 +254,7 @@ La función `serveFromRouter` tiene la misma interfaz que `fileServer`, tomando Nuestra función `serveFromRouter` utiliza una convención similar a la del servidor de archivos del [capítulo anterior](node) para las respuestas: los manejadores en el enrutador devuelven promesas que se resuelven en objetos que describen la respuesta. -```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs", lang:null} import {Router} from "./router.mjs"; const router = new Router(); @@ -272,14 +277,14 @@ async function serveFromRouter(server, request, ### Charlas como recursos -Las ((charlas)) que se han propuesto se almacenan en la propiedad `talks` del servidor, un objeto cuyas propiedades son los títulos de las charlas. Agregaremos algunos controladores a nuestro enrutador que expongan estos como ((recursos)) HTTP bajo `/charlas/[título]`. +Las ((charlas)) que se han propuesto se almacenan en la propiedad `talks` del servidor, un objeto cuyas propiedades son los títulos de las charlas. Agregaremos algunos controladores a nuestro enrutador que expongan estos como ((recursos)) HTTP bajo `/talks/[título]`. {{index "método GET", "404 (código de estado HTTP)", "función hasOwn"}} El controlador para las solicitudes que `GET` una sola charla debe buscar la charla y responder ya sea con los datos JSON de la charla o con una respuesta de error 404. -```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} -const talkPath = /^\/charlas\/([^\/]+)$/; +```{includeCode: ">code/skillsharing/skillsharing_server.mjs", lang:null} +const talkPath = /^\/talks\/([^\/]+)$/; router.add("GET", talkPath, async (server, title) => { if (Object.hasOwn(server.talks, title)) { @@ -293,9 +298,9 @@ router.add("GET", talkPath, async (server, title) => { {{index "método DELETE"}} -Eliminar una charla se hace eliminándola del objeto `talks`. +Para eliminar una charla la eliminamos del objeto `talks`. -```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs", lang:null} router.add("DELETE", talkPath, async (server, title) => { if (Object.hasOwn(server.talks, title)) { delete server.talks[title]; @@ -307,11 +312,11 @@ router.add("DELETE", talkPath, async (server, title) => { {{index "espera larga", "método updated"}} -El método `updated`, que definiremos [más adelante](skillsharing#updated), notifica a las solicitudes de espera larga sobre el cambio. +El método `updated`, que definiremos [más adelante](skillsharing#updated), notifica a las solicitudes de long polling sobre el cambio. {{index "validación", entrada, "método PUT"}} -Un controlador que necesita leer cuerpos de solicitud es el controlador `PUT`, que se utiliza para crear nuevas ((charlas)). Debe verificar si los datos que se le proporcionaron tienen propiedades `presentador` y `resumen`, que son cadenas de texto. Cualquier dato que provenga de fuera del sistema podría ser un sinsentido y no queremos corromper nuestro modelo de datos interno o ((fallar)) cuando lleguen solicitudes incorrectas. +Un manejador que necesita leer cuerpos de solicitud es el manejador `PUT`, que se utiliza para crear nuevas ((charlas)). Debe verificar si los datos que se le proporcionaron tienen propiedades `presenter` y `summary`, que son cadenas de texto. Cualquier dato que provenga de fuera del sistema podría ser un sinsentido y no queremos corromper nuestro modelo de datos interno o ((fallar)) cuando lleguen solicitudes incorrectas. {{index "método updated"}} @@ -321,7 +326,7 @@ Si los datos parecen válidos, el controlador almacena un objeto que representa Para leer el cuerpo del flujo de solicitud, utilizaremos la función `json` de `"node:stream/consumers"`, que recopila los datos en el flujo y luego los analiza como JSON. Hay exportaciones similares llamadas `text` (para leer el contenido como una cadena) y `buffer` (para leerlo como datos binarios) en este paquete. Dado que `json` es un nombre genérico, la importación lo renombra a `readJSON` para evitar confusiones. -```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs", lang:null} import {json as readJSON} from "node:stream/consumers" router.add("PUT", talkPath, @@ -341,9 +346,11 @@ router.add("PUT", talkPath, server.updated(); return {status: 204}; }); -```Agregar un ((comentario)) a una ((charla)) funciona de manera similar. Usamos `readJSON` para obtener el contenido de la solicitud, validamos los datos resultantes y los almacenamos como un comentario cuando parecen válidos. +``` + +Agregar un ((comentario)) a una ((charla)) funciona de manera similar. Usamos `readJSON` para obtener el contenido de la solicitud, validamos los datos resultantes y los almacenamos como un comentario cuando parecen válidos. -```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs", lang:null} router.add("POST", /^\/talks\/([^\/]+)\/comments$/, async (server, title, request) => { let comment = await readJSON(request); @@ -365,15 +372,15 @@ router.add("POST", /^\/talks\/([^\/]+)\/comments$/, Intentar agregar un comentario a una charla inexistente devuelve un error 404. -### Soporte para larga espera +### Soporte para long polling -El aspecto más interesante del servidor es la parte que maneja la ((larga espera)). Cuando llega una solicitud `GET` para `/charlas`, puede ser una solicitud regular o una solicitud de larga espera. +El aspecto más interesante del servidor es la parte que maneja el _((long polling))_ (o la larga espera). Cuando llega una solicitud `GET` para `/talks`, puede ser una solicitud normal o una solicitud de larga espera. {{index "método talkResponse", "encabezado ETag"}} -Habrá varios lugares en los que debamos enviar una matriz de charlas al cliente, por lo que primero definimos un método auxiliar que construya dicha matriz e incluya un encabezado `ETag` en la respuesta. +Habrá varios lugares en los que debamos enviar un array de charlas al cliente, por lo que primero definimos un método auxiliar que construya dicho array e incluya un encabezado `ETag` en la respuesta. -```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs", lang:null} SkillShareServer.prototype.talkResponse = function() { let talks = Object.keys(this.talks) .map(title => this.talks[title]); @@ -388,9 +395,9 @@ SkillShareServer.prototype.talkResponse = function() { {{index "cadena de consulta", "paquete url", "análisis"}} -El controlador en sí mismo necesita examinar los encabezados de la solicitud para ver si están presentes los encabezados `If-None-Match` y `Prefer`. Node almacena los encabezados, cuyos nombres se especifican como insensibles a mayúsculas y minúsculas, bajo sus nombres en minúsculas. +El manejador en sí mismo necesita examinar los encabezados de la solicitud para ver si están presentes los encabezados `If-None-Match` y `Prefer`. Node almacena los encabezados, cuyos nombres se especifican como insensibles a mayúsculas y minúsculas, bajo sus nombres en minúsculas. -```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs", lang:null} router.add("GET", /^\/talks$/, async (server, request) => { let tag = /"(.*)"/.exec(request.headers["if-none-match"]); let wait = /\bwait=(\d+)/.exec(request.headers["prefer"]); @@ -406,13 +413,13 @@ router.add("GET", /^\/talks$/, async (server, request) => { {{index "larga espera", "método waitForChanges", "encabezado If-None-Match", "encabezado Prefer"}} -Si no se proporcionó ninguna etiqueta o se proporcionó una etiqueta que no coincide con la versión actual del servidor, el controlador responde con la lista de charlas. Si la solicitud es condicional y las charlas no han cambiado, consultamos el encabezado `Prefer` para ver si debemos retrasar la respuesta o responder de inmediato. +Si no se proporcionó ninguna etiqueta o se proporcionó una etiqueta que no coincide con la versión actual del servidor, el manejador responde con la lista de charlas. Si la solicitud es condicional y las charlas no han cambiado, consultamos el encabezado `Prefer` para ver si debemos retrasar la respuesta o responder de inmediato. {{index "304 (código de estado HTTP)", "función setTimeout", tiempo de espera, "función de devolución de llamada"}} -Las funciones de devolución de llamada para solicitudes retardadas se almacenan en la matriz `waiting` del servidor para que puedan ser notificadas cuando ocurra algo. El método `waitForChanges` también establece inmediatamente un temporizador para responder con un estado 304 cuando la solicitud haya esperado el tiempo suficiente. +Las funciones de callback para solicitudes retardadas se almacenan en el array `waiting` del servidor para que puedan ser notificadas cuando ocurra algo. El método `waitForChanges` también establece inmediatamente un temporizador para responder con un estado 304 cuando la solicitud haya esperado el tiempo suficiente. -```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs", lang:null} SkillShareServer.prototype.waitForChanges = function(time) { return new Promise(resolve => { this.waiting.push(resolve); @@ -431,7 +438,7 @@ SkillShareServer.prototype.waitForChanges = function(time) { Registrar un cambio con `updated` incrementa la propiedad `versión` y despierta todas las solicitudes en espera. -```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs", lang:null} SkillShareServer.prototype.updated = function() { this.version++; let response = this.talkResponse(); @@ -444,7 +451,7 @@ SkillShareServer.prototype.updated = function() { Eso concluye el código del servidor. Si creamos una instancia de `SkillShareServer` y la iniciamos en el puerto 8000, el servidor HTTP resultante servirá archivos desde el subdirectorio `public` junto con una interfaz para manejar charlas bajo la URL `/talks`. -```{includeCode: ">code/skillsharing/skillsharing_server.mjs"} +```{includeCode: ">code/skillsharing/skillsharing_server.mjs", lang:null} new SkillShareServer({}).start(8000); ``` @@ -458,7 +465,7 @@ La parte del ((cliente)) del sitio web de intercambio de habilidades consiste en {{index "index.html"}} -Es una convención ampliamente utilizada para servidores web intentar servir un archivo llamado `index.html` cuando se realiza una solicitud directamente a una ruta que corresponde a un directorio. El módulo de servidor de archivos que utilizamos, `serve-static`, soporta esta convención. Cuando se realiza una solicitud a la ruta `/`, el servidor busca el archivo `./public/index.html` (`./public` siendo la raíz que le dimos) y devuelve ese archivo si se encuentra. +Es una convención ampliamente utilizada para servidores web intentar servir un archivo llamado `index.html` cuando se realiza una solicitud directamente a una ruta que corresponde a un directorio. El módulo de servidor de archivos que utilizamos, `serve-static`, adopta esta convención. Cuando se realiza una solicitud a la ruta `/`, el servidor busca el archivo `./public/index.html` (`./public` siendo la raíz que le dimos) y devuelve ese archivo si se encuentra. Por lo tanto, si queremos que una página aparezca cuando un navegador apunta a nuestro servidor, deberíamos colocarla en `public/index.html`. Este es nuestro archivo de índice: @@ -479,13 +486,13 @@ Define el ((título)) del documento e incluye una hoja de estilos, que define al ### Acciones -El estado de la aplicación consiste en la lista de charlas y el nombre del usuario, y lo almacenaremos en un objeto `{charlas, usuario}`. No permitimos que la interfaz de usuario manipule directamente el estado ni envíe solicitudes HTTP. En cambio, puede emitir _acciones_ que describen lo que el usuario está intentando hacer. +El estado de la aplicación consiste en la lista de charlas y el nombre del usuario, y lo almacenaremos en un objeto `{talks, user}`. No permitimos que la interfaz de usuario manipule directamente el estado ni envíe solicitudes HTTP. En cambio, puede emitir _acciones_ que describen lo que el usuario está intentando hacer. {{index "función handleAction"}} -La función `handleAction` toma una acción de este tipo y la lleva a cabo. Debido a que nuestras actualizaciones de estado son tan simples, los cambios de estado se manejan en la misma función. +La función `handleAction` toma una acción de este tipo y la lleva a cabo. Como nuestras actualizaciones de estado son tan simples, los cambios de estado se manejan en la misma función. -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, lang:null} function handleAction(state, action) { if (action.type == "setUser") { localStorage.setItem("userName", action.user); @@ -526,7 +533,7 @@ Almacenaremos el nombre del usuario en `localStorage` para que pueda ser restaur Las acciones que necesitan involucrar al servidor realizan peticiones a la red, utilizando `fetch`, a la interfaz HTTP descrita anteriormente. Utilizamos una función de envoltura, `fetchOK`, que se asegura de que la promesa devuelta sea rechazada cuando el servidor devuelve un código de error. -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, lang:null} function fetchOK(url, options) { return fetch(url, options).then(response => { if (response.status < 400) return response; @@ -539,7 +546,7 @@ function fetchOK(url, options) { Esta función auxiliar se utiliza para construir una ((URL)) para una charla con un título dado. -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, lang:null} function talkURL(title) { return "talks/" + encodeURIComponent(title); } @@ -547,9 +554,9 @@ function talkURL(title) { {{index "manejo de errores", "experiencia de usuario", "función reportError"}} -Cuando la petición falla, no queremos que nuestra página simplemente se quede ahí, sin hacer nada sin explicación. Así que definimos una función llamada `reportError`, que al menos muestra al usuario un cuadro de diálogo que le informa que algo salió mal. +Cuando la petición falla, no queremos que nuestra página simplemente se quede ahí, sin hacer nada sin explicación. Así que definimos una función llamada `reportError`, que al menos muestra al usuario un cuadro de diálogo que le informa de que algo salió mal. -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, lang:null} function reportError(error) { alert(String(error)); } @@ -559,9 +566,9 @@ function reportError(error) { {{index "función renderUserField"}} -Utilizaremos un enfoque similar al que vimos en el [Capítulo ?](paint), dividiendo la aplicación en componentes. Pero dado que algunos de los componentes nunca necesitan actualizarse o siempre se redibujan por completo cuando se actualizan, definiremos aquellos no como clases, sino como funciones que devuelven directamente un nodo DOM. Por ejemplo, aquí hay un componente que muestra el campo donde el usuario puede ingresar su nombre: +Utilizaremos un enfoque similar al que vimos en el [Capítulo ?](paint), dividiendo la aplicación en componentes. Pero dado que algunos de los componentes nunca necesitan actualizarse o siempre se redibujan por completo cuando se actualizan, definiremos aquellos no como clases, sino como funciones que devuelven directamente un nodo del DOM. Por ejemplo, aquí hay un componente que muestra el campo dónde el usuario puede ingresar su nombre: -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, lang:null} function renderUserField(name, dispatch) { return elt("label", {}, "Tu nombre: ", elt("input", { type: "text", @@ -577,7 +584,7 @@ function renderUserField(name, dispatch) { La función `elt` utilizada para construir elementos DOM es la misma que usamos en el [Capítulo ?](paint). -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, hidden: true} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, hidden: true, lang:null} function elt(type, props, ...children) { let dom = document.createElement(type); if (props) Object.assign(dom, props); @@ -593,7 +600,7 @@ function elt(type, props, ...children) { Se utiliza una función similar para renderizar charlas, que incluyen una lista de comentarios y un formulario para agregar un nuevo ((comentario)). -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, lang:null} function renderTalk(talk, dispatch) { return elt( "section", {className: "talk"}, @@ -629,7 +636,7 @@ Cuando se crean piezas moderadamente complejas del DOM, este estilo de programac Los comentarios son simples de renderizar. -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, lang:null} function renderComment(comment) { return elt("p", {className: "comment"}, elt("strong", null, comment.author), @@ -641,7 +648,7 @@ function renderComment(comment) { Finalmente, el formulario que el usuario puede usar para crear una nueva charla se representa de la siguiente manera: -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, lang:null} function renderTalkForm(dispatch) { let title = elt("input", {type: "text"}); let summary = elt("input", {type: "text"}); @@ -664,9 +671,9 @@ function renderTalkForm(dispatch) { {{index "función pollTalks", "sondeo prolongado", "cabecera If-None-Match", "cabecera Prefer", "función fetch"}} -Para iniciar la aplicación necesitamos la lista actual de charlas. Dado que la carga inicial está estrechamente relacionada con el proceso de sondeo prolongado, el `ETag` de la carga debe ser utilizado al sondear, escribiremos una función que siga sondeando al servidor en busca de `/charlas` y llame a una ((función de devolución de llamada)) cuando un nuevo conjunto de charlas esté disponible. +Para iniciar la aplicación necesitamos la lista actual de charlas. Dado que la carga inicial está estrechamente relacionada con el proceso de sondeo prolongado (_long polling_), el `ETag` de la carga debe ser utilizado al sondear, escribiremos una función que siga sondeando al servidor en busca de `/talks` y llame a una ((función de callback)) cuando un nuevo conjunto de charlas esté disponible. -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, lang:null} async function pollTalks(update) { let tag = undefined; for (;;) { @@ -688,17 +695,17 @@ 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. +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 long polling. {{index "manejo de errores", "clase Promise", "función setTimeout"}} -Cuando una solicitud falla, la función espera un momento y luego intenta nuevamente. De esta manera, si tu conexión de red se interrumpe por un tiempo y luego vuelve, la aplicación puede recuperarse y continuar actualizándose. La promesa resuelta a través de `setTimeout` es una forma de forzar a la función `async` a esperar. +Cuando una solicitud falla, la función espera un momento y luego lo intenta de nuevo. De esta manera, si tu conexión de red se interrumpe por un tiempo y luego vuelve, la aplicación puede recuperarse y continuar actualizándose. La promesa resuelta a través de `setTimeout` es una forma de forzar a la función `async` a esperar. {{index "304 (código de estado HTTP)", "encabezado ETag"}} -Cuando el servidor devuelve una respuesta 304, eso significa que una solicitud de intercambio de larga duración expiró, por lo que la función debería comenzar inmediatamente la siguiente solicitud. Si la respuesta es un estado 200 normal, su cuerpo se lee como JSON y se pasa a la devolución de llamada, y el valor del encabezado `ETag` se almacena para la próxima iteración. +Cuando el servidor devuelve una respuesta 304, eso significa que una solicitud de intercambio de larga duración expiró, por lo que la función debería comenzar inmediatamente la siguiente solicitud. Si la respuesta es un estado 200 normal, su cuerpo se lee como JSON y se pasa a la función de callback, y el valor del encabezado `ETag` se almacena para la próxima iteración. ### La aplicación @@ -706,7 +713,7 @@ Cuando el servidor devuelve una respuesta 304, eso significa que una solicitud d El siguiente componente une toda la interfaz de usuario: -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, lang:null} class SkillShareApp { constructor(state, dispatch) { this.dispatch = dispatch; @@ -737,7 +744,7 @@ Cuando las charlas cambian, este componente las vuelve a dibujar todas. Esto es Podemos iniciar la aplicación de esta manera: -```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no} +```{includeCode: ">code/skillsharing/public/skillsharing_client.js", test: no, lang:null} function runApp() { let user = localStorage.getItem("userName") || "Anon"; let state, app; @@ -766,7 +773,7 @@ Si ejecutas el servidor y abres dos ventanas del navegador para [_http://localho {{index "Node.js", NPM}} -Los siguientes ejercicios implicarán modificar el sistema definido en este capítulo. Para trabajar en ellos, asegúrate de ((descargar)) primero el código ([_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip)), tener Node instalado ([_https://nodejs.org_](https://nodejs.org)), e instalar la dependencia del proyecto con `npm install`. +Los siguientes ejercicios implicarán modificar el sistema definido en este capítulo. Para trabajar en ellos, asegúrate de ((descargar)) primero el código ([_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip)), tener Node instalado ([_https://nodejs.org_](https://nodejs.org)), e instalar las dependencias del proyecto con `npm install`. ### Persistencia en disco @@ -786,7 +793,7 @@ La solución más simple que se me ocurre es codificar todo el objeto `talks` co {{index "función readFile", "función JSON.parse"}} -Elige un nombre de archivo, por ejemplo `./talks.json`. Cuando el servidor se inicie, puede intentar leer ese archivo con `readFile`, y si tiene éxito, el servidor puede usar el contenido del archivo como sus datos iniciales. +Elige un nombre de archivo, por ejemplo `./talks.json`. Cuando el servidor se inicie, puede intentar leer ese archivo con `readFile`, y si tiene éxito, el servidor puede usar el contenido del archivo como datos iniciales. hint}} @@ -794,7 +801,7 @@ hint}} {{index "restablecimiento del campo de comentarios (ejercicio)", plantilla, [estado, "de la aplicación"]}} -La remodelación completa de las charlas funciona bastante bien porque generalmente no se puede distinguir entre un nodo de DOM y su sustitución idéntica. Pero hay excepciones. Si empiezas a escribir algo en el campo de comentarios para una charla en una ventana del navegador y luego, en otra, añades un comentario a esa charla, el campo en la primera ventana se volverá a dibujar, eliminando tanto su contenido como su enfoque. +La remodelación completa de las charlas funciona bastante bien porque generalmente no se puede distinguir entre un nodo del DOM y su sustitución idéntica. Pero hay excepciones. Si empiezas a escribir algo en el campo de comentarios para una charla en una ventana del navegador y luego, en otra, añades un comentario a esa charla, el campo en la primera ventana se volverá a dibujar, eliminando tanto su contenido como su enfoque. Cuando varias personas están añadiendo comentarios al mismo tiempo, esto podría resultar molesto. ¿Puedes idear una manera de resolverlo? @@ -808,6 +815,6 @@ La parte difícil es que, cuando llega una lista modificada de charlas, tenemos {{index "sincronización", "vista en vivo"}} -Para hacer esto, podría ser útil mantener una estructura de datos que almacene los componentes de las charlas bajo los títulos de las charlas para que puedas averiguar fácilmente si existe un componente para una charla dada. Luego puedes recorrer la nueva matriz de charlas y, para cada una de ellas, sincronizar un componente existente o crear uno nuevo. Para eliminar los componentes de charlas eliminadas, también tendrás que recorrer los componentes y comprobar si las charlas correspondientes aún existen. +Para hacer esto, podría ser útil mantener una estructura de datos que almacene los componentes de las charlas bajo los títulos de las charlas para que puedas averiguar fácilmente si existe un componente para una charla dada. Luego puedes recorrer el nuevo array de charlas y, para cada una de ellas, sincronizar un componente existente o crear uno nuevo. Para eliminar los componentes de charlas eliminadas, también tendrás que recorrer los componentes y comprobar si las charlas correspondientes aún existen. hint}} \ No newline at end of file diff --git a/README.md b/README.md index d102b9b2..7a5865d1 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Eloquent JavaScript en Español <a href='https://midu.link/eloquent'> -<img alt="Portada del libro" src="https://github.com/midudev/eloquent-javascript-es/assets/1561955/727c8b2a-0a0f-4e9b-a6e1-9c1ec5765612" width="250px" /> +<img alt="Portada del libro" src="./img/cover.jpg" width="250px" /> </a> -Traducción de la cuarta edición de [Eloquent JavaScript](https://eloquentjavascript.net/) al español por [midudev](https://twitch.tv/midudev). +Traducción de la cuarta edición de [Eloquent JavaScript](https://eloquentjavascript.net/) al español por [midudev](https://twitch.tv/midudev). Modificaciones por [ckdvk](https://github.com/ckdvk) para una lectura más natural. [Repositorio original](https://github.com/marijnh/Eloquent-JavaScript) @@ -49,4 +49,4 @@ Traducción de la cuarta edición de [Eloquent JavaScript](https://eloquentjavas Avallone. Fotografía del pueblo en el Capítulo 11 por Fabrice Creuzot. Concepto de juego para el Capítulo 16 por <a href="http://lessmilk.com">Thomas Palef</a>.</p> -</div> \ No newline at end of file +</div> diff --git a/book.epub b/book.epub index 69a68515..ef79bb44 100644 Binary files a/book.epub and b/book.epub differ diff --git a/book.mobi b/book.mobi index d89c7c7a..1997b7d1 100644 Binary files a/book.mobi and b/book.mobi differ diff --git a/book.pdf b/book.pdf index 7b9356fb..62d918c7 100644 Binary files a/book.pdf and b/book.pdf differ diff --git a/book_mobile.pdf b/book_mobile.pdf index 570a4348..778d4ec3 100644 Binary files a/book_mobile.pdf and b/book_mobile.pdf differ 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/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/epub/style.css b/epub/style.css index a2ad2876..5ae6c28f 100644 --- a/epub/style.css +++ b/epub/style.css @@ -158,3 +158,20 @@ ol li p { .cm-atom {color: #219;} .cm-number {color: #164;} .cm-def {color: #00f;} + + +.translator-note { + background-color: #f8f9fa; + border-left: 4px solid #e9da0875; + padding: 0px 12px; + font-size: medium; + font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; + word-spacing: 0.2em; + margin: 10px 0; + font-style: italic; +} +.translator-note code { + font-size: inherit; + font-family: 'Courier New', Courier, monospace; + background-color: #f1f1f100; +} diff --git a/html/00_intro.html b/html/00_intro.html index 77ba1004..69a24ea4 100644 --- a/html/00_intro.html +++ b/html/00_intro.html @@ -16,31 +16,31 @@ <h1>Introducción</h1> <p><a class="p_ident" id="p-d5O1EXJ3e7" href="#p-d5O1EXJ3e7" tabindex="-1" role="presentation"></a>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.</p> -<footer>Ellen Ullman, <cite>Cerca de la máquina: Tecnofilia y sus Descontentos</cite></footer> +<footer>Ellen Ullman, <cite>Close to the Machine: Technophilia and Its Discontents</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_00.jpg" alt="Ilustración de un destornillador junto a una placa de circuitos de aproximadamente el mismo tamaño"></figure> -<p><a class="p_ident" id="p-92tUqeqK59" href="#p-92tUqeqK59" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-92tUqeqK59" href="#p-92tUqeqK59" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-Fy6EtbARYb" href="#p-Fy6EtbARYb" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-Fy6EtbARYb" href="#p-Fy6EtbARYb" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-SnAitKxiCq" href="#p-SnAitKxiCq" tabindex="-1" role="presentation"></a>Ahí es donde entra en juego la programación. <em>Programar</em> es el acto de construir un <em>programa</em>—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.</p> +<p><a class="p_ident" id="p-3/n3ngu7wQ" href="#p-3/n3ngu7wQ" tabindex="-1" role="presentation"></a>Ahí es donde entra en juego la programación. <em>Programar</em> es el acto de construir un <em>programa</em>—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.</p> -<p><a class="p_ident" id="p-Fw3zORuAFa" href="#p-Fw3zORuAFa" tabindex="-1" role="presentation"></a>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 <em>una eternidad</em> 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.</p> +<p><a class="p_ident" id="p-Fw3zORuAFa" href="#p-Fw3zORuAFa" tabindex="-1" role="presentation"></a>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 <em>una eternidad</em> 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.</p> -<p><a class="p_ident" id="p-1oy7fNrtli" href="#p-1oy7fNrtli" tabindex="-1" role="presentation"></a>La mayoría de la programación se realiza con lenguajes de programación. Un <em>lenguaje de programación</em> 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.</p> +<p><a class="p_ident" id="p-Wz1fJfzcaD" href="#p-Wz1fJfzcaD" tabindex="-1" role="presentation"></a>La mayoría de la programación se realiza con lenguajes de programación. Un <em>lenguaje de programación</em> 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.</p> -<p><a class="p_ident" id="p-Hxa6tGupTz" href="#p-Hxa6tGupTz" tabindex="-1" role="presentation"></a>En un momento dado, las interfaces basadas en lenguaje, como los <em>prompts</em> 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, <em>JavaScript</em>, está integrado en cada navegador web moderno—y por lo tanto está disponible en casi todos los dispositivos.</p> +<p><a class="p_ident" id="p-Hxa6tGupTz" href="#p-Hxa6tGupTz" tabindex="-1" role="presentation"></a>En un momento dado, las interfaces basadas en lenguaje, como los <em>prompts</em> 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, <em>JavaScript</em>, está integrado en cada navegador web moderno —y por tanto está disponible en casi todos los dispositivos.</p> -<p><a class="p_ident" id="p-UM1T1aJ7ou" href="#p-UM1T1aJ7ou" tabindex="-1" role="presentation"></a>Este libro intentará que te familiarices lo suficiente con este lenguaje para hacer cosas útiles y entretenidas con él.</p> +<p><a class="p_ident" id="p-UM1T1aJ7ou" href="#p-UM1T1aJ7ou" tabindex="-1" role="presentation"></a>Este libro intentará que te familiarices lo suficiente con este lenguaje como para hacer cosas útiles y entretenidas con él.</p> <h2><a class="h_ident" id="h-VQTIspPR7B" href="#h-VQTIspPR7B" tabindex="-1" role="presentation"></a>Sobre la programación</h2> -<p><a class="p_ident" id="p-TFkPAVo4Zu" href="#p-TFkPAVo4Zu" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-je5fpiAaJ3" href="#p-je5fpiAaJ3" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-BA4O1NBe2a" href="#p-BA4O1NBe2a" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-2nnx+NCu7r" href="#p-2nnx+NCu7r" tabindex="-1" role="presentation"></a>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 <em>combinará</em> de maneras que requierirán que hagas nuevas conexiones mentales.</p> -<p><a class="p_ident" id="p-60iAe/I8K9" href="#p-60iAe/I8K9" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-60iAe/I8K9" href="#p-60iAe/I8K9" tabindex="-1" role="presentation"></a>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.</p> <blockquote> @@ -48,19 +48,19 @@ <h2><a class="h_ident" id="h-VQTIspPR7B" href="#h-VQTIspPR7B" tabindex="-1" role </blockquote> -<p><a class="p_ident" id="p-60RIJ55+oa" href="#p-60RIJ55+oa" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-60RIJ55+oa" href="#p-60RIJ55+oa" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-o59z2SeQPs" href="#p-o59z2SeQPs" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-o59z2SeQPs" href="#p-o59z2SeQPs" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-uDy6Oe7a3B" href="#p-uDy6Oe7a3B" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-uDy6Oe7a3B" href="#p-uDy6Oe7a3B" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-/9F8Q+2arA" href="#p-/9F8Q+2arA" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-OHXlyW2KqV" href="#p-OHXlyW2KqV" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-B9jltzE+HA" href="#p-B9jltzE+HA" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-B9jltzE+HA" href="#p-B9jltzE+HA" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-Vf3QzWZiwT" href="#h-Vf3QzWZiwT" tabindex="-1" role="presentation"></a>Por qué importa el lenguaje</h2> -<p><a class="p_ident" id="p-kVnydnWFwX" href="#p-kVnydnWFwX" tabindex="-1" role="presentation"></a>Al principio, en los inicios de la informática, no existían los lenguajes de programación. Los programas lucían algo así:</p> +<p><a class="p_ident" id="p-+k9DR0Lgss" href="#p-+k9DR0Lgss" tabindex="-1" role="presentation"></a>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:</p> <pre class="snippet" data-language="null" ><a class="c_ident" id="c-D6PkB3Wa2f" href="#c-D6PkB3Wa2f" tabindex="-1" role="presentation"></a>00110001 00000000 00000000 00110001 00000001 00000001 @@ -72,9 +72,9 @@ <h2><a class="h_ident" id="h-Vf3QzWZiwT" href="#h-Vf3QzWZiwT" tabindex="-1" role 00010000 00000010 00000000 01100010 00000000 00000000</pre> -<p><a class="p_ident" id="p-eQ2BxOZRN2" href="#p-eQ2BxOZRN2" tabindex="-1" role="presentation"></a>Este es un programa para sumar los números del 1 al 10 y mostrar el resultado: <code>1 + 2 + .<wbr>.<wbr>.<wbr> + 10 = 55</code>. 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.</p> +<p><a class="p_ident" id="p-eQ2BxOZRN2" href="#p-eQ2BxOZRN2" tabindex="-1" role="presentation"></a>Este es un programa para sumar los números del 1 al 10 y mostrar el resultado: <code>1 + 2 + .<wbr>.<wbr>.<wbr> + 10 = 55</code>. 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.</p> -<p><a class="p_ident" id="p-qVW3tGNC2l" href="#p-qVW3tGNC2l" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-qVW3tGNC2l" href="#p-qVW3tGNC2l" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-XX2sDr75GI" href="#p-XX2sDr75GI" tabindex="-1" role="presentation"></a>Cada línea del programa anterior contiene una única instrucción. Podría escribirse en español de la siguiente manera:</p> @@ -118,84 +118,86 @@ <h2><a class="h_ident" id="h-Vf3QzWZiwT" href="#h-Vf3QzWZiwT" tabindex="-1" role </ol> -<p><a class="p_ident" id="p-8IC8S1UXRk" href="#p-8IC8S1UXRk" tabindex="-1" role="presentation"></a>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:</p> +<p><a class="p_ident" id="p-8IC8S1UXRk" href="#p-8IC8S1UXRk" tabindex="-1" role="presentation"></a>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:</p> -<pre class="snippet" data-language="null" ><a class="c_ident" id="c-cKkkJHQLZK" href="#c-cKkkJHQLZK" tabindex="-1" role="presentation"></a> Establecer “total” en 0. - Establecer “count” en 1. +<pre class="snippet" data-language="null" ><a class="c_ident" id="c-CGVe9pDADg" href="#c-CGVe9pDADg" tabindex="-1" role="presentation"></a> 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”.</pre> -<p><a class="p_ident" id="p-s8J4Dp+dt3" href="#p-s8J4Dp+dt3" tabindex="-1" role="presentation"></a>¿Puedes ver cómo funciona el programa en este punto? Las dos primeras líneas asignan los valores iniciales a dos ubicaciones de memoria: <code>total</code> se utilizará para construir el resultado de la computación, y <code>count</code> llevará la cuenta del número que estamos observando en ese momento. Las líneas que utilizan <code>compare</code> probablemente sean las más confusas. El programa quiere ver si <code>count</code> 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 <code>compare</code> para calcular el valor de <code>count - 11</code> y tomar una decisión basada en ese valor. Las siguientes dos líneas suman el valor de <code>count</code> al resultado e incrementan <code>count</code> en 1 cada vez que el programa decide que <code>count</code> aún no es 11. Aquí está el mismo programa en JavaScript:</p> +<p><a class="p_ident" id="p-s8J4Dp+dt3" href="#p-s8J4Dp+dt3" tabindex="-1" role="presentation"></a>¿Puedes ver cómo funciona el programa en este punto? Las dos primeras líneas asignan los valores iniciales a dos ubicaciones de memoria: <code>total</code> se utilizará para construir el resultado de la suma, y <code>contador</code> llevará la cuenta del número que estamos observando en ese momento. Las líneas que utilizan <code>comparación</code> probablemente sean las más confusas. El programa quiere ver si <code>contador</code> 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 <code>comparación</code> para calcular el valor de <code>contador - 11</code> y tomar una decisión basada en el resultado. Las siguientes dos líneas suman el valor de <code>contador</code> al resultado e incrementan <code>contador</code> en 1 cada vez que el programa decide que <code>contador</code> aún no vale 11. Aquí está el mismo programa en JavaScript:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-KfhVPRGaZ0" href="#c-KfhVPRGaZ0" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">total</span> = <span class="tok-number">0</span>, <span class="tok-definition">count</span> = <span class="tok-number">1</span>; -<span class="tok-keyword">while</span> (count <= <span class="tok-number">10</span>) { - total += count; - count += <span class="tok-number">1</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-iM8PtaIM37" href="#c-iM8PtaIM37" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">total</span> = <span class="tok-number">0</span>, <span class="tok-definition">contador</span> = <span class="tok-number">1</span>; +<span class="tok-keyword">while</span> (contador <= <span class="tok-number">10</span>) { + total += contador; + contador += <span class="tok-number">1</span>; } console.log(total); <span class="tok-comment">// → 55</span></pre> -<p><a class="p_ident" id="p-ysxaRKJkF8" href="#p-ysxaRKJkF8" tabindex="-1" role="presentation"></a>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 <code>while</code> 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 <code>count <= 10</code>, 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.</p> +<p><a class="p_ident" id="p-UcYthLYDcQ" href="#p-UcYthLYDcQ" tabindex="-1" role="presentation"></a>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 <code>while</code> 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 <code>contador <= 10</code>, 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.</p> -<p><a class="p_ident" id="p-ScjldEdVpL" href="#p-ScjldEdVpL" tabindex="-1" role="presentation"></a>Al final del programa, después de que la construcción <code>while</code> haya terminado, se utiliza la operación <code>console.log</code> para escribir el resultado.</p> +<p><a class="p_ident" id="p-vx/hH46gXD" href="#p-vx/hH46gXD" tabindex="-1" role="presentation"></a>Al final del programa, después de que la construcción <code>while</code> haya terminado, se utiliza la operación <code>console.log</code> para mostrar el resultado.</p> -<p><a class="p_ident" id="p-3Rf3RperI1" href="#p-3Rf3RperI1" tabindex="-1" role="presentation"></a>Finalmente, así es como podría verse el programa si tuviéramos a nuestra disposición las operaciones convenientes <code>rango</code> y <code>suma</code>, que respectivamente crean una colección de números dentro de un rango y calculan la suma de una colección de números:</p> +<p><a class="p_ident" id="p-uAJ2bxUUHb" href="#p-uAJ2bxUUHb" tabindex="-1" role="presentation"></a>Finalmente, así es como podría verse el programa si tuviéramos a nuestra disposición las útiles operaciones <code>rango</code> y <code>suma</code>, 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:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-zRop5p/qNC" href="#c-zRop5p/qNC" tabindex="-1" role="presentation"></a>console.log(suma(rango(<span class="tok-number">1</span>, <span class="tok-number">10</span>))); <span class="tok-comment">// → 55</span></pre> -<p><a class="p_ident" id="p-driPI8xmvd" href="#p-driPI8xmvd" tabindex="-1" role="presentation"></a>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 (<code>log</code>) la <code>suma</code> del <code>rango</code> de números del 1 al 10. (Veremos en <a href="04_data.html">capítulos posteriores</a> cómo definir operaciones como <code>suma</code> y <code>rango</code>.)</p> +<p><a class="p_ident" id="p-driPI8xmvd" href="#p-driPI8xmvd" tabindex="-1" role="presentation"></a>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 (<code>log</code>) la <code>suma</code> del <code>rango</code> de números del 1 al 10 (veremos en <a href="04_data.html">capítulos posteriores</a> cómo definir operaciones como <code>suma</code> y <code>rango</code>).</p> -<p><a class="p_ident" id="p-QB3ntHn9ea" href="#p-QB3ntHn9ea" tabindex="-1" role="presentation"></a>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 <code>while</code> y <code>console.log</code>), te permite definir tus propios bloques de construcción (como <code>suma</code> y <code>rango</code>), y hace que esos bloques sean fáciles de componer.</p> +<p><a class="p_ident" id="p-QB3ntHn9ea" href="#p-QB3ntHn9ea" tabindex="-1" role="presentation"></a>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 <code>while</code> y <code>console.log</code>), te permite definir tus propios bloques de construcción (como <code>suma</code> y <code>rango</code>), y hace que esos bloques sean fáciles de componer.</p> <h2><a class="h_ident" id="h-ZSPWomsNoS" href="#h-ZSPWomsNoS" tabindex="-1" role="presentation"></a>¿Qué es JavaScript?</h2> -<p><a class="p_ident" id="p-m9cW2SSLiU" href="#p-m9cW2SSLiU" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-m9cW2SSLiU" href="#p-m9cW2SSLiU" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-auhUpsPEpN" href="#p-auhUpsPEpN" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-I9EVupky+n" href="#p-I9EVupky+n" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-Hq9j2uTVYN" href="#p-Hq9j2uTVYN" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-Hq9j2uTVYN" href="#p-Hq9j2uTVYN" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-NlqGr+U7g9" href="#p-NlqGr+U7g9" tabindex="-1" role="presentation"></a>Hay quienes dirán cosas <em>terribles</em> 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á.</p> +<p><a class="p_ident" id="p-MP5P8l36it" href="#p-MP5P8l36it" tabindex="-1" role="presentation"></a>Hay quienes dirán cosas <em>terribles</em> 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.</p> <p><a class="p_ident" id="p-dQ3cLeMn/a" href="#p-dQ3cLeMn/a" tabindex="-1" role="presentation"></a>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 <em>gustarme</em> JavaScript.</p> -<p><a class="p_ident" id="p-ueMoUbQJQR" href="#p-ueMoUbQJQR" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-ueMoUbQJQR" href="#p-ueMoUbQJQR" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-V3olDHU8MM" href="#p-V3olDHU8MM" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-V3olDHU8MM" href="#p-V3olDHU8MM" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-nYkQYnLtNA" href="#p-nYkQYnLtNA" tabindex="-1" role="presentation"></a>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 <a href="20_node.html">Capítulo 20</a>), proporcionan un entorno para programar en JavaScript fuera del navegador.</p> -<h2><a class="h_ident" id="h-zzimxwEZTP" href="#h-zzimxwEZTP" tabindex="-1" role="presentation"></a>Código y qué hacer con él</h2> +<h2><a class="h_ident" id="h-+1rKt3uvSY" href="#h-+1rKt3uvSY" tabindex="-1" role="presentation"></a>Código, y qué hacer con él</h2> -<p><a class="p_ident" id="p-IcWVXRKHWW" href="#p-IcWVXRKHWW" tabindex="-1" role="presentation"></a>El <em>código</em> 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.</p> +<p><a class="p_ident" id="p-XNiDnL54xH" href="#p-XNiDnL54xH" tabindex="-1" role="presentation"></a>El <em>código</em> 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.</p> <p><a class="p_ident" id="p-br4nSZqakS" href="#p-br4nSZqakS" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-UU46Kz9M/v" href="#p-UU46Kz9M/v" tabindex="-1" role="presentation"></a>Cuando leas este libro en tu navegador, puedes editar (y ejecutar) todos los programas de ejemplo haciendo clic en ellos.</p> -<p><a class="p_ident" id="p-xvB5KPA8vp" href="#p-xvB5KPA8vp" tabindex="-1" role="presentation"></a>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 <a href="https://eloquentjavascript.net/code">sandbox</a> 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.</p> +<p><a class="p_ident" id="p-xvB5KPA8vp" href="#p-xvB5KPA8vp" tabindex="-1" role="presentation"></a>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 <a href="https://eloquentjavascript.net/code">sandbox</a> 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.</p> <h2><a class="h_ident" id="h-vMfJEvmTxP" href="#h-vMfJEvmTxP" tabindex="-1" role="presentation"></a>Visión general de este libro</h2> -<p><a class="p_ident" id="p-7Kp/fwb17I" href="#p-7Kp/fwb17I" tabindex="-1" role="presentation"></a>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 <em>capítulos de proyectos</em> en el libro que describen programas de ejemplo más grandes para darte una idea de la programación real.</p> +<p><a class="p_ident" id="p-KjnJN9aVae" href="#p-KjnJN9aVae" tabindex="-1" role="presentation"></a>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 <em>capítulos de proyectos</em> en el libro que describen programas de ejemplo más grandes para darte una idea de programación de verdad.</p> -<p><a class="p_ident" id="p-dmkxdCw6TE" href="#p-dmkxdCw6TE" tabindex="-1" role="presentation"></a>La parte del lenguaje del libro comienza con cuatro capítulos que introducen la estructura básica del lenguaje JavaScript. Discuten las <a href="02_program_structure.html">estructuras de control</a> (como la palabra <code>while</code> que viste en esta introducción), las <a href="03_functions.html">funciones</a> (escribir tus propios bloques de construcción) y las <a href="04_data.html">estructuras de datos</a>. Después de estos, serás capaz de escribir programas básicos. Luego, los Capítulos <a href="05_higher_order.html">5</a> y <a href="06_object.html">6</a> introducen técnicas para usar funciones y objetos para escribir código más <em>abstracto</em> y mantener la complejidad bajo control. Después de un <a href="07_robot.html">primer capítulo del proyecto</a> que construye un robot de entrega rudimentario, la parte del lenguaje del libro continúa con capítulos sobre <a href="08_error.html">manejo de errores y corrección de errores</a>, <a href="09_regexp.html">expresiones regulares</a> (una herramienta importante para trabajar con texto), <a href="10_modules.html">modularidad</a> (otra defensa contra la complejidad) y <a href="11_async.html">programación asíncrona</a> (tratando con eventos que toman tiempo). El <a href="12_language.html">segundo capítulo del proyecto</a>, donde implementamos un lenguaje de programación, concluye la primera parte del libro.</p> +<p><a class="p_ident" id="p-eAsMYrL/Ey" href="#p-eAsMYrL/Ey" tabindex="-1" role="presentation"></a>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 <a href="02_program_structure.html">estructuras de control</a> (como la palabra <code>while</code> que viste en esta introducción), las <a href="03_functions.html">funciones</a> (escribir tus propios bloques de construcción) y las <a href="04_data.html">estructuras de datos</a>. Después de estos, serás capaz de escribir programas básicos. Luego, los Capítulos <a href="05_higher_order.html">5</a> y <a href="06_object.html">6</a> introducen técnicas para usar funciones y objetos para escribir código más <em>abstracto</em> y mantener la complejidad bajo control.</p> -<p><a class="p_ident" id="p-OcJlTB3xRl" href="#p-OcJlTB3xRl" tabindex="-1" role="presentation"></a>La segunda parte del libro, de los capítulos <a href="13_browser.html">13</a> a <a href="19_paint.html">19</a>, describe las herramientas a las que tiene acceso JavaScript en un navegador. Aprenderás a mostrar cosas en la pantalla (Capítulos <a href="14_dom.html">14</a> y <a href="17_canvas.html">17</a>), responder a la entrada del usuario (<a href="15_event.html">Capítulo 15</a>) y comunicarte a través de la red (<a href="18_http.html">Capítulo 18</a>). Nuevamente hay dos capítulos de proyecto en esta parte, construyendo un <a href="16_game.html">juego de plataformas</a> y un <a href="19_paint.html">programa de pintura de píxeles</a>.</p> +<p><a class="p_ident" id="p-T2zLxztShc" href="#p-T2zLxztShc" tabindex="-1" role="presentation"></a>Después de un <a href="07_robot.html">primer capítulo de proyecto</a> en el que se construye un robot de entrega rudimentario, la parte del libro sobre lenguaje continúa con capítulos acerca de <a href="08_error.html">manejo de errores y corrección de errores</a>, <a href="09_regexp.html">expresiones regulares</a> (una herramienta importante para trabajar con texto), <a href="10_modules.html">modularidad</a> (otra defensa contra la complejidad) y <a href="11_async.html">programación asíncrona</a> (tratando con eventos que llevan tiempo). El <a href="12_language.html">segundo capítulo de proyecto</a>, donde implementamos un lenguaje de programación, cierra la primera parte del libro.</p> -<p><a class="p_ident" id="p-PNQ+76pXRg" href="#p-PNQ+76pXRg" tabindex="-1" role="presentation"></a>El <a href="20_node.html">Capítulo 20</a> describe Node.js, y el <a href="21_skillsharing.html">Capítulo 21</a> construye un pequeño sitio web utilizando esa herramienta.</p> +<p><a class="p_ident" id="p-vwensKLodv" href="#p-vwensKLodv" tabindex="-1" role="presentation"></a>La segunda parte del libro, de los capítulos <a href="13_browser.html">13</a> a <a href="19_paint.html">19</a>, describe las herramientas a las que tiene acceso JavaScript en un navegador. Aprenderás a mostrar cosas en la pantalla (Capítulos <a href="14_dom.html">14</a> y <a href="17_canvas.html">17</a>), responder a la entrada del usuario (<a href="15_event.html">Capítulo 15</a>) y comunicarte a través de la red (<a href="18_http.html">Capítulo 18</a>). Nuevamente hay dos capítulos de proyecto en esta parte que consisten construir un <a href="16_game.html">juego de plataformas</a> y un <a href="19_paint.html">programa de pintura de píxeles</a>, respectivamente.</p> + +<p><a class="p_ident" id="p-oZGYqIDtj/" href="#p-oZGYqIDtj/" tabindex="-1" role="presentation"></a>En el <a href="20_node.html">Capítulo 20</a> se describe Node.js, y en el <a href="21_skillsharing.html">Capítulo 21</a> se construye un pequeño sitio web utilizando esta herramienta.</p> <h2><a class="h_ident" id="h-ai8p4G2a5R" href="#h-ai8p4G2a5R" tabindex="-1" role="presentation"></a>Convenciones tipográficas</h2> -<p><a class="p_ident" id="p-E6jge1oF7L" href="#p-E6jge1oF7L" tabindex="-1" role="presentation"></a>En este libro, el texto escrito en una fuente <code>monoespaciada</code> 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:</p> +<p><a class="p_ident" id="p-E6jge1oF7L" href="#p-E6jge1oF7L" tabindex="-1" role="presentation"></a>En este libro, el texto escrito en una fuente <code>monoespaciada</code> 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:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5GjN2pXyt/" href="#c-5GjN2pXyt/" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">factorial</span>(<span class="tok-definition">n</span>) { <span class="tok-keyword">if</span> (n == <span class="tok-number">0</span>) { @@ -205,7 +207,7 @@ <h2><a class="h_ident" id="h-ai8p4G2a5R" href="#h-ai8p4G2a5R" tabindex="-1" role } }</pre> -<p><a class="p_ident" id="p-ktoGPoTwVt" href="#p-ktoGPoTwVt" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-vI4Y6WgFOO" href="#p-vI4Y6WgFOO" tabindex="-1" role="presentation"></a>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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-jUF93Xlrf8" href="#c-jUF93Xlrf8" tabindex="-1" role="presentation"></a>console.log(factorial(<span class="tok-number">8</span>)); <span class="tok-comment">// → 40320</span></pre> diff --git a/html/01_values.html b/html/01_values.html index 56c1e7db..206a3827 100644 --- a/html/01_values.html +++ b/html/01_values.html @@ -14,17 +14,17 @@ <h1>Valores, Tipos y Operadores</h1> <blockquote> -<p><a class="p_ident" id="p-bpaGsABGGJ" href="#p-bpaGsABGGJ" tabindex="-1" role="presentation"></a>Debajo de la superficie de la máquina, el programa se mueve. Sin esfuerzo, se expande y contrae. En gran armonía, los electrones se dispersan y se reagrupan. Las formas en el monitor no son más que ondas en el agua. La esencia permanece invisible debajo.</p> +<p><a class="p_ident" id="p-ILDglix78z" href="#p-ILDglix78z" tabindex="-1" role="presentation"></a>Bajo de la superficie de la máquina, el programa se mueve. Sin esfuerzo, se expande y contrae. En gran armonía, los electrones se dispersan y reagrupan. Las formas en el monitor no son sino ondas en el agua. La esencia permanece invisible debajo.</p> -<footer>Master Yuan-Ma, <cite>El Libro de la Programación</cite></footer> +<footer>Master Yuan-Ma, <cite>The Book of Programming</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_1.jpg" alt="Una foto de un mar de bits"></figure> -<p><a class="p_ident" id="p-5q3V1d0pX9" href="#p-5q3V1d0pX9" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-8y5+SFCMLf" href="#p-8y5+SFCMLf" tabindex="-1" role="presentation"></a>En el mundo de la computadora, solo existe la información. Puedes leer datos, modificar datos, crear nuevos datos, pero no se puede hablar de nada que no sean datos. Todos estos datos se almacenan como largas secuencias de bits, y, por tanto, en el fondo, son todos iguales.</p> -<p><a class="p_ident" id="p-ufYtysiybY" href="#p-ufYtysiybY" tabindex="-1" role="presentation"></a><em>Los bits</em> 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.</p> +<p><a class="p_ident" id="p-9HVAw+hhzG" href="#p-9HVAw+hhzG" tabindex="-1" role="presentation"></a>Llamamos <em>bits</em> a cualquier tipo de cosa con dos posibles valores, y generalmente los describimos usando ceros y unos. Dentro de la computadora, aparecen en forma de 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.</p> -<p><a class="p_ident" id="p-e7UmOUc3PL" href="#p-e7UmOUc3PL" tabindex="-1" role="presentation"></a>Por ejemplo, podemos expresar el número 13 en bits. Esto funciona de la misma manera que un número decimal, pero en lugar de diez dígitos diferentes, tenemos solo 2, y el peso de cada uno aumenta por un factor de 2 de derecha a izquierda. Aquí están los bits que componen el número 13, con los pesos de los dígitos mostrados debajo de ellos:</p> +<p><a class="p_ident" id="p-e7UmOUc3PL" href="#p-e7UmOUc3PL" tabindex="-1" role="presentation"></a>Por ejemplo, podemos expresar el número 13 en bits de la misma manera que lo hacemos como número decimal, pero en lugar de utilizando diez dígitos diferentes, usando solo 2, aumentando el peso de cada dígito de la representación por un factor de 2 de derecha a izquierda. Aquí están los bits que componen el número 13, con los pesos de los dígitos mostrados debajo de ellos:</p> <pre class="snippet" data-language="null" ><a class="c_ident" id="c-+fMMNc3yUt" href="#c-+fMMNc3yUt" tabindex="-1" role="presentation"></a> 0 0 0 0 1 1 0 1 128 64 32 16 8 4 2 1</pre> @@ -33,11 +33,13 @@ <h1>Valores, Tipos y Operadores</h1> <h2><a class="h_ident" id="h-U9F8ZU147i" href="#h-U9F8ZU147i" tabindex="-1" role="presentation"></a>Valores</h2> -<p><a class="p_ident" id="p-ueMEhoC62i" href="#p-ueMEhoC62i" tabindex="-1" role="presentation"></a>Imagina una mar de bits—un océano de ellos. Una computadora moderna típica tiene más de 100 mil millones de bits en su almacenamiento de datos volátil (memoria de trabajo). El almacenamiento no volátil (el disco duro o equivalente) tiende a tener aún unos cuantos órdenes de magnitud más.</p> +<p><a class="p_ident" id="p-ueMEhoC62i" href="#p-ueMEhoC62i" tabindex="-1" role="presentation"></a>Imagina una mar de bits—un océano de ellos. Una computadora corriente de hoy en día tiene más de 100 mil millones de bits en su almacenamiento de datos volátil (memoria de trabajo o memoria RAM). El almacenamiento no volátil (el disco duro o cualquier equivalente) tiende a tener aún unos cuantos órdenes de magnitud más.</p> -<p><a class="p_ident" id="p-f1AoXKYSbn" href="#p-f1AoXKYSbn" tabindex="-1" role="presentation"></a>Para poder trabajar con tales cantidades de bits sin perderse, los separamos en trozos que representan piezas de información. En un entorno de JavaScript, esos trozos se llaman <em>valores</em>. Aunque todos los valores están hechos de bits, desempeñan roles diferentes. Cada valor tiene un tipo que determina su función. Algunos valores son números, otros son fragmentos de texto, otros son funciones, y así sucesivamente.</p> +<p><a class="p_ident" id="p-UZXNHNgZfk" href="#p-UZXNHNgZfk" tabindex="-1" role="presentation"></a>Para poder trabajar con tales cantidades de bits sin perdernos, los separamos en trozos que representan piezas de información. En un entorno de JavaScript, esos trozos se llaman <em>valores</em>. Aunque todos los valores están hechos de bits, desempeñan roles diferentes. Cada valor tiene un tipo que determina su función. Algunos valores son números, otros son fragmentos de texto, otros son funciones, etc.</p> -<p><a class="p_ident" id="p-mJiLbj24GZ" href="#p-mJiLbj24GZ" tabindex="-1" role="presentation"></a>Para crear un valor, simplemente debes invocar su nombre. Esto es conveniente. No tienes que recolectar material de construcción para tus valores ni pagar por ellos. Solo solicitas uno, y ¡zas!, lo tienes. Por supuesto, los valores no se crean realmente de la nada. Cada uno tiene que almacenarse en algún lugar, y si deseas usar gigantescas cantidades de ellos al mismo tiempo, podrías quedarte sin memoria de computadora. Afortunadamente, este es un problema solo si los necesitas todos simultáneamente. Tan pronto como dejes de usar un valor, se disipará, dejando atrás sus bits para ser reciclados como material de construcción para la próxima generación de valores. El resto de este capítulo presenta los elementos atómicos de los programas de JavaScript, es decir, los tipos de valores simples y los operadores que pueden actuar sobre dichos valores.</p> +<p><a class="p_ident" id="p-Iamt82VWK9" href="#p-Iamt82VWK9" tabindex="-1" role="presentation"></a>Para crear un valor, basta con invocar su nombre. Esto es conveniente. No tienes que recolectar material de construcción para tus valores ni pagar por ellos. Solo solicitas uno, y ¡pum!, ahí está. Por supuesto, los valores no se crean realmente de la nada. Cada uno tiene que almacenarse en algún lugar, y, si deseas usar gigantescas cantidades de ellos al mismo tiempo, podrías quedarte sin memoria en la computadora. Por suerte, esto es un problema solo si los necesitas todos a la vez. Tan pronto como dejes de usar un valor, se disipará, dejando atrás sus bits para ser reciclados como material de construcción para la próxima generación de valores.</p> + +<p><a class="p_ident" id="p-1IM4xyT/lZ" href="#p-1IM4xyT/lZ" tabindex="-1" role="presentation"></a>El resto de este capítulo presenta los elementos atómicos de los programas de JavaScript, es decir, los tipos de valores simples y los operadores que pueden actuar sobre dichos valores.</p> <h2><a class="h_ident" id="h-2eDSr4FTTr" href="#h-2eDSr4FTTr" tabindex="-1" role="presentation"></a>Números</h2> @@ -47,33 +49,33 @@ <h2><a class="h_ident" id="h-2eDSr4FTTr" href="#h-2eDSr4FTTr" tabindex="-1" role <p><a class="p_ident" id="p-2b0WdUMWiY" href="#p-2b0WdUMWiY" tabindex="-1" role="presentation"></a>Usar esto en un programa hará que el patrón de bits para el número 13 exista en la memoria del ordenador.</p> -<p><a class="p_ident" id="p-iNSri5YZBd" href="#p-iNSri5YZBd" tabindex="-1" role="presentation"></a>JavaScript utiliza un número fijo de bits, 64 de ellos, para almacenar un único valor numérico. Hay un número limitado de patrones que puedes hacer con 64 bits, lo que limita la cantidad de números diferentes que se pueden representar. Con <em>N</em> dígitos decimales, puedes representar 10<sup>N</sup> números. De manera similar, dada una cifra de 64 dígitos binarios, puedes representar 2<sup>64</sup> números diferentes, que son alrededor de 18 mil trillones (un 18 seguido de 18 ceros). Eso es mucho.</p> +<p><a class="p_ident" id="p-HXtKQ7s/e2" href="#p-HXtKQ7s/e2" tabindex="-1" role="presentation"></a>JavaScript utiliza una cantidad fija de bits, 64 de ellos, para almacenar cada valor numérico. Hay una cantidad limitada de patrones que puedes formar con 64 bits, lo que limita la cantidad de números diferentes que se pueden representar. Con <em>N</em> dígitos decimales, puedes representar 10<sup>N</sup> números distintos. De manera similar, utilizando 64 dígitos binarios, puedes representar 2<sup>64</sup> números diferentes, que son alrededor de 18 mil trillones (un 18 seguido de 18 ceros). Eso es un montón.</p> -<p><a class="p_ident" id="p-zoQfMGsjpS" href="#p-zoQfMGsjpS" tabindex="-1" role="presentation"></a>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 <em>desbordamiento</em> 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.</p> +<p><a class="p_ident" id="p-rvmt60g/XE" href="#p-rvmt60g/XE" tabindex="-1" role="presentation"></a>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 acabar por error con un <em>desbordamiento</em> (comúnmente conocido como <em>overflow</em>) con un conjunto posible de números tan pequeño —es decir, terminar con un número que no puede ser representado con la cantidad dada de bits. Hoy en día, incluso un dispositivo que cabe en tu bolsillo tiene mucha memoria, por lo que puedes utilizar trozos de 64 bits para representar números, y solo necesitas preocuparte por el desbordamiento cuando pretendes representar cantidades realmente astronómicas.</p> -<p><a class="p_ident" id="p-g9ZdolZbnN" href="#p-g9ZdolZbnN" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-3B3trY1G3F" href="#p-3B3trY1G3F" tabindex="-1" role="presentation"></a>En cualquier caso, no todos los números naturales menores que 18 mil trillones caben en un número de JavaScript. Esos bits también almacenan números negativos, por lo que un bit se usa para indicar el signo del número. Y aún más complicado 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ás grande que en realidad se puede almacenar está más en el rango de los 9 mil billones (15 ceros), que sigue siendo increíblemente grande.</p> -<p><a class="p_ident" id="p-QuaxCY0XYg" href="#p-QuaxCY0XYg" tabindex="-1" role="presentation"></a>Los números fraccionarios se escriben usando un punto:</p> +<p><a class="p_ident" id="p-A5C6OiK5VV" href="#p-A5C6OiK5VV" tabindex="-1" role="presentation"></a>Para representar números con parte decimal se utiliza un punto:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-tM8nqv41Gp" href="#c-tM8nqv41Gp" tabindex="-1" role="presentation"></a><span class="tok-number">9.81</span></pre> -<p><a class="p_ident" id="p-swGkw2BgKN" href="#p-swGkw2BgKN" tabindex="-1" role="presentation"></a>Para números muy grandes o muy pequeños, también puedes usar notación científica agregando una <em>e</em> (de <em>exponente</em>), seguida del exponente del número:</p> +<p><a class="p_ident" id="p-swGkw2BgKN" href="#p-swGkw2BgKN" tabindex="-1" role="presentation"></a>Para números muy grandes o muy pequeños, también se puede usar notación científica agregando una <em>e</em> (de <em>exponente</em>), seguida del exponente del número:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-6ew5w+VhSM" href="#c-6ew5w+VhSM" tabindex="-1" role="presentation"></a><span class="tok-number">2.998e8</span></pre> -<p><a class="p_ident" id="p-vx7ox7vlBw" href="#p-vx7ox7vlBw" tabindex="-1" role="presentation"></a>Eso es 2.998 × 10<sup>8</sup> = 299,800,000.</p> +<p><a class="p_ident" id="p-jwQzz0n15f" href="#p-jwQzz0n15f" tabindex="-1" role="presentation"></a>Esto representaría al número 2.998 × 10<sup>8</sup> = 299,800,000.</p> -<p><a class="p_ident" id="p-At4IfW0/+5" href="#p-At4IfW0/+5" tabindex="-1" role="presentation"></a>Los cálculos con números enteros (también llamados <em>enteros</em>) 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.</p> +<p><a class="p_ident" id="p-HfKizr1nUC" href="#p-HfKizr1nUC" tabindex="-1" role="presentation"></a>La precisión de cualquier cálculo con números <em>enteros</em> con valor absoluto menor a los 9 mil billones antes mencionados está siempre garantizada. Por desgracia, la de los cálculos con números no enteros generalmente no está. Así como π (pi) no puede expresarse con total precisión mediante un número finito de dígitos decimales, muchos números pierden algo de precisión en su representación cuando solo tenemos 64 bits para almacenarlos. Es una lástima, pero solo provoca problemas prácticos en situaciones específicas. Lo importante tenerlo en cuenta y tratar los números digitales con parte decimal como aproximaciones, no como valores precisos.</p> <h3><a class="i_ident" id="i-aIf95z3S0d" href="#i-aIf95z3S0d" tabindex="-1" role="presentation"></a>Aritmética</h3> -<p><a class="p_ident" id="p-fEl5oInJjs" href="#p-fEl5oInJjs" tabindex="-1" role="presentation"></a>Lo principal que se puede hacer con los números es la aritmética. Operaciones aritméticas como la suma o la multiplicación toman dos valores numéricos y producen un nuevo número a partir de ellos. Así es como se ven en JavaScript:</p> +<p><a class="p_ident" id="p-DGirvf5riQ" href="#p-DGirvf5riQ" tabindex="-1" role="presentation"></a>Con números, lo principal que uno puede hacer es aritmética. Operaciones aritméticas como la suma o la multiplicación toman dos valores numéricos y producen un nuevo número a partir de ellos. Esta es la pinta que tienen en JavaScript:</p> <pre tabindex="0" class="snippet" data-language="javascript" data-meta="expr"><a class="c_ident" id="c-bSU4Vtv/mt" href="#c-bSU4Vtv/mt" tabindex="-1" role="presentation"></a><span class="tok-number">100</span> + <span class="tok-number">4</span> * <span class="tok-number">11</span></pre> <p><a class="p_ident" id="p-PNPKgKWrHK" href="#p-PNPKgKWrHK" tabindex="-1" role="presentation"></a>Los símbolos <code>+</code> y <code>*</code> se llaman <em>operadores</em>. 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.</p> -<p><a class="p_ident" id="p-rbQOL3GnYq" href="#p-rbQOL3GnYq" tabindex="-1" role="presentation"></a>¿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:</p> +<p><a class="p_ident" id="p-rbQOL3GnYq" href="#p-rbQOL3GnYq" tabindex="-1" role="presentation"></a>¿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, esto se puede cambiar escribiendo la suma entre paréntesis:</p> <pre tabindex="0" class="snippet" data-language="javascript" data-meta="expr"><a class="c_ident" id="c-ij6V90ZZBQ" href="#c-ij6V90ZZBQ" tabindex="-1" role="presentation"></a>(<span class="tok-number">100</span> + <span class="tok-number">4</span>) * <span class="tok-number">11</span></pre> @@ -83,27 +85,27 @@ <h3><a class="i_ident" id="i-aIf95z3S0d" href="#i-aIf95z3S0d" tabindex="-1" role <p><a class="p_ident" id="p-icq/wIw6Tp" href="#p-icq/wIw6Tp" tabindex="-1" role="presentation"></a>No te preocupes demasiado por estas reglas de precedencia. Cuando tengas dudas, simplemente agrega paréntesis.</p> -<p><a class="p_ident" id="p-6mDQ8afNaM" href="#p-6mDQ8afNaM" tabindex="-1" role="presentation"></a>Hay un operador aritmético más, que quizás no reconozcas de inmediato. El símbolo <code>%</code> se utiliza para representar la operación de <em>residuo</em>. <code>X % Y</code> es el residuo de dividir <code>X</code> por <code>Y</code>. Por ejemplo, <code>314 % 100</code> produce <code>14</code>, y <code>144 % 12</code> da <code>0</code>. La precedencia del operador de residuo es la misma que la de multiplicación y división. También verás a menudo a este operador referido como <em>módulo</em>.</p> +<p><a class="p_ident" id="p-6mDQ8afNaM" href="#p-6mDQ8afNaM" tabindex="-1" role="presentation"></a>Hay un operador aritmético más que quizás no te suene tanto. El símbolo <code>%</code> se utiliza para representar la operación de <em>resto</em>. <code>X % Y</code> es el resto de la división entera de <code>X</code> entre <code>Y</code>. Por ejemplo, <code>314 % 100</code> produce <code>14</code>, y <code>144 % 12</code> da <code>0</code>. La precedencia del operador de resto es la misma que la de multiplicación y división. También verás a menudo a este operador referido como <em>módulo</em>.</p> <h3><a class="i_ident" id="i-zFIgofBZed" href="#i-zFIgofBZed" tabindex="-1" role="presentation"></a>Números especiales</h3> -<p><a class="p_ident" id="p-Yzdmqjc9np" href="#p-Yzdmqjc9np" tabindex="-1" role="presentation"></a>Hay tres valores especiales en JavaScript que se consideran números pero no se comportan como números normales. Los dos primeros son <code>Infinity</code> y <code>-Infinity</code>, que representan el infinito positivo y negativo. <code>Infinity - 1</code> sigue siendo <code>Infinity</code>, 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: <code>NaN</code>.</p> +<p><a class="p_ident" id="p-Yzdmqjc9np" href="#p-Yzdmqjc9np" tabindex="-1" role="presentation"></a>Hay tres valores especiales en JavaScript que se consideran números pero no se comportan como números normales. Los dos primeros son <code>Infinity</code> y <code>-Infinity</code>, que representan el infinito positivo y negativo. <code>Infinity - 1</code> sigue siendo <code>Infinity</code>, etc. De todos modos, no te fíes demasiado de las cuentas que involucren al infinito. No suele tener sentido matemáticamente y rápidamente te llevará a encontrarte con el siguiente número especial: <code>NaN</code>.</p> -<p><a class="p_ident" id="p-FTEAETPV0R" href="#p-FTEAETPV0R" tabindex="-1" role="presentation"></a><code>NaN</code> significa “no es un número”, aunque <em>es</em> un valor del tipo numérico. Obtendrás este resultado cuando, por ejemplo, intentes calcular <code>0 / 0</code> (cero dividido por cero), <code>Infinity - Infinity</code>, u cualquier otra operación numérica que no produzca un resultado significativo.</p> +<p><a class="p_ident" id="p-O0KRWbmkvj" href="#p-O0KRWbmkvj" tabindex="-1" role="presentation"></a><code>NaN</code> significa “no es un número” (en inglés, <em>not a number</em>), aunque <em>es</em> un valor del tipo <em>number</em>. Obtendrás este resultado cuando, por ejemplo, intentes calcular <code>0 / 0</code> (cero dividido por cero), <code>Infinity - Infinity</code>, o cualquier otra cuanta que no tenga un sentido determinado.</p> <h2><a class="h_ident" id="h-J4o1MED4vw" href="#h-J4o1MED4vw" tabindex="-1" role="presentation"></a>Cadenas</h2> -<p><a class="p_ident" id="p-LSngdQYH80" href="#p-LSngdQYH80" tabindex="-1" role="presentation"></a>El siguiente tipo de dato básico es la <em>cadena</em>. Las cadenas se utilizan para representar texto. Se escriben encerrando su contenido entre comillas.</p> +<p><a class="p_ident" id="p-LSngdQYH80" href="#p-LSngdQYH80" tabindex="-1" role="presentation"></a>El siguiente tipo de dato básico es la <em>cadena</em> (en inglés, <em>string</em>). Las cadenas se utilizan para representar texto. Se escriben encerrando su contenido entre comillas.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-qjwe3Dgr5R" href="#c-qjwe3Dgr5R" tabindex="-1" role="presentation"></a><span class="tok-string2">`En el mar`</span> <span class="tok-string">"Acostado en el océano"</span> <span class="tok-string">'Flotando en el océano'</span></pre> -<p><a class="p_ident" id="p-IHr2y937ks" href="#p-IHr2y937ks" tabindex="-1" role="presentation"></a>Puedes usar comillas simples, comillas dobles o acentos graves para marcar las cadenas, siempre y cuando las comillas al principio y al final de la cadena coincidan.</p> +<p><a class="p_ident" id="p-IHr2y937ks" href="#p-IHr2y937ks" tabindex="-1" role="presentation"></a>Puedes usar comillas simples, comillas dobles o acentos graves para marcar las cadenas, siempre y cuando el tipo de comillas al principio y al final de la cadena coincidan.</p> -<p><a class="p_ident" id="p-FbJ7LdbtTG" href="#p-FbJ7LdbtTG" tabindex="-1" role="presentation"></a>Puedes poner casi cualquier cosa entre comillas para que JavaScript genere un valor de cadena a partir de ello. Pero algunos caracteres son más difíciles. Puedes imaginar lo complicado que sería poner comillas entre comillas, ya que parecerían el final de la cadena. <em>Saltos de línea</em> (los caracteres que obtienes al presionar <span class="keyname">enter</span>) solo se pueden incluir cuando la cadena está entre acentos graves (<code>`</code>).</p> +<p><a class="p_ident" id="p-FbJ7LdbtTG" href="#p-FbJ7LdbtTG" tabindex="-1" role="presentation"></a>Puedes poner casi cualquier cosa entre comillas para que JavaScript genere un valor de tipo cadena a partir de ello. Pero algunos caracteres son más difíciles. Ya te puedes imaginar lo complicado que sería poner comillas entre comillas, ya que parecerían el final de la cadena. <em>Saltos de línea</em> (los caracteres que obtienes cuando le das al <span class="keyname">enter</span>) solo se pueden incluir cuando la cadena está entre acentos graves (<code>`</code>).</p> -<p><a class="p_ident" id="p-EcZzTv5ywl" href="#p-EcZzTv5ywl" tabindex="-1" role="presentation"></a>Para poder incluir dichos caracteres en una cadena, se utiliza la siguiente notación: una barra invertida (<code>\</code>) dentro de un texto entre comillas indica que el carácter posterior tiene un significado especial. Esto se llama <em>escapar</em> el carácter. Una comilla que va precedida por una barra invertida no finalizará la cadena, sino que formará parte de ella. Cuando un carácter <code>n</code> aparece después de una barra invertida, se interpreta como un salto de línea. De manera similar, un <code>t</code> después de una barra invertida significa un carácter de tabulación. Toma la siguiente cadena:</p> +<p><a class="p_ident" id="p-EcZzTv5ywl" href="#p-EcZzTv5ywl" tabindex="-1" role="presentation"></a>Para poder incluir tales caracteres en una cadena, se utiliza la siguiente notación: una barra invertida (<code>\</code>) dentro de un texto entre comillas indica que el carácter posterior tiene un significado especial. A esto se le llama <em>escapar</em> el carácter. Una comilla que va precedida por una barra invertida no finalizará la cadena, sino que formará parte de ella. Cuando un carácter <code>n</code> aparece después de una barra invertida, se interpreta como un salto de línea. De manera similar, un <code>t</code> después de una barra invertida significa un carácter de tabulación. Consideremos la siguiente cadena:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-3CI2v7hMPn" href="#c-3CI2v7hMPn" tabindex="-1" role="presentation"></a><span class="tok-string">"Esta es la primera línea</span><span class="tok-string2">\n</span><span class="tok-string">Y esta es la segunda"</span></pre> @@ -112,45 +114,45 @@ <h2><a class="h_ident" id="h-J4o1MED4vw" href="#h-J4o1MED4vw" tabindex="-1" role <pre class="snippet" data-language="null" ><a class="c_ident" id="c-fV5QN7Aj+D" href="#c-fV5QN7Aj+D" tabindex="-1" role="presentation"></a>Esta es la primera línea Y esta es la segunda</pre> -<p><a class="p_ident" id="p-RWoLLBFRNt" href="#p-RWoLLBFRNt" tabindex="-1" role="presentation"></a>Por supuesto, hay situaciones en las que deseas que una barra invertida en una cadena sea simplemente una barra invertida, no un código especial. Si dos barras invertidas van seguidas, se colapsarán juntas y solo quedará una en el valor de cadena resultante. Así es como se puede expresar la cadena “<em>Un carácter de nueva línea se escribe como <code>"</code>\n<code>"</code>.</em>”:</p> +<p><a class="p_ident" id="p-RWoLLBFRNt" href="#p-RWoLLBFRNt" tabindex="-1" role="presentation"></a>Por supuesto, hay situaciones en las que te gustaría que una barra invertida en una cadena fuera simplemente una barra invertida, no un código especial. Si aparecen dos barras invertidas seguidas en una cadena, estas colapsarán en una en el valor de cadena resultante. Así es como se puede expresar la cadena “<em>Un carácter de salto de línea se escribe como <code>"</code>\n<code>"</code>.</em>”:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-vqlaw2tox2" href="#c-vqlaw2tox2" tabindex="-1" role="presentation"></a><span class="tok-string">"Un carácter de nueva línea se escribe como </span><span class="tok-string2">\"</span><span class="tok-string2">\\</span><span class="tok-string">n</span><span class="tok-string2">\"</span><span class="tok-string">."</span></pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-r8ADhqGojW" href="#c-r8ADhqGojW" tabindex="-1" role="presentation"></a><span class="tok-string">"Un carácter de salto de línea se escribe como </span><span class="tok-string2">\"</span><span class="tok-string2">\\</span><span class="tok-string">n</span><span class="tok-string2">\"</span><span class="tok-string">."</span></pre> -<p id="unicode"><a class="p_ident" id="p-mbzJaYAs/g" href="#p-mbzJaYAs/g" tabindex="-1" role="presentation"></a>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 <em>Unicode</em>. 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.</p> +<p id="unicode"><a class="p_ident" id="p-mbzJaYAs/g" href="#p-mbzJaYAs/g" tabindex="-1" role="presentation"></a>Las cadenas también hay que modelarlas como una serie de bits para que puedan existir dentro de la computadora. La forma en que JavaScript lo hace se basa en el estándar <em>Unicode</em>. Este estándar asigna un número a prácticamente cada carácter que puedas necesitar, incluidos los caracteres griegos, árabes, japoneses, armenios, etc. 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.</p> -<p><a class="p_ident" id="p-Ygjt4M8HFV" href="#p-Ygjt4M8HFV" tabindex="-1" role="presentation"></a>Sin embargo, hay una complicación: la representación de JavaScript utiliza 16 bits por elemento de cadena, lo que puede describir hasta 2<sup>16</sup> 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 <a href="05_higher_order.html#code_units">Capítulo 5</a>.</p> +<p><a class="p_ident" id="p-bmXKm5HFnp" href="#p-bmXKm5HFnp" tabindex="-1" role="presentation"></a>Pero hay una complicación: la representación de JavaScript utiliza 16 bits por cada elemento de tipo cadena, lo que puede describir hasta 2<sup>16</sup> caracteres diferentes. Sin embargo, Unicode define más caracteres (alrededor del doble que eso, de momento). Por lo tanto, algunos caracteres, como muchos emoji, ocupan dos “posiciones de caracteres” en las cadenas de JavaScript. Volveremos a esto en el <a href="05_higher_order.html#code_units">Capítulo 5</a>.</p> -<p><a class="p_ident" id="p-yVQTjTfX8O" href="#p-yVQTjTfX8O" tabindex="-1" role="presentation"></a>Las cadenas no se pueden dividir, multiplicar o restar. El operador <code>+</code> se puede usar en ellas, no para sumar, sino para <em>concatenar</em> —unir dos cadenas. La siguiente línea producirá la cadena <code>"concatenar"</code>:</p> +<p><a class="p_ident" id="p-yVQTjTfX8O" href="#p-yVQTjTfX8O" tabindex="-1" role="presentation"></a>Las cadenas no se pueden dividir, multiplicar o restar. El operador <code>+</code> se puede usar en ellas, no para sumar, sino para <em>concatenar</em> (unir dos cadenas). La siguiente línea producirá la cadena <code>"concatenar"</code>:</p> <pre tabindex="0" class="snippet" data-language="javascript" data-meta="expr"><a class="c_ident" id="c-AF1pg1pIWq" href="#c-AF1pg1pIWq" tabindex="-1" role="presentation"></a><span class="tok-string">"con"</span> + <span class="tok-string">"cat"</span> + <span class="tok-string">"e"</span> + <span class="tok-string">"nar"</span></pre> -<p><a class="p_ident" id="p-lOVOo+omcp" href="#p-lOVOo+omcp" tabindex="-1" role="presentation"></a>Los valores de cadena tienen una serie de funciones asociadas (<em>métodos</em>) que se pueden utilizar para realizar otras operaciones con ellos. Hablaré más sobre esto en el <a href="04_data.html#methods">Capítulo 4</a>.</p> +<p><a class="p_ident" id="p-lOVOo+omcp" href="#p-lOVOo+omcp" tabindex="-1" role="presentation"></a>Los valores de tipo cadena tienen una serie de funciones asociadas (<em>métodos</em>) que se pueden utilizar para realizar otras operaciones con ellos. Hablaré más sobre esto en el <a href="04_data.html#methods">Capítulo 4</a>.</p> <p><a class="p_ident" id="p-ihSs5peL7s" href="#p-ihSs5peL7s" tabindex="-1" role="presentation"></a>Las cadenas escritas con comillas simples o dobles se comportan de manera muy similar, la única diferencia radica en qué tipo de comilla necesitas escapar dentro de ellas. Las cadenas entre acentos graves, generalmente llamadas <em>template literals</em>, pueden hacer algunas cosas más. Aparte de poder abarcar varias líneas, también pueden incrustar otros valores.</p> <pre tabindex="0" class="snippet" data-language="javascript" data-meta="expr"><a class="c_ident" id="c-Q70paqTNYa" href="#c-Q70paqTNYa" tabindex="-1" role="presentation"></a><span class="tok-string2">`la mitad de 100 es </span>${<span class="tok-number">100</span> / <span class="tok-number">2</span>}<span class="tok-string2">`</span></pre> -<p><a class="p_ident" id="p-6tfJCNVaMB" href="#p-6tfJCNVaMB" tabindex="-1" role="presentation"></a>Cuando escribes algo dentro de <code>${}</code> en una plantilla literal, su resultado se calculará, se convertirá en una cadena y se incluirá en esa posición. Este ejemplo produce “<em>la mitad de 100 es 50</em>”.</p> +<p><a class="p_ident" id="p-6tfJCNVaMB" href="#p-6tfJCNVaMB" tabindex="-1" role="presentation"></a>Cuando escribes algo dentro de <code>${}</code> en un <em>template literal</em>, su resultado se calculará, se convertirá en una cadena, y se incluirá en esa posición. Este ejemplo produce “<em>la mitad de 100 es 50</em>”.</p> <h2><a class="h_ident" id="h-Ba/lVluC/D" href="#h-Ba/lVluC/D" tabindex="-1" role="presentation"></a>Operadores unarios</h2> -<p><a class="p_ident" id="p-301hsEim2E" href="#p-301hsEim2E" tabindex="-1" role="presentation"></a>No todos los operadores son símbolos. Algunos se escriben como palabras. Un ejemplo es el operador <code>typeof</code>, que produce un valor de cadena que indica el tipo del valor que le proporcionas.</p> +<p><a class="p_ident" id="p-301hsEim2E" href="#p-301hsEim2E" tabindex="-1" role="presentation"></a>No todos los operadores son símbolos. Algunos se representan con palabras. Un ejemplo es el operador <code>typeof</code>, que produce un valor de cadena que indica el tipo del valor que le proporcionas.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-iWT//VyY7j" href="#c-iWT//VyY7j" tabindex="-1" role="presentation"></a>console.log(<span class="tok-keyword">typeof</span> <span class="tok-number">4.5</span>) <span class="tok-comment">// → number</span> console.log(<span class="tok-keyword">typeof</span> <span class="tok-string">"x"</span>) <span class="tok-comment">// → string</span></pre> -<p id="console.log"><a class="p_ident" id="p-8bF6kfCQe4" href="#p-8bF6kfCQe4" tabindex="-1" role="presentation"></a>Utilizaremos <code>console.log</code> en ejemplos de código para indicar que queremos ver el resultado de evaluar algo. Más sobre eso en el <a href="02_program_structure.html">próximo capítulo</a>.</p> +<p id="console.log"><a class="p_ident" id="p-8bF6kfCQe4" href="#p-8bF6kfCQe4" tabindex="-1" role="presentation"></a>Utilizaremos <code>console.log</code> en ejemplos de código para indicar que queremos ver el resultado de evaluar algo (veremos más sobre esto en el <a href="02_program_structure.html">próximo capítulo</a>).</p> -<p><a class="p_ident" id="p-IOtfkjfLvQ" href="#p-IOtfkjfLvQ" tabindex="-1" role="presentation"></a>Los otros operadores mostrados hasta ahora en este capítulo operaron sobre dos valores, pero <code>typeof</code> toma solo uno. Los operadores que utilizan dos valores se llaman operadores <em>binarios</em>, mientras que aquellos que toman uno se llaman operadores <em>unarios</em>. El operador menos se puede usar tanto como un operador binario como un operador unario.</p> +<p><a class="p_ident" id="p-IOtfkjfLvQ" href="#p-IOtfkjfLvQ" tabindex="-1" role="presentation"></a>Los otros operadores mostrados hasta ahora en este capítulo operaron sobre dos valores, pero <code>typeof</code> toma solo uno. Los operadores que utilizan dos valores se llaman operadores <em>binarios</em>, mientras que aquellos que toman uno se llaman operadores <em>unarios</em>. El operador menos (<code>-</code>) se puede usar tanto como un operador binario como un operador unario.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-VpL89RFAPj" href="#c-VpL89RFAPj" tabindex="-1" role="presentation"></a>console.log(- (<span class="tok-number">10</span> - <span class="tok-number">2</span>)) <span class="tok-comment">// → -8</span></pre> <h2><a class="h_ident" id="h-hcL3SuiDyH" href="#h-hcL3SuiDyH" tabindex="-1" role="presentation"></a>Valores booleanos</h2> -<p><a class="p_ident" id="p-a0eGPQEgO5" href="#p-a0eGPQEgO5" tabindex="-1" role="presentation"></a>A menudo es útil tener un valor que distinga solo entre dos posibilidades, como “sí" y “no” o “encendido” y “apagado”. Para este propósito, JavaScript tiene un tipo <em>Booleano</em>, que tiene solo dos valores, true y false, escritos como esas palabras.</p> +<p><a class="p_ident" id="p-a0eGPQEgO5" href="#p-a0eGPQEgO5" tabindex="-1" role="presentation"></a>A menudo es útil tener un valor que distinga solo entre dos posibilidades, como “sí" y “no” o “encendido” y “apagado”. Para este propósito, JavaScript tiene un tipo <em>Booleano</em>, que tiene solo dos valores, true y false, y que se escriben tal cual como esas palabras.</p> <h3><a class="i_ident" id="i-j8pkHXp4wK" href="#i-j8pkHXp4wK" tabindex="-1" role="presentation"></a>Comparación</h3> @@ -161,14 +163,14 @@ <h3><a class="i_ident" id="i-j8pkHXp4wK" href="#i-j8pkHXp4wK" tabindex="-1" role console.log(<span class="tok-number">3</span> < <span class="tok-number">2</span>) <span class="tok-comment">// → false</span></pre> -<p><a class="p_ident" id="p-RXzYYl/NBh" href="#p-RXzYYl/NBh" tabindex="-1" role="presentation"></a>Los signos <code>></code> y <code><</code> son símbolos tradicionales para “es mayor que” y “es menor que”, respectivamente. Son operadores binarios. Aplicarlos da como resultado un valor booleano que indica si son verdaderos en este caso.</p> +<p><a class="p_ident" id="p-o9ffjM6Row" href="#p-o9ffjM6Row" tabindex="-1" role="presentation"></a>Los signos <code>></code> y <code><</code> son símbolos tradicionales para “es mayor que” y “es menor que”, respectivamente. Son operadores binarios. Aplicarlos da como resultado un valor booleano que indica si son verdaderos en el caso en cuestión.</p> <p><a class="p_ident" id="p-Ch4A0mWDGs" href="#p-Ch4A0mWDGs" tabindex="-1" role="presentation"></a>Las cadenas se pueden comparar de la misma manera:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Qud5plnVuV" href="#c-Qud5plnVuV" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"Aardvark"</span> < <span class="tok-string">"Zoroaster"</span>) <span class="tok-comment">// → true</span></pre> -<p><a class="p_ident" id="p-D+mD6K0/Wd" href="#p-D+mD6K0/Wd" tabindex="-1" role="presentation"></a>La forma en que se ordenan las cadenas es aproximadamente alfabética pero no es realmente lo que esperarías ver en un diccionario: las letras mayúsculas son siempre “menores” que las minúsculas, por lo que <code>"Z" < "a"</code>, y los caracteres no alfabéticos (!, -, y así sucesivamente) también se incluyen en la ordenación. Al comparar cadenas, JavaScript recorre los caracteres de izquierda a derecha, comparando los códigos Unicode uno por uno.</p> +<p><a class="p_ident" id="p-P6ToKNBzPW" href="#p-P6ToKNBzPW" tabindex="-1" role="presentation"></a>El orden entre casenas es más o menos el alfabético pero no exactamente como se hace en un diccionario: las letras mayúsculas son siempre “menores” que las minúsculas, conque <code>"Z" < "a"</code>; y los caracteres no alfabéticos (!, -, etc.) también se incluyen en la ordenación. Para comparar cadenas, lo que JavaScript hace es recorrer los caracteres de izquierda a derecha, comparando los códigos Unicode uno por uno.</p> <p><a class="p_ident" id="p-LWMB/exwEI" href="#p-LWMB/exwEI" tabindex="-1" role="presentation"></a>Otros operadores similares son <code>>=</code> (mayor o igual que), <code><=</code> (menor o igual que), <code>==</code> (igual a), y <code>!=</code> (no igual a).</p> @@ -177,7 +179,7 @@ <h3><a class="i_ident" id="i-j8pkHXp4wK" href="#i-j8pkHXp4wK" tabindex="-1" role console.log(<span class="tok-string">"Perla"</span> == <span class="tok-string">"Amatista"</span>) <span class="tok-comment">// → false</span></pre> -<p><a class="p_ident" id="p-qeF5rEgoVg" href="#p-qeF5rEgoVg" tabindex="-1" role="presentation"></a>Solo hay un valor en JavaScript que no es igual a sí mismo, y ese es <code>NaN</code> (“no es un número”).</p> +<p><a class="p_ident" id="p-Dhc9EDqGi3" href="#p-Dhc9EDqGi3" tabindex="-1" role="presentation"></a>Solo hay un valor en JavaScript que no es igual a sí mismo, y ese es <code>NaN</code>.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Vhz09Rgw3h" href="#c-Vhz09Rgw3h" tabindex="-1" role="presentation"></a>console.log(NaN == NaN) <span class="tok-comment">// → false</span></pre> @@ -188,7 +190,7 @@ <h3><a class="i_ident" id="i-gt6oADpxuG" href="#i-gt6oADpxuG" tabindex="-1" role <p><a class="p_ident" id="p-bDUM8R6qD8" href="#p-bDUM8R6qD8" tabindex="-1" role="presentation"></a>También hay algunas operaciones que se pueden aplicar a los propios valores Booleanos. JavaScript soporta tres operadores lógicos: <em>and</em> (y), <em>or</em> (o), y <em>not</em> (no). Estos se pueden usar para “razonar” sobre valores Booleanos.</p> -<p><a class="p_ident" id="p-IlZiB/NoWt" href="#p-IlZiB/NoWt" tabindex="-1" role="presentation"></a>El operador <code>&&</code> representa el <em>and</em> lógico. Es un operador binario, y su resultado es verdadero solo si ambos valores dados son verdaderos.</p> +<p><a class="p_ident" id="p-IlZiB/NoWt" href="#p-IlZiB/NoWt" tabindex="-1" role="presentation"></a>El operador <code>&&</code> representa el <em>and</em> lógico. Es un operador binario, y su resultado es verdadero solo si los dos valores dados son verdaderos.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-SHi38sNkwM" href="#c-SHi38sNkwM" tabindex="-1" role="presentation"></a>console.log(true && false) <span class="tok-comment">// → false</span> @@ -202,30 +204,30 @@ <h3><a class="i_ident" id="i-gt6oADpxuG" href="#i-gt6oADpxuG" tabindex="-1" role console.log(false || false) <span class="tok-comment">// → false</span></pre> -<p><a class="p_ident" id="p-yd+g1Em++r" href="#p-yd+g1Em++r" tabindex="-1" role="presentation"></a><em>Not</em> se escribe con un signo de exclamación (<code>!</code>). Es un operador unario que invierte el valor dado; <code>!true</code> produce <code>false</code> y <code>!false</code> produce <code>true</code>.</p> +<p><a class="p_ident" id="p-yd+g1Em++r" href="#p-yd+g1Em++r" tabindex="-1" role="presentation"></a><em>Not</em> se escribe con un signo de exclamación (<code>!</code>). Es un operador unario que invierte el valor del valor dado; <code>!true</code> produce <code>false</code> y <code>!false</code> produce <code>true</code>.</p> -<p><a class="p_ident" id="p-93dQyS+qYG" href="#p-93dQyS+qYG" tabindex="-1" role="presentation"></a>Al combinar estos operadores Booleanos con operadores aritméticos y otros operadores, no siempre es obvio cuándo se necesitan paréntesis. En la práctica, generalmente puedes avanzar sabiendo que de los operadores que hemos visto hasta ahora, <code>||</code> tiene la menor precedencia, luego viene <code>&&</code>, luego los operadores de comparación (<code>></code>, <code>==</code>, etc.), y luego el resto. Este orden ha sido elegido de tal manera que, en expresiones típicas como la siguiente, se necesiten la menor cantidad de paréntesis posible:</p> +<p><a class="p_ident" id="p-93dQyS+qYG" href="#p-93dQyS+qYG" tabindex="-1" role="presentation"></a>Al combinar estos operadores Booleanos con operadores aritméticos y otros operadores, no siempre es obvio cuándo se necesitan paréntesis. En la práctica, generalmente puedes tirar para alante sabiendo que, de los operadores que hemos visto hasta ahora, <code>||</code> tiene la menor precedencia, luego viene <code>&&</code>, luego los operadores de comparación (<code>></code>, <code>==</code>, etc.), y luego el resto. Este orden ha sido elegido de tal manera que, en expresiones típicas como la siguiente, se necesite la menor cantidad de paréntesis posible:</p> <pre tabindex="0" class="snippet" data-language="javascript" data-meta="expr"><a class="c_ident" id="c-6eZ07bDo11" href="#c-6eZ07bDo11" tabindex="-1" role="presentation"></a><span class="tok-number">1</span> + <span class="tok-number">1</span> == <span class="tok-number">2</span> && <span class="tok-number">10</span> * <span class="tok-number">10</span> > <span class="tok-number">50</span></pre> -<p><a class="p_ident" id="p-UiSwCiAKjN" href="#p-UiSwCiAKjN" tabindex="-1" role="presentation"></a>El último operador lógico que veremos no es unario ni binario, sino <em>ternario</em>, operando en tres valores. Se escribe con un signo de interrogación y dos puntos, así:</p> +<p><a class="p_ident" id="p-Wlk+fDzW24" href="#p-Wlk+fDzW24" tabindex="-1" role="presentation"></a>El último operador lógico que veremos no es unario ni binario, sino <em>ternario</em>, operando en tres valores. Se escribe con un signo de interrogación y dos puntos, como sigue:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-G7eVm8ilWm" href="#c-G7eVm8ilWm" tabindex="-1" role="presentation"></a>console.log(true ? <span class="tok-number">1</span> : <span class="tok-number">2</span>); <span class="tok-comment">// → 1</span> console.log(false ? <span class="tok-number">1</span> : <span class="tok-number">2</span>); <span class="tok-comment">// → 2</span></pre> -<p><a class="p_ident" id="p-iVFzSp2zoP" href="#p-iVFzSp2zoP" tabindex="-1" role="presentation"></a>Este se llama el operador <em>condicional</em> (o a veces simplemente <em>el operador ternario</em> ya que es el único operador de este tipo en el lenguaje). El operador usa el valor a la izquierda del signo de interrogación para decidir cuál de los otros dos valores “elegir”. Si escribes <code>a ? b : c</code>, el resultado será <code>b</code> cuando <code>a</code> es verdadero y <code>c</code> de lo contrario.</p> +<p><a class="p_ident" id="p-le+07C59qk" href="#p-le+07C59qk" tabindex="-1" role="presentation"></a>Este es el llamado operador <em>condicional</em> (o a veces simplemente <em>el operador ternario</em> ya que es el único operador de este tipo en el lenguaje). El operador usa el valor a la izquierda del signo de interrogación para decidir cuál de los otros dos valores “elegir”. Si escribes <code>a ? b : c</code>, el resultado será <code>b</code> cuando <code>a</code> es verdadero y <code>c</code> en caso contrario.</p> <h2><a class="h_ident" id="h-u3yR/DNtwV" href="#h-u3yR/DNtwV" tabindex="-1" role="presentation"></a>Valores vacíos</h2> -<p><a class="p_ident" id="p-u9OVOcj2FK" href="#p-u9OVOcj2FK" tabindex="-1" role="presentation"></a>Hay dos valores especiales, escritos <code>null</code> y <code>undefined</code>, que se utilizan para denotar la ausencia de un valor <em>significativo</em>. Son valores en sí mismos, pero no llevan ninguna información. Muchas operaciones en el lenguaje que no producen un valor significativo devuelven <code>undefined</code> simplemente porque tienen que devolver <em>algún</em> valor.</p> +<p><a class="p_ident" id="p-u9OVOcj2FK" href="#p-u9OVOcj2FK" tabindex="-1" role="presentation"></a>Hay dos valores especiales que se escriben <code>null</code> y <code>undefined</code> y que se utilizan para denotar la ausencia de un valor <em>significativo</em>. Son valores en sí mismos, pero no llevan ninguna información. Muchas operaciones en el lenguaje que no producen un valor significativo devuelven <code>undefined</code> simplemente porque tienen que devolver <em>algún</em> valor.</p> -<p><a class="p_ident" id="p-m+cr0CKvEq" href="#p-m+cr0CKvEq" tabindex="-1" role="presentation"></a>La diferencia en el significado entre <code>undefined</code> y <code>null</code> es un accidente del diseño de JavaScript, y la mayoría de las veces no importa. En casos en los que realmente tienes que preocuparte por estos valores, recomiendo tratarlos como en su mayoría intercambiables.</p> +<p><a class="p_ident" id="p-jFuq0AZiyF" href="#p-jFuq0AZiyF" tabindex="-1" role="presentation"></a>La diferencia en el significado entre <code>undefined</code> y <code>null</code> es un accidente del diseño de JavaScript, y la mayoría de las veces no es relevante. En casos en los que tienes que tratar con estos valores, recomiendo tratarlos como esencialmente intercambiables.</p> <h2><a class="h_ident" id="h-d0EfIV+05Q" href="#h-d0EfIV+05Q" tabindex="-1" role="presentation"></a>Conversión automática de tipos</h2> -<p><a class="p_ident" id="p-hiiKbgQYbK" href="#p-hiiKbgQYbK" tabindex="-1" role="presentation"></a>En la Introducción, mencioné que JavaScript se esfuerza por aceptar casi cualquier programa que le des, incluso programas que hacen cosas extrañas. Esto se demuestra claramente con las siguientes expresiones:</p> +<p><a class="p_ident" id="p-hiiKbgQYbK" href="#p-hiiKbgQYbK" tabindex="-1" role="presentation"></a>En la <a href="00_intro.html">Introducción</a>, mencioné que JavaScript se pasa por aceptando cualquier programa que le des, incluso programas que hacen cosas extrañas. Esto se demuestra claramente con las siguientes expresiones:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-QqYG9KqZ2/" href="#c-QqYG9KqZ2/" tabindex="-1" role="presentation"></a>console.log(<span class="tok-number">8</span> * <span class="tok-keyword">null</span>) <span class="tok-comment">// → 0</span> @@ -238,24 +240,26 @@ <h2><a class="h_ident" id="h-d0EfIV+05Q" href="#h-d0EfIV+05Q" tabindex="-1" role console.log(false == <span class="tok-number">0</span>) <span class="tok-comment">// → true</span></pre> -<p><a class="p_ident" id="p-kB8tbSpJvt" href="#p-kB8tbSpJvt" tabindex="-1" role="presentation"></a>Cuando se aplica un operador al tipo de valor “incorrecto”, JavaScript convertirá silenciosamente ese valor al tipo que necesita, utilizando un conjunto de reglas que a menudo no son las que deseas o esperas. Esto se llama <em>coerción de tipos</em>. El <code>null</code> en la primera expresión se convierte en <code>0</code> y el <code>"5"</code> en la segunda expresión se convierte en <code>5</code> (de cadena a número). Sin embargo, en la tercera expresión, <code>+</code> intenta la concatenación de cadenas antes que la suma numérica, por lo que el <code>1</code> se convierte en <code>"1"</code> (de número a cadena).</p> +<p><a class="p_ident" id="p-kB8tbSpJvt" href="#p-kB8tbSpJvt" tabindex="-1" role="presentation"></a>Cuando se aplica un operador al tipo de valor “incorrecto”, JavaScript convertirá silenciosamente ese valor al tipo que necesita, utilizando una serie de reglas que a menudo no son las que buscas o esperas. Esto se llama <em>coerción de tipos</em>. El <code>null</code> en la primera expresión se convierte en <code>0</code> y el <code>"5"</code> en la segunda expresión se convierte en <code>5</code> (de cadena a número). Sin embargo, en la tercera expresión, <code>+</code> intenta la concatenación de cadenas antes que la suma de números, por lo que el <code>1</code> se convierte en <code>"1"</code> (de número a cadena).</p> -<p><a class="p_ident" id="p-jcnJfAvvp6" href="#p-jcnJfAvvp6" tabindex="-1" role="presentation"></a>Cuando algo que no se corresponde con un número de manera obvia (como <code>"five"</code> o <code>undefined</code>) se convierte en un número, obtienes el valor <code>NaN</code>. Más operaciones aritméticas en <code>NaN</code> siguen produciendo <code>NaN</code>, así que si te encuentras con uno de estos en un lugar inesperado, busca conversiones de tipo accidentales.</p> +<p><a class="p_ident" id="p-Od4I2t6RG4" href="#p-Od4I2t6RG4" tabindex="-1" role="presentation"></a>Cuando se convierte en un número algo que no se corresponde de manera obvia con un número (como <code>"five"</code> o <code>undefined</code>), obtienes el valor <code>NaN</code>. Más operaciones aritméticas en <code>NaN</code> siguen produciendo <code>NaN</code>, así que si ves que te sale uno de estos en un lugar inesperado, busca conversiones de tipo accidentales.</p> -<p><a class="p_ident" id="p-qunwSNidbj" href="#p-qunwSNidbj" tabindex="-1" role="presentation"></a>Cuando se comparan valores del mismo tipo usando el operador <code>==</code>, el resultado es fácil de predecir: deberías obtener verdadero cuando ambos valores son iguales, excepto en el caso de <code>NaN</code>. Pero cuando los tipos difieren, JavaScript utiliza un conjunto de reglas complicado y confuso para determinar qué hacer. En la mayoría de los casos, simplemente intenta convertir uno de los valores al tipo del otro valor. Sin embargo, cuando <code>null</code> o <code>undefined</code> aparece en cualquiera de los lados del operador, produce verdadero solo si ambos lados son uno de <code>null</code> o <code>undefined</code>.</p> +<p><a class="p_ident" id="p-UKIAT7ZHGQ" href="#p-UKIAT7ZHGQ" tabindex="-1" role="presentation"></a>Cuando se comparan valores del mismo tipo usando el operador <code>==</code>, el resultado es fácil de predecir: deberías obtener verdadero cuando ambos valores son iguales, excepto en el caso de <code>NaN</code>. Pero cuando los tipos difieren, JavaScript utiliza una serie de reglas extrañas para determinar qué hacer. En la mayoría de los casos, simplemente intenta convertir uno de los valores al tipo del otro valor. Sin embargo, cuando <code>null</code> o <code>undefined</code> aparecen en cualquiera de los lados del operador, este produce verdadero solo si el valor de ambos lados está entre <code>null</code> y <code>undefined</code>.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-qmGDPdETlf" href="#c-qmGDPdETlf" tabindex="-1" role="presentation"></a>console.log(<span class="tok-keyword">null</span> == undefined); <span class="tok-comment">// → true</span> console.log(<span class="tok-keyword">null</span> == <span class="tok-number">0</span>); <span class="tok-comment">// → false</span></pre> -<p><a class="p_ident" id="p-Xtu7tKxIxD" href="#p-Xtu7tKxIxD" tabindex="-1" role="presentation"></a>Ese comportamiento a menudo es útil. Cuando quieres probar si un valor tiene un valor real en lugar de <code>null</code> o <code>undefined</code>, puedes compararlo con <code>null</code> usando el operador <code>==</code> o <code>!=</code>.</p> +<p><a class="p_ident" id="p-Xtu7tKxIxD" href="#p-Xtu7tKxIxD" tabindex="-1" role="presentation"></a>Ese comportamiento a menudo es útil. Cuando quieres comprobar si un valor tiene un valor real en lugar de <code>null</code> o <code>undefined</code>, puedes simplemente compararlo con <code>null</code> usando el operador <code>==</code> o <code>!=</code>.</p> + +<p><a class="p_ident" id="p-B5GeEdriT6" href="#p-B5GeEdriT6" tabindex="-1" role="presentation"></a>¿Qué sucede si quieres probar si algo se refiere al valor preciso <code>false</code>? Expresiones como <code>0 == false</code> y <code>"" == false</code> también dan verdadero debido a la conversión automática de tipos. Cuando <em>no</em> deseas que ocurran conversiones de tipo, hay dos operadores adicionales: <code>===</code> y <code>!==</code>. El primero prueba si un valor es <em>precisamente</em> igual al otro, y el segundo prueba si no es precisamente igual. Por lo tanto, <code>"" === false</code> es falso, como era de esperar.</p> -<p><a class="p_ident" id="p-ov1vMdqygq" href="#p-ov1vMdqygq" tabindex="-1" role="presentation"></a>¿Qué sucede si quieres probar si algo se refiere al valor preciso <code>false</code>? Expresiones como <code>0 == false</code> y <code>"" == false</code> también son verdaderas debido a la conversión automática de tipos. Cuando <em>no</em> deseas que ocurran conversiones de tipo, hay dos operadores adicionales: <code>===</code> y <code>!==</code>. El primero prueba si un valor es <em>precisamente</em> igual al otro, y el segundo prueba si no es precisamente igual. Por lo tanto, <code>"" === false</code> es falso como se espera. Recomiendo usar los operadores de comparación de tres caracteres defensivamente para evitar conversiones de tipo inesperadas que puedan complicarte las cosas. Pero cuando estés seguro de que los tipos en ambos lados serán los mismos, no hay problema en usar los operadores más cortos.</p> +<p><a class="p_ident" id="p-qXj7SOKlWI" href="#p-qXj7SOKlWI" tabindex="-1" role="presentation"></a>Recomiendo usar los operadores de comparación de tres caracteres de manera preventiva para evitar conversiones de tipo inesperadas que puedan complicarte las cosas. Pero cuando estés seguro de que los tipos en ambos lados serán los mismos, no hay problema en usar los otros operadores más cortos.</p> <h3><a class="i_ident" id="i-SOUbB0ZSTp" href="#i-SOUbB0ZSTp" tabindex="-1" role="presentation"></a>Cortocircuito de operadores lógicos</h3> -<p><a class="p_ident" id="p-uieZyUDZJD" href="#p-uieZyUDZJD" tabindex="-1" role="presentation"></a>Los operadores lógicos <code>&&</code> y <code>||</code> manejan valores de diferentes tipos de una manera peculiar. Convertirán el valor del lado izquierdo a tipo Booleano para decidir qué hacer, pero dependiendo del operador y el resultado de esa conversión, devolverán ya sea el valor original del lado izquierdo o el valor del lado derecho.</p> +<p><a class="p_ident" id="p-uieZyUDZJD" href="#p-uieZyUDZJD" tabindex="-1" role="presentation"></a>Los operadores lógicos <code>&&</code> y <code>||</code> manejan valores de tipos distintos de una forma peculiar. Convierten el valor del lado izquierdo a tipo Booleano para decidir qué hacer, pero, dependiendo del operador y el resultado de esa conversión, devuelven el valor <em>original</em> del lado izquierdo o el valor del lado derecho.</p> <p><a class="p_ident" id="p-d+fK5uFMGA" href="#p-d+fK5uFMGA" tabindex="-1" role="presentation"></a>El operador <code>||</code>, por ejemplo, devolverá el valor de su izquierda cuando ese valor pueda convertirse en true y devolverá el valor de su derecha de lo contrario. Esto tiene el efecto esperado cuando los valores son Booleanos y hace algo análogo para valores de otros tipos.</p> @@ -264,9 +268,9 @@ <h3><a class="i_ident" id="i-SOUbB0ZSTp" href="#i-SOUbB0ZSTp" tabindex="-1" role console.log(<span class="tok-string">"Agnes"</span> || <span class="tok-string">"usuario"</span>) <span class="tok-comment">// → Agnes</span></pre> -<p><a class="p_ident" id="p-x9D4HrfdZX" href="#p-x9D4HrfdZX" tabindex="-1" role="presentation"></a>Podemos utilizar esta funcionalidad como una forma de utilizar un valor predeterminado. Si tienes un valor que podría estar vacío, puedes colocar <code>||</code> después de él con un valor de reemplazo. Si el valor inicial se puede convertir en false, obtendrás el valor de reemplazo en su lugar. Las reglas para convertir cadenas y números en valores Booleanos establecen que <code>0</code>, <code>NaN</code> y la cadena vacía (<code>""</code>) cuentan como <code>false</code>, mientras que todos los demás valores cuentan como <code>true</code>. Esto significa que <code>0 || -1</code> produce <code>-1</code>, y <code>"" || "!?"</code> da como resultado <code>"!?"</code>.</p> +<p><a class="p_ident" id="p-x9D4HrfdZX" href="#p-x9D4HrfdZX" tabindex="-1" role="presentation"></a>Podemos utilizar esta funcionalidad como una forma de utilizar un valor predeterminado. Si tienes un valor que podría estar vacío, puedes colocar <code>||</code> después de él con un valor de reemplazo. Si el valor inicial se puede convertir en false, obtendrás el valor de reemplazo en su lugar. Las reglas para convertir cadenas y números en valores Booleanos establecen que <code>0</code>, <code>NaN</code> y la cadena vacía (<code>""</code>) cuentan como <code>false</code>, mientras que todos los demás valores serán <code>true</code>. Esto significa que <code>0 || -1</code> produce <code>-1</code>, y <code>"" || "!?"</code> da como resultado <code>"!?"</code>.</p> -<p><a class="p_ident" id="p-E/l93sJCpQ" href="#p-E/l93sJCpQ" tabindex="-1" role="presentation"></a>El operador <code>??</code> se asemeja a <code>||</code>, pero devuelve el valor de la derecha solo si el de la izquierda es null o undefined, no si es algún otro valor que se pueda convertir en <code>false</code>. A menudo, este comportamiento es preferible al de <code>||</code>.</p> +<p><a class="p_ident" id="p-E/l93sJCpQ" href="#p-E/l93sJCpQ" tabindex="-1" role="presentation"></a>El operador <code>??</code> se parece a <code>||</code>, pero devuelve el valor de la derecha solo si el de la izquierda es null o undefined, no si es algún otro valor que se pueda convertir en <code>false</code>. Normalmente, este comportamiento es preferible al de <code>||</code>.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-UVdLwFRz8U" href="#c-UVdLwFRz8U" tabindex="-1" role="presentation"></a>console.log(<span class="tok-number">0</span> || <span class="tok-number">100</span>); <span class="tok-comment">// → 100</span> @@ -275,17 +279,19 @@ <h3><a class="i_ident" id="i-SOUbB0ZSTp" href="#i-SOUbB0ZSTp" tabindex="-1" role console.log(<span class="tok-keyword">null</span> ?? <span class="tok-number">100</span>); <span class="tok-comment">// → 100</span></pre> -<p><a class="p_ident" id="p-NtTtZBsnPX" href="#p-NtTtZBsnPX" tabindex="-1" role="presentation"></a>El operador <code>&&</code> funciona de manera similar pero en sentido contrario. Cuando el valor a su izquierda es algo que se convierte en false, devuelve ese valor, y de lo contrario devuelve el valor de su derecha.</p> +<p><a class="p_ident" id="p-NtTtZBsnPX" href="#p-NtTtZBsnPX" tabindex="-1" role="presentation"></a>El operador <code>&&</code> funciona de manera parecida pero en sentido contrario. Cuando el valor a su izquierda es algo que se convierte en false, devuelve ese valor, y de lo contrario devuelve el valor de su derecha.</p> -<p><a class="p_ident" id="p-+rquv/zDNk" href="#p-+rquv/zDNk" tabindex="-1" role="presentation"></a>Otra propiedad importante de estos dos operadores es que la parte de su derecha se evalúa solo cuando es necesario. En el caso de <code>true || X</code>, no importa qué sea <code>X</code>, incluso si es una parte del programa que hace algo <em>terrible</em>, el resultado será true, y <code>X</code> nunca se evaluará. Lo mismo ocurre con <code>false && X</code>, que es false e ignorará <code>X</code>. Esto se llama <em>evaluación de cortocircuito</em>.</p> +<p><a class="p_ident" id="p-DfC6QQzgoh" href="#p-DfC6QQzgoh" tabindex="-1" role="presentation"></a>Otra propiedad importante de estos dos operadores es que la parte de su derecha se evalúa solo cuando es necesario. En el caso de <code>true || X</code>, no importa lo que sea <code>X</code> —incluso si es una parte del programa que hace algo <em>terrible</em>—, el resultado será true, y <code>X</code> nunca se evaluará. Lo mismo ocurre con <code>false && X</code>, que es false e ignorará <code>X</code>. Esto se llama <em>evaluación de cortocircuito</em> (o <em>evaluación mínima</em>).</p> <p><a class="p_ident" id="p-tWKK3353PD" href="#p-tWKK3353PD" tabindex="-1" role="presentation"></a>El operador condicional funciona de manera similar. De los valores segundo y tercero, solo se evalúa el que sea seleccionado.</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-8teOb/HsE+" href="#p-8teOb/HsE+" tabindex="-1" role="presentation"></a>En este capítulo examinamos cuatro tipos de valores en JavaScript: números, cadenas, Booleanos y valores indefinidos. Tales valores son creados escribiendo su nombre (<code>true</code>, <code>null</code>) o valor (<code>13</code>, <code>"abc"</code>). Puedes combinar y transformar valores con operadores. Vimos operadores binarios para aritmética (<code>+</code>, <code>-</code>, <code>*</code>, <code>/</code> y <code>%</code>), concatenación de cadenas (<code>+</code>), comparación (<code>==</code>, <code>!=</code>, <code>===</code>, <code>!==</code>, <code><</code>, <code>></code>, <code><=</code>, <code>>=</code>) y lógica (<code>&&</code>, <code>||</code>, <code>??</code>), así como varios operadores unarios (<code>-</code> para negar un número, <code>!</code> para negar lógicamente, y <code>typeof</code> para encontrar el tipo de un valor) y un operador ternario (<code>?:</code>) para elegir uno de dos valores basado en un tercer valor.</p> +<p><a class="p_ident" id="p-zHupHOsvBq" href="#p-zHupHOsvBq" tabindex="-1" role="presentation"></a>En este capítulo examinamos cuatro tipos de valores en JavaScript: números, cadenas, Booleanos y valores indefinidos. Tales valores son creados escribiendo su nombre (<code>true</code>, <code>null</code>) o valor (<code>13</code>, <code>"abc"</code>).</p> + +<p><a class="p_ident" id="p-V2oU2tKrEz" href="#p-V2oU2tKrEz" tabindex="-1" role="presentation"></a>Puedes combinar y transformar valores con operadores. Hemos visto operadores binarios para aritmética (<code>+</code>, <code>-</code>, <code>*</code>, <code>/</code> y <code>%</code>), concatenación de cadenas (<code>+</code>), comparación (<code>==</code>, <code>!=</code>, <code>===</code>, <code>!==</code>, <code><</code>, <code>></code>, <code><=</code>, <code>>=</code>) y lógica (<code>&&</code>, <code>||</code>, <code>??</code>), así como varios operadores unarios (<code>-</code> para obtener el opuesto de un número, <code>!</code> para negar lógicamente, y <code>typeof</code> para descubrir el tipo de un valor) y un operador ternario (<code>?:</code>) para elegir uno de dos valores basado en un tercer valor.</p> -<p><a class="p_ident" id="p-asWL1nI+Xu" href="#p-asWL1nI+Xu" tabindex="-1" role="presentation"></a>Esto te proporciona suficiente información para usar JavaScript como una calculadora de bolsillo, pero no mucho más. El <a href="02_program_structure.html">próximo capítulo</a> comenzará a unir estas expresiones en programas básicos.</p><nav><a href="00_intro.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="02_program_structure.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-b3ceu9ngIK" href="#p-b3ceu9ngIK" tabindex="-1" role="presentation"></a>Esto es información suficiente para usar JavaScript como una calculadora de bolsillo, pero no mucho más. El <a href="02_program_structure.html">próximo capítulo</a> comenzará a unir estas expresiones en programas básicos.</p><nav><a href="00_intro.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="02_program_structure.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button> </nav> </article> diff --git a/html/02_program_structure.html b/html/02_program_structure.html index 7a8a3fe0..cdb40c34 100644 --- a/html/02_program_structure.html +++ b/html/02_program_structure.html @@ -14,50 +14,53 @@ <h1>Estructura del Programa</h1> <blockquote> -<p><a class="p_ident" id="p-o/py/y446p" href="#p-o/py/y446p" tabindex="-1" role="presentation"></a>Y mi corazón brilla intensamente bajo mi piel diáfana y translúcida, y tienen que administrarme 10cc de JavaScript para hacerme volver. (Respondo bien a las toxinas en la sangre.) ¡Hombre, esa cosa sacará los melocotones de tus agallas!</p> +<p><a class="p_ident" id="p-qP+4WumZIz" href="#p-qP+4WumZIz" tabindex="-1" role="presentation"></a>Y mi corazón brilla de color rojo intenso bajo mi diáfana y translúcida piel, y tienen que administrarme 10cc de JavaScript para traerme de vuelta. —Tolero bien las toxinas en la sangre. ¡Amigo, esa cosa sería capaz hasta de sacarte un melocotón atorado en las branquias!</p> -<footer>_why, <cite>Guía (conmovedora) de Ruby de Why</cite></footer> +<footer>_why, <cite>La (Conmovedora) Guía de Ruby de Why</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_2.jpg" alt="Ilustración que muestra varios tentáculos sujetando piezas de ajedrez"></figure> -<p><a class="p_ident" id="p-uqRM4ZM/cN" href="#p-uqRM4ZM/cN" tabindex="-1" role="presentation"></a>En este capítulo, comenzaremos a hacer cosas que realmente pueden ser llamadas <em>programación</em>. Ampliaremos nuestro dominio del lenguaje JavaScript más allá de los sustantivos y fragmentos de oraciones que hemos visto hasta ahora, hasta el punto en que podamos expresar prosa significativa.</p> +<p><a class="p_ident" id="p-uqRM4ZM/cN" href="#p-uqRM4ZM/cN" tabindex="-1" role="presentation"></a>En este capítulo, vamos a empezar a hacer cosas que realmente podrían llamarse <em>programación</em>. Ampliaremos nuestro dominio del lenguaje JavaScript más allá de los sustantivos y fragmentos de oraciones que hemos visto hasta ahora, hasta el punto en que podamos expresar prosa significativa.</p> <h2><a class="h_ident" id="h-oqfl+ptPGG" href="#h-oqfl+ptPGG" tabindex="-1" role="presentation"></a>Expresiones y declaraciones</h2> -<p><a class="p_ident" id="p-wp0fJOzSaK" href="#p-wp0fJOzSaK" tabindex="-1" role="presentation"></a>En el <a href="01_values.html">Capítulo 1</a> creamos valores y le aplicamos operadores para obtener nuevos valores. Crear valores de esta manera es la sustancia principal de cualquier programa JavaScript. Pero esa sustancia debe enmarcarse en una estructura más grande para ser útil. Eso es lo que cubriremos en este capítulo.</p> +<p><a class="p_ident" id="p-wp0fJOzSaK" href="#p-wp0fJOzSaK" tabindex="-1" role="presentation"></a>En el <a href="01_values.html">Capítulo 1</a> hemos creado valores y les hemos aplicado operadores para obtener nuevos valores. Crear valores de esta manera es la esencia principal de cualquier programa de JavaScript. Pero esa esencia debe enmarcarse en una estructura más grande para ser de utilidad. Eso es lo que vamos a cubrir en este capítulo.</p> -<p><a class="p_ident" id="p-3x4YDXd58+" href="#p-3x4YDXd58+" tabindex="-1" role="presentation"></a>Un fragmento de código que produce un valor se llama una <em>expresión</em>. Cada valor que está escrito literalmente (como <code>22</code> o <code>"psicoanálisis"</code>) es una expresión. Una expresión entre paréntesis también es una expresión, al igual que un operador binario aplicado a dos expresiones o un operador unario aplicado a uno.</p> +<p><a class="p_ident" id="p-ZOiJ36tTUi" href="#p-ZOiJ36tTUi" tabindex="-1" role="presentation"></a>Un trozo de código que produce un valor se llama una <em>expresión</em>. Cada valor escrito literalmente (como <code>22</code> o <code>"psicoanálisis"</code>) es una expresión. Una expresión entre paréntesis también es una expresión, al igual que un operador binario aplicado a dos expresiones o un operador unario aplicado a una.</p> -<p><a class="p_ident" id="p-UqOwwtmeFY" href="#p-UqOwwtmeFY" tabindex="-1" role="presentation"></a>Esto muestra parte de la belleza de una interfaz basada en un lenguaje. Las expresiones pueden contener otras expresiones de manera similar a cómo las oraciones están anidadas en el lenguaje humano: una oración puede contener sus propias oraciones y así sucesivamente. Esto nos permite construir expresiones que describen cálculos arbitrariamente complejos.</p> +<p><a class="p_ident" id="p-UqOwwtmeFY" href="#p-UqOwwtmeFY" tabindex="-1" role="presentation"></a>Esto muestra parte de la belleza de una interfaz basada en un lenguaje. Las expresiones pueden contener otras expresiones de manera similar a cómo las oraciones están anidadas en el lenguaje humano —una oración puede contener sus propias oraciones y así sucesivamente. Esto nos permite construir expresiones que describen cálculos arbitrariamente complejos.</p> -<p><a class="p_ident" id="p-ZVBoR+/oNP" href="#p-ZVBoR+/oNP" tabindex="-1" role="presentation"></a>Si una expresión corresponde a un fragmento de oración, una <em>declaración</em> de JavaScript corresponde a una oración completa. Un programa es una lista de declaraciones.</p> +<p><a class="p_ident" id="p-ZVBoR+/oNP" href="#p-ZVBoR+/oNP" tabindex="-1" role="presentation"></a>Si una expresión corresponde a un fragmento de oración, una <em>declaración</em> (o sentencia, o instrucción) de JavaScript corresponde a una oración completa. Un programa es una lista de declaraciones.</p> -<p><a class="p_ident" id="p-VEI0X64AX2" href="#p-VEI0X64AX2" tabindex="-1" role="presentation"></a>El tipo más simple de declaración es una expresión con un punto y coma al final. Este es un programa:</p> +<p><a class="p_ident" id="p-VEI0X64AX2" href="#p-VEI0X64AX2" tabindex="-1" role="presentation"></a>El tipo más simple de declaración es una expresión con un punto y coma al final. Esto es un programa:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-hjwmfcgDR0" href="#c-hjwmfcgDR0" tabindex="-1" role="presentation"></a><span class="tok-number">1</span>; !false;</pre> -<p><a class="p_ident" id="p-tnSc/zEyIA" href="#p-tnSc/zEyIA" tabindex="-1" role="presentation"></a>Sin embargo, es un programa inútil. Una expresión puede conformarse con simplemente producir un valor, que luego puede ser utilizado por el código que la contiene. Sin embargo, una declaración se mantiene por sí misma, por lo que si no afecta al mundo, es inútil. Puede mostrar algo en la pantalla, como con <code>console.log</code>, o cambiar el estado de la máquina de una manera que afectará a las declaraciones que vienen después de ella. Estos cambios se llaman <em>efectos secundarios</em>. Las declaraciones en el ejemplo anterior simplemente producen los valores <code>1</code> y <code>verdadero</code>, y luego los desechan inmediatamente. Esto no deja ninguna impresión en el mundo en absoluto. Cuando ejecutas este programa no sucede nada observable.</p> +<p><a class="p_ident" id="p-QL6KXOoBJm" href="#p-QL6KXOoBJm" tabindex="-1" role="presentation"></a>Aunque es un programa inútil. Una expresión puede conformarse con simplemente producir un valor, que luego podrá ser utilizado por el código que la contiene. Sin embargo, una declaración es autónoma, por lo que, si no afecta al mundo, es inútil. Puede mostrar algo en la pantalla, como con <code>console.log</code>, o cambiar el estado de la máquina de una manera que afectará a las declaraciones que vienen después de ella. Estos cambios se llaman <em>efectos secundarios</em>. Las declaraciones en el ejemplo anterior simplemente producen los valores <code>1</code> y <code>verdadero</code>, y luego los desecha inmediatamente. Esto no deja huella alguna en el mundo. Cuando ejecutas este programa no sucede nada observable.</p> -<p><a class="p_ident" id="p-cfI3s2SImw" href="#p-cfI3s2SImw" tabindex="-1" role="presentation"></a>En algunos casos, JavaScript te permite omitir el punto y coma al final de una declaración. En otros casos, debe estar ahí, o la próxima línea se tratará como parte de la misma declaración. Las reglas sobre cuándo se puede omitir de manera segura son algo complejas y propensas a errores. Por lo tanto, en este libro, cada declaración que necesite un punto y coma siempre recibirá uno. Te recomiendo que hagas lo mismo, al menos hasta que hayas aprendido más sobre las sutilezas de la omisión del punto y coma.</p> +<p><a class="p_ident" id="p-dbde+JAl0p" href="#p-dbde+JAl0p" tabindex="-1" role="presentation"></a>A veces, JavaScript te permite omitir el punto y coma al final de una declaración. Otras veces, debe estar ahí, o, si no, la próxima línea se tratará como parte de la misma declaración. Las reglas sobre cuándo se puede omitir de manera segura son algo complejas y propensas a causar errores. Por lo tanto, en este libro vamos a ponerle un punto y coma a cada declaración que lo necesite. Te recomiendo que hagas lo mismo, al menos hasta que aprendas más sobre las sutilezas que conlleva la omisión del punto y coma.</p> <h2><a class="h_ident" id="h-MCmlRffKOG" href="#h-MCmlRffKOG" tabindex="-1" role="presentation"></a>Enlaces</h2> -<p><a class="p_ident" id="p-38bl8CNA5y" href="#p-38bl8CNA5y" tabindex="-1" role="presentation"></a>¿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 cambia los valores antiguos, y el nuevo valor debe utilizarse inmediatamente o se disipará nuevamente. Para atrapar y retener valores, JavaScript proporciona una cosa llamada un <em>enlace</em>, o <em>variable</em>:</p> +<p><a class="p_ident" id="p-38bl8CNA5y" href="#p-38bl8CNA5y" tabindex="-1" role="presentation"></a>¿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 <em>asociación</em>, o <em>enlace</em>, o <em>variable</em>:</p> + +<div class="translator-note"><p><strong>N. del T.:</strong> El uso de la palabra <strong>variable</strong> 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 <strong>bind</strong> en lugar de <strong>variable</strong> para referirse a estas entidades. Nosotros haremos lo mismo utilizando la palabra <strong>asociación</strong>, aunque, en ocasiones, también se usará la palabra <strong>enlace</strong> o <strong>asignación</strong>. Rara vez usaremos la palabra <strong>variable</strong>, 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 <a href="functions">siguiente capítulo</a></p> +</div> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-aT9yLxdY/V" href="#c-aT9yLxdY/V" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">caught</span> = <span class="tok-number">5</span> * <span class="tok-number">5</span>;</pre> -<p><a class="p_ident" id="p-X/x/ckvx7F" href="#p-X/x/ckvx7F" tabindex="-1" role="presentation"></a>Eso nos da un segundo tipo de declaración. La palabra clave (<em>keyword</em>) <code>let</code> indica que esta frase va a definir un enlace. Está seguida por el nombre del enlace y, si queremos darle inmediatamente un valor, por un operador <code>=</code> y una expresión.</p> +<p><a class="p_ident" id="p-X/x/ckvx7F" href="#p-X/x/ckvx7F" tabindex="-1" role="presentation"></a>Eso nos da un segundo tipo de declaración. La palabra clave (<em>keyword</em>) <code>let</code> 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 <code>=</code> y una expresión.</p> -<p><a class="p_ident" id="p-dj86pp78/1" href="#p-dj86pp78/1" tabindex="-1" role="presentation"></a>El ejemplo crea un enlace llamado <code>caught</code> y lo utiliza para capturar el número que se produce al multiplicar 5 por 5.</p> +<p><a class="p_ident" id="p-AT3mFhUtnW" href="#p-AT3mFhUtnW" tabindex="-1" role="presentation"></a>En el ejemplo se crea una asociación llamada <code>caught</code> y se utiliza para capturar el número que se produce al multiplicar 5 por 5.</p> -<p><a class="p_ident" id="p-K3pByXEqHE" href="#p-K3pByXEqHE" tabindex="-1" role="presentation"></a>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 mantiene actualmente. Aquí tienes un ejemplo:</p> +<p><a class="p_ident" id="p-K3pByXEqHE" href="#p-K3pByXEqHE" tabindex="-1" role="presentation"></a>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:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-FfLIqaoaFx" href="#c-FfLIqaoaFx" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">ten</span> = <span class="tok-number">10</span>; console.log(ten * ten); <span class="tok-comment">// → 100</span></pre> -<p><a class="p_ident" id="p-AvjTHEDvCk" href="#p-AvjTHEDvCk" tabindex="-1" role="presentation"></a>Cuando un enlace apunta a un valor, eso no significa que esté atado a ese valor para siempre. El operador <code>=</code> se puede usar en cualquier momento en enlaces existentes para desconectarlos de su valor actual y hacer que apunten a uno nuevo:</p> +<p><a class="p_ident" id="p-2GPt2tymia" href="#p-2GPt2tymia" tabindex="-1" role="presentation"></a>Cuando una asociación apunta a un valor, eso no significa que esté atada a ese valor para siempre. El operador <code>=</code> se puede usar en cualquier momento en asociaciones existentes para desconectarlas de su valor actual y hacer que apunten a uno nuevo:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-FiZBrHz3CX" href="#c-FiZBrHz3CX" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">mood</span> = <span class="tok-string">"light"</span>; console.log(mood); @@ -66,39 +69,39 @@ <h2><a class="h_ident" id="h-MCmlRffKOG" href="#h-MCmlRffKOG" tabindex="-1" role console.log(mood); <span class="tok-comment">// → dark</span></pre> -<p><a class="p_ident" id="p-/t6Rp11+gu" href="#p-/t6Rp11+gu" tabindex="-1" role="presentation"></a>Debes imaginarte los enlaces como tentáculos en lugar de cajas. No <em>contienen</em> valores; los <em>agarran</em>—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.</p> +<p><a class="p_ident" id="p-F8iWo5Sq8i" href="#p-F8iWo5Sq8i" tabindex="-1" role="presentation"></a>Debes imaginarte las asociaciones como tentáculos más que como cajas. No <em>contienen</em> valores; los <em>agarran</em> —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.</p> -<p><a class="p_ident" id="p-rQptflTqgB" href="#p-rQptflTqgB" tabindex="-1" role="presentation"></a>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:</p> +<p><a class="p_ident" id="p-rQptflTqgB" href="#p-rQptflTqgB" tabindex="-1" role="presentation"></a>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:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-UpFQBNACng" href="#c-UpFQBNACng" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">luigisDebt</span> = <span class="tok-number">140</span>; luigisDebt = luigisDebt - <span class="tok-number">35</span>; console.log(luigisDebt); <span class="tok-comment">// → 105</span></pre> -<p><a class="p_ident" id="p-kDETpjHlkv" href="#p-kDETpjHlkv" tabindex="-1" role="presentation"></a>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 <code>undefined</code>.</p> +<p><a class="p_ident" id="p-frevBaVx3D" href="#p-frevBaVx3D" tabindex="-1" role="presentation"></a>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 <code>undefined</code>.</p> -<p><a class="p_ident" id="p-i2sMZlYPHm" href="#p-i2sMZlYPHm" tabindex="-1" role="presentation"></a>Una sola instrucción <code>let</code> puede definir múltiples enlaces. Las definiciones deben estar separadas por comas:</p> +<p><a class="p_ident" id="p-i2sMZlYPHm" href="#p-i2sMZlYPHm" tabindex="-1" role="presentation"></a>Una sola instrucción <code>let</code> puede definir múltiples asociaciones. Las definiciones deben estar separadas por comas:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-pT4pqev8kx" href="#c-pT4pqev8kx" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">one</span> = <span class="tok-number">1</span>, <span class="tok-definition">two</span> = <span class="tok-number">2</span>; console.log(one + two); <span class="tok-comment">// → 3</span></pre> -<p><a class="p_ident" id="p-/pFwdGuv9j" href="#p-/pFwdGuv9j" tabindex="-1" role="presentation"></a>Las palabras <code>var</code> y <code>const</code> también se pueden usar para crear enlaces, de manera similar a <code>let</code>:</p> +<p><a class="p_ident" id="p-8J5hlJwJL/" href="#p-8J5hlJwJL/" tabindex="-1" role="presentation"></a>Las palabras <code>var</code> y <code>const</code> también se pueden usar para crear asociaciones de manera similar a como lo hace <code>let</code>:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-MMmzlomlXC" href="#c-MMmzlomlXC" tabindex="-1" role="presentation"></a><span class="tok-keyword">var</span> <span class="tok-definition">name</span> = <span class="tok-string">"Ayda"</span>; <span class="tok-keyword">const</span> <span class="tok-definition">greeting</span> = <span class="tok-string">"Hola "</span>; console.log(greeting + name); <span class="tok-comment">// → Hola Ayda</span></pre> -<p><a class="p_ident" id="p-MRfMa48ppd" href="#p-MRfMa48ppd" tabindex="-1" role="presentation"></a>La primera de estas, <code>var</code> (abreviatura de “variable”), es la forma en que se declaraban los enlaces en JavaScript anterior a 2015, cuando aún no existía <code>let</code>. Volveré a la forma precisa en que difiere de <code>let</code> en el <a href="03_functions.html">próximo capítulo</a>. 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.</p> +<p><a class="p_ident" id="p-MRfMa48ppd" href="#p-MRfMa48ppd" tabindex="-1" role="presentation"></a>La primera de estas, <code>var</code> (abreviatura de “variable”), es la forma en que se declaraban las asociaciones en JavaScript anterior a 2015, cuando aún no existía <code>let</code>. Veremos la forma precisa en que difiere de <code>let</code> en el <a href="03_functions.html">próximo capítulo</a>. 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.</p> -<p><a class="p_ident" id="p-pY2LGjYZbd" href="#p-pY2LGjYZbd" tabindex="-1" role="presentation"></a>La palabra <code>const</code> significa <em>constante</em>. Define un enlace constante, que apunta al mismo valor mientras exista. Esto es útil para enlaces que solo dan un nombre a un valor para poder referirse fácilmente a él más tarde.</p> +<p><a class="p_ident" id="p-De7FCWnS3+" href="#p-De7FCWnS3+" tabindex="-1" role="presentation"></a>La palabra <code>const</code> significa <em>constante</em>. 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.</p> <h2><a class="h_ident" id="h-FHPw62JANA" href="#h-FHPw62JANA" tabindex="-1" role="presentation"></a>Nombres de enlaces</h2> -<p><a class="p_ident" id="p-abUAVwO7KN" href="#p-abUAVwO7KN" tabindex="-1" role="presentation"></a>Los nombres de enlaces pueden ser cualquier secuencia de una o más letras. Los dígitos pueden formar parte de los nombres de enlaces, <code>catch22</code> es un nombre válido, por ejemplo, pero el nombre no puede empezar con un dígito. Un nombre de enlace puede incluir signos de dólar (<code>$</code>) o subrayados (<code>_</code>), pero no otros signos de puntuación o caracteres especiales.</p> +<p><a class="p_ident" id="p-zmdNm4zBdm" href="#p-zmdNm4zBdm" tabindex="-1" role="presentation"></a>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 —<code>catch22</code> 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 (<code>$</code>) o subrayados (<code>_</code>), pero ningún otro carácter especial o signo de puntuación.</p> -<p><a class="p_ident" id="p-jVZGDp93c2" href="#p-jVZGDp93c2" tabindex="-1" role="presentation"></a>Palabras con un significado especial, como <code>let</code>, son <em>palabra clave</em>, y no pueden ser usadas como nombres de enlaces. 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:</p> +<p><a class="p_ident" id="p-twMqL1Xqqf" href="#p-twMqL1Xqqf" tabindex="-1" role="presentation"></a>Cualquier palabra con un significado especial, como <code>let</code>, es una <em>palabra clave</em>, 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:</p> <pre class="snippet" data-language="null" ><a class="c_ident" id="c-7BGolnv7qC" href="#c-7BGolnv7qC" tabindex="-1" role="presentation"></a>break case catch class const continue debugger default delete do else enum export extends false finally for @@ -106,25 +109,25 @@ <h2><a class="h_ident" id="h-FHPw62JANA" href="#h-FHPw62JANA" tabindex="-1" role new package private protected public return static super switch this throw true try typeof var void while with yield</pre> -<p><a class="p_ident" id="p-4lYOwB49B4" href="#p-4lYOwB49B4" tabindex="-1" role="presentation"></a>No te preocupes por memorizar esta lista. Cuando al crear un enlace se produce un error de sintaxis inesperado, verifica si estás intentando definir una palabra reservada.</p> +<p><a class="p_ident" id="p-X1tpMP4uiz" href="#p-X1tpMP4uiz" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-QDYVZFuV/L" href="#h-QDYVZFuV/L" tabindex="-1" role="presentation"></a>El entorno</h2> -<p><a class="p_ident" id="p-K18DlGgInQ" href="#p-K18DlGgInQ" tabindex="-1" role="presentation"></a>La colección de enlaces y sus valores que existen en un momento dado se llama <em>entorno</em>. Cuando un programa se inicia, este entorno no está vacío. Siempre contiene enlaces que forman parte del lenguaje estándar, y la mayoría de las veces también tiene enlaces que proporcionan formas de interactuar con el sistema circundante. Por ejemplo, en un navegador, existen funciones para interactuar con el sitio web cargado actualmente y para leer la entrada del ratón y el teclado.</p> +<p><a class="p_ident" id="p-K18DlGgInQ" href="#p-K18DlGgInQ" tabindex="-1" role="presentation"></a>La colección de enlaces y sus valores que existen en un momento dado se llama <em>entorno</em>. Cuando un programa se inicia, este entorno no está vacío. Siempre contiene enlaces que forman parte del estándar del lenguaje, y la mayoría de las veces también tiene enlaces que proporcionan formas de interactuar con el sistema circundante. Por ejemplo, en un navegador, existen funciones para interactuar con el sitio web cargado actualmente y para leer la entrada del ratón y el teclado.</p> <h2><a class="h_ident" id="h-H0l5He7QIh" href="#h-H0l5He7QIh" tabindex="-1" role="presentation"></a>Funciones</h2> -<p><a class="p_ident" id="p-Ic41DcsHv8" href="#p-Ic41DcsHv8" tabindex="-1" role="presentation"></a>Muchos de los valores proporcionados en el entorno predeterminado tienen el tipo de <em>función</em>. Una función es un fragmento de programa envuelto en un valor. Estos valores pueden ser <em>aplicados</em> para ejecutar el programa envuelto. Por ejemplo, en un entorno de navegador, el enlace <code>prompt</code> contiene una función que muestra un pequeño cuadro de diálogo pidiendo la entrada del usuario. Se utiliza de la siguiente manera:</p> +<p><a class="p_ident" id="p-Ic41DcsHv8" href="#p-Ic41DcsHv8" tabindex="-1" role="presentation"></a>Muchos de los valores proporcionados en el entorno predeterminado tienen el tipo <em>función</em>. Una función es un fragmento de programa encapsulado en un valor. Estos valores pueden ser <em>aplicados</em> para ejecutar el programa encapsulado. Por ejemplo, en un entorno de navegador, el enlace <code>prompt</code> contiene una función que muestra un pequeño cuadro de diálogo pidiendo la entrada del usuario. Se utiliza de la siguiente manera:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-QSUssd4nHv" href="#c-QSUssd4nHv" tabindex="-1" role="presentation"></a>prompt(<span class="tok-string">"Enter passcode"</span>);</pre><figure><img src="img/prompt.png" alt="Un cuadro de diálogo que dice 'Enter passcode'"></figure> -<p><a class="p_ident" id="p-ndYtFXmH+W" href="#p-ndYtFXmH+W" tabindex="-1" role="presentation"></a>Ejecutar una función se llama <em>invocar</em>, <em>llamar</em>, o <em>aplicar</em> la función. Puedes llamar una función poniendo paréntesis después de una expresión que produce un valor de función. Usualmente usarás directamente el nombre del enlace que contiene la función. Los valores entre paréntesis se le pasan al programa dentro de la función. En el ejemplo, la función <code>prompt</code> utiliza la cadena que le pasamos como el texto a mostrar en el cuadro de diálogo. Los valores dados a las funciones se llaman <em>argumentos</em>. Diferentes funciones pueden necesitar un número diferente o diferentes tipos de argumentos.</p> +<p><a class="p_ident" id="p-ndYtFXmH+W" href="#p-ndYtFXmH+W" tabindex="-1" role="presentation"></a>Ejecutar una función es lo que se conoce como <em>invocar</em>, <em>llamar</em>, o <em>aplicar</em> la función. Puedes llamar a una función poniendo paréntesis después de una expresión que produce un valor de función. Usualmente usarás directamente el nombre del enlace que contiene la función. Los valores entre paréntesis se le pasan al programa de dentro de la función. En el ejemplo, la función <code>prompt</code> utiliza la cadena que le pasamos como el texto a mostrar en el cuadro de diálogo. Los valores dados a las funciones se llaman <em>argumentos</em>. Diferentes funciones pueden necesitar un número diferente o diferentes tipos de argumentos.</p> <p><a class="p_ident" id="p-ocgjHJ27CZ" href="#p-ocgjHJ27CZ" tabindex="-1" role="presentation"></a>La función <code>prompt</code> no se usa mucho en la programación web moderna, principalmente porque no tienes control sobre cómo se ve el cuadro de diálogo resultante, pero puede ser útil en programas simples y experimentos.</p> <h2><a class="h_ident" id="h-Q017V5uet0" href="#h-Q017V5uet0" tabindex="-1" role="presentation"></a>La función console.log</h2> -<p><a class="p_ident" id="p-vU+xFwvodB" href="#p-vU+xFwvodB" tabindex="-1" role="presentation"></a>En los ejemplos, utilicé <code>console.log</code> para mostrar valores. La mayoría de los sistemas de JavaScript (incluidos todos los navegadores web modernos y Node.js) proveen una función <code>console.log</code> que escribe sus argumentos en <em>algún</em> dispositivo de salida de texto. En los navegadores, la salida va a la consola de JavaScript. Esta parte de la interfaz del navegador está oculta por defecto, pero la mayoría de los navegadores la abren cuando presionas F12 o, en Mac, <span class="keyname">comando</span>-<span class="keyname">opción</span>-I. Si eso no funciona, busca a través de los menús un elemento llamado Herramientas para Desarrolladores o similar.</p> +<p><a class="p_ident" id="p-vU+xFwvodB" href="#p-vU+xFwvodB" tabindex="-1" role="presentation"></a>En los ejemplos, he usado <code>console.log</code> para mostrar valores. La mayoría de los sistemas de JavaScript (incluidos todos los navegadores web modernos y Node.js) proveen una función <code>console.log</code> que escribe sus argumentos en <em>algún</em> dispositivo de salida de texto. En los navegadores, la salida va a la consola de JavaScript. Esta parte de la interfaz del navegador está oculta por defecto, pero la mayoría de los navegadores la abren cuando pulsas F12 o, en Mac, <span class="keyname">comando</span>-<span class="keyname">opción</span>-I. Si eso no funciona, busca a través de los menús un elemento llamado Herramientas para Desarrolladores o similar.</p> <p><a class="p_ident" id="p-pHtjXNwFtz" href="#p-pHtjXNwFtz" tabindex="-1" role="presentation"></a>Cuando ejecutas los ejemplos (o tu propio código) en las páginas de este libro, la salida de <code>console.log</code> se mostrará después del ejemplo, en lugar de en la consola de JavaScript del navegador.</p> @@ -132,11 +135,11 @@ <h2><a class="h_ident" id="h-Q017V5uet0" href="#h-Q017V5uet0" tabindex="-1" role console.log(<span class="tok-string">"el valor de x es"</span>, x); <span class="tok-comment">// → el valor de x es 30</span></pre> -<p><a class="p_ident" id="p-1QcFA54MdF" href="#p-1QcFA54MdF" tabindex="-1" role="presentation"></a>Aunque los nombres de enlaces no pueden contener puntos, <code>console.log</code> tiene uno. Esto se debe a que <code>console.log</code> no es un simple enlace, sino una expresión que recupera la propiedad <code>log</code> del valor contenido por el enlace <code>console</code>. Descubriremos exactamente lo que esto significa en el <a href="04_data.html#properties">Capítulo 4</a>.</p> +<p><a class="p_ident" id="p-1QcFA54MdF" href="#p-1QcFA54MdF" tabindex="-1" role="presentation"></a>Aunque los nombres de asociaciones no pueden contener puntos, <code>console.log</code> tiene uno. Esto se debe a que <code>console.log</code> no es un simple enlace, sino una expresión que recupera la propiedad <code>log</code> del valor contenido por el enlace <code>console</code>. Descubriremos exactamente lo que esto significa en el <a href="04_data.html#properties">Capítulo 4</a>.</p> <h2 id="valores_retorno"><a class="h_ident" id="h-xdOUAXPBXv" href="#h-xdOUAXPBXv" tabindex="-1" role="presentation"></a>Valores de retorno</h2> -<p><a class="p_ident" id="p-+t4lXUwK/u" href="#p-+t4lXUwK/u" tabindex="-1" role="presentation"></a>Mostrar un cuadro de diálogo o escribir texto en la pantalla es un efecto secundario. Muchas funciones son útiles debido a los efectos secundarios que producen. Las funciones también pueden producir valores, en cuyo caso no necesitan tener un efecto secundario para ser útiles. Por ejemplo, la función <code>Math.max</code> toma cualquier cantidad de argumentos numéricos y devuelve el mayor:</p> +<p><a class="p_ident" id="p-7wc9bq6hIg" href="#p-7wc9bq6hIg" tabindex="-1" role="presentation"></a>Mostrar un cuadro de diálogo o escribir texto en la pantalla es un efecto secundario. Muchas funciones son útiles debido a los efectos secundarios que producen. Las funciones también pueden producir valores, en cuyo caso no necesitan tener un efecto secundario para ser útiles. Por ejemplo, la función <code>Math.max</code> toma una cantidad cualquiera de argumentos numéricos y devuelve el mayor de ellos:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-PEuTYZX6NI" href="#c-PEuTYZX6NI" tabindex="-1" role="presentation"></a>console.log(Math.max(<span class="tok-number">2</span>, <span class="tok-number">4</span>)); <span class="tok-comment">// → 4</span></pre> @@ -148,23 +151,23 @@ <h2 id="valores_retorno"><a class="h_ident" id="h-xdOUAXPBXv" href="#h-xdOUAXPBX <p><a class="p_ident" id="p-Pk0vMYrbRo" href="#p-Pk0vMYrbRo" tabindex="-1" role="presentation"></a>El <a href="03_functions.html">Capítulo 3</a> explicará cómo escribir tus propias funciones.</p> -<h2><a class="h_ident" id="h-T9Yc/t6Db4" href="#h-T9Yc/t6Db4" tabindex="-1" role="presentation"></a>Control de flujo</h2> +<h2><a class="h_ident" id="h-hdgf/RurEo" href="#h-hdgf/RurEo" tabindex="-1" role="presentation"></a>Flujo de control</h2> -<p><a class="p_ident" id="p-9EgKeHK0je" href="#p-9EgKeHK0je" tabindex="-1" role="presentation"></a>Cuando tu programa contiene más de una sentencia, las sentencias se ejecutan como si fueran una historia, de arriba hacia abajo. Por ejemplo, el siguiente programa tiene dos sentencias. La primera le pide al usuario un número, y la segunda, que se ejecuta después de la primera, muestra el cuadrado de ese número:</p> +<p><a class="p_ident" id="p-9EgKeHK0je" href="#p-9EgKeHK0je" tabindex="-1" role="presentation"></a>Cuando tu programa contiene más de una declaración (o sentencia), estas se ejecutan como si fueran una historia, de arriba hacia abajo. Por ejemplo, el siguiente programa tiene dos declaraciones. La primera le pide al usuario un número, y la segunda, que se ejecuta después de la primera, muestra el cuadrado de ese número:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-/qJYz8f0H0" href="#c-/qJYz8f0H0" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">elNumero</span> = Number(prompt(<span class="tok-string">"Elige un número"</span>)); console.log(<span class="tok-string">"Tu número es la raíz cuadrada de "</span> + elNumero * elNumero);</pre> -<p><a class="p_ident" id="p-8DDMYXGv2v" href="#p-8DDMYXGv2v" tabindex="-1" role="presentation"></a>La función <code>Number</code> convierte un valor a un número. Necesitamos esa conversión porque el resultado de <code>prompt</code> es un valor de tipo string, y queremos un número. Hay funciones similares llamadas <code>String</code> y <code>Boolean</code> que convierten valores a esos tipos.</p> +<p><a class="p_ident" id="p-8DDMYXGv2v" href="#p-8DDMYXGv2v" tabindex="-1" role="presentation"></a>La función <code>Number</code> convierte un valor a un número. Necesitamos esa conversión porque el resultado de <code>prompt</code> es un valor de tipo <em>string</em> (una cadena), y queremos un número (un valor de tipo <em>number</em>). Hay funciones similares llamadas <code>String</code> y <code>Boolean</code> que convierten valores a esos tipos.</p> -<p><a class="p_ident" id="p-Hmketh0qEs" href="#p-Hmketh0qEs" tabindex="-1" role="presentation"></a>Aquí está la representación esquemática bastante trivial del flujo de control en línea recta:</p><figure><img src="img/controlflow-straight.svg" alt="Diagrama mostrando una flecha recta"></figure> +<p><a class="p_ident" id="p-Hmketh0qEs" href="#p-Hmketh0qEs" tabindex="-1" role="presentation"></a>Aquí está la más bien trivial representación esquemática del flujo de control en línea recta:</p><figure><img src="img/controlflow-straight.svg" alt="Diagrama mostrando una flecha recta"></figure> <h2><a class="h_ident" id="h-iTDXrRhIfn" href="#h-iTDXrRhIfn" tabindex="-1" role="presentation"></a>Ejecución condicional</h2> <p><a class="p_ident" id="p-e8j4kqj9t6" href="#p-e8j4kqj9t6" tabindex="-1" role="presentation"></a>No todos los programas son caminos rectos. Podríamos, por ejemplo, querer crear una carretera ramificada donde el programa tome la rama adecuada basada en la situación en cuestión. Esto se llama <em>ejecución condicional</em>.</p><figure><img src="img/controlflow-if.svg" alt="Diagrama de una flecha que se divide en dos y luego se une de nuevo"></figure> -<p><a class="p_ident" id="p-fZc5KedAGN" href="#p-fZc5KedAGN" tabindex="-1" role="presentation"></a>La ejecución condicional se crea con la palabra clave <code>if</code> en JavaScript. En el caso simple, queremos que cierto código se ejecute si, y solo si, una cierta condición es verdadera. Por ejemplo, podríamos querer mostrar el cuadrado de la entrada solo si la entrada es realmente un número:</p> +<p><a class="p_ident" id="p-fZc5KedAGN" href="#p-fZc5KedAGN" tabindex="-1" role="presentation"></a>La ejecución condicional se crea con la palabra clave <code>if</code> en JavaScript. La idea es que queremos que cierto código se ejecute si, y solo si, una cierta condición es verdadera. Por ejemplo, podríamos querer mostrar el cuadrado de la entrada solo si la entrada es realmente un número:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-X/zXi7paJC" href="#c-X/zXi7paJC" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">elNumero</span> = Number(prompt(<span class="tok-string">"Elige un número"</span>)); <span class="tok-keyword">if</span> (!Number.isNaN(elNumero)) { @@ -174,26 +177,26 @@ <h2><a class="h_ident" id="h-iTDXrRhIfn" href="#h-iTDXrRhIfn" tabindex="-1" role <p><a class="p_ident" id="p-wm40Jh5aS8" href="#p-wm40Jh5aS8" tabindex="-1" role="presentation"></a>Con esta modificación, si introduces “loro”, no se mostrará ninguna salida.</p> -<p><a class="p_ident" id="p-qmGSztfyJW" href="#p-qmGSztfyJW" tabindex="-1" role="presentation"></a>La palabra clave <code>if</code> ejecuta o salta una sentencia dependiendo del valor de una expresión booleana. La expresión de decisión se escribe después de la palabra clave, entre paréntesis, seguida de la sentencia a ejecutar.</p> +<p><a class="p_ident" id="p-qmGSztfyJW" href="#p-qmGSztfyJW" tabindex="-1" role="presentation"></a>La palabra clave <code>if</code> ejecuta o salta una sentencia dependiendo del valor de una expresión booleana. La expresión de decisión (la condición) se escribe después de la palabra clave, entre paréntesis, seguida de la sentencia a ejecutar.</p> <p><a class="p_ident" id="p-76c1pNKDSl" href="#p-76c1pNKDSl" tabindex="-1" role="presentation"></a>La función <code>Number.isNaN</code> es una función estándar de JavaScript que devuelve <code>true</code> solo si el argumento que se le pasa es <code>NaN</code>. La función <code>Number</code> devuelve <code>NaN</code> cuando le das una cadena que no representa un número válido. Por lo tanto, la condición se traduce a “a menos que <code>elNumero</code> no sea un número, haz esto”.</p> -<p><a class="p_ident" id="p-qWA8ZIJYjv" href="#p-qWA8ZIJYjv" tabindex="-1" role="presentation"></a>La sentencia después del <code>if</code> está envuelta entre llaves (<code>{</code> y <code>}</code>) en este ejemplo. Las llaves se pueden usar para agrupar cualquier cantidad de sentencias en una sola sentencia, llamada un <em>bloque</em>. También podrías haber omitido en este caso, ya que contienen solo una sentencia, pero para evitar tener que pensar si son necesarias, la mayoría de los programadores de JavaScript las usan en cada sentencia envuelta de esta manera. Seguiremos principalmente esa convención en este libro, excepto por los casos ocasionales de una sola línea.</p> +<p><a class="p_ident" id="p-pmUn3Gxq/x" href="#p-pmUn3Gxq/x" tabindex="-1" role="presentation"></a>La sentencia después del <code>if</code> está envuelta entre llaves (<code>{</code> y <code>}</code>) en este ejemplo. Las llaves se pueden usar para agrupar cualquier cantidad de sentencias en una sola sentencia llamada <em>bloque</em>. También las podrías haber omitido en este caso, ya que contienen solo una sentencia, pero para evitar tener que pensar si son necesarias, la mayoría de los programadores de JavaScript las usan en todas las sentencias que forman parte de un <code>if</code>. Seguiremos principalmente esa convención en este libro, excepto por ocasionales expresiones de una sola línea (o <em>one-liners</em>).</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Oe35vWsR97" href="#c-Oe35vWsR97" tabindex="-1" role="presentation"></a><span class="tok-keyword">if</span> (<span class="tok-number">1</span> + <span class="tok-number">1</span> == <span class="tok-number">2</span>) console.log(<span class="tok-string">"Es verdad"</span>); <span class="tok-comment">// → Es verdad</span></pre> -<p><a class="p_ident" id="p-Pu3zQ4TgAo" href="#p-Pu3zQ4TgAo" tabindex="-1" role="presentation"></a>A menudo no solo tendrás código que se ejecuta cuando una condición es verdadera, sino también código que maneja el otro caso. Esta ruta alternativa está representada por la segunda flecha en el diagrama. Puedes usar la palabra clave <code>else</code>, junto con <code>if</code>, para crear dos caminos de ejecución alternativos y separados:</p> +<p><a class="p_ident" id="p-Pu3zQ4TgAo" href="#p-Pu3zQ4TgAo" tabindex="-1" role="presentation"></a>A menudo no solo tendrás código que se ejecuta cuando una condición es verdadera, sino también código que se encarga de lo que ocurre en caso contrario. Esta ruta alternativa está representada por la segunda flecha en el diagrama. Puedes usar la palabra clave <code>else</code>, junto con <code>if</code>, para crear dos caminos de ejecución alternativos y separados:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-01vgXGotuf" href="#c-01vgXGotuf" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">elNumero</span> = Number(prompt(<span class="tok-string">"Elige un número"</span>)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NjAA+mEzvG" href="#c-NjAA+mEzvG" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">elNumero</span> = Number(prompt(<span class="tok-string">"Elige un número"</span>)); <span class="tok-keyword">if</span> (!Number.isNaN(elNumero)) { console.log(<span class="tok-string">"Tu número es la raíz cuadrada de "</span> + elNumero * elNumero); } <span class="tok-keyword">else</span> { - console.log(<span class="tok-string">"Oye. ¿Por qué no me diste un número?"</span>); + console.log(<span class="tok-string">"Oye. ¿Por qué no me has dado un número?"</span>); }</pre> -<p><a class="p_ident" id="p-wBEEbOmaGZ" href="#p-wBEEbOmaGZ" tabindex="-1" role="presentation"></a>Si tienes más de dos caminos para elegir, puedes “encadenar” múltiples pares <code>if</code>/<code>else</code>. Aquí tienes un ejemplo:</p> +<p><a class="p_ident" id="p-wBEEbOmaGZ" href="#p-wBEEbOmaGZ" tabindex="-1" role="presentation"></a>Si tienes más de dos caminos entre los que elegir, puedes “encadenar” múltiples pares <code>if</code>/<code>else</code>. Aquí tienes un ejemplo:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Q0hG4Qfs2i" href="#c-Q0hG4Qfs2i" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">num</span> = Number(prompt(<span class="tok-string">"Escoge un número"</span>)); @@ -211,7 +214,7 @@ <h2><a class="h_ident" id="h-iTDXrRhIfn" href="#h-iTDXrRhIfn" tabindex="-1" role <h2 id="loops"><a class="h_ident" id="h-n6ktDf8NGQ" href="#h-n6ktDf8NGQ" tabindex="-1" role="presentation"></a>Bucles while y do</h2> -<p><a class="p_ident" id="p-TNAfAvS4od" href="#p-TNAfAvS4od" tabindex="-1" role="presentation"></a>Considera un programa que imprime todos los números pares de 0 a 12. Una forma de escribirlo es la siguiente:</p> +<p><a class="p_ident" id="p-TNAfAvS4od" href="#p-TNAfAvS4od" tabindex="-1" role="presentation"></a>Considera un programa que muestre en la consola todos los números pares de 0 a 12. Una forma de escribirlo es la siguiente:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NiH5lbw9eg" href="#c-NiH5lbw9eg" tabindex="-1" role="presentation"></a>console.log(<span class="tok-number">0</span>); console.log(<span class="tok-number">2</span>); @@ -221,9 +224,9 @@ <h2 id="loops"><a class="h_ident" id="h-n6ktDf8NGQ" href="#h-n6ktDf8NGQ" tabinde console.log(<span class="tok-number">10</span>); console.log(<span class="tok-number">12</span>);</pre> -<p><a class="p_ident" id="p-bG0ATAFQ8m" href="#p-bG0ATAFQ8m" tabindex="-1" role="presentation"></a>Eso funciona, pero la idea de escribir un programa es hacer <em>menos</em> trabajo, no más. Si necesitáramos todos los números pares menores que 1,000, este enfoque sería inviable. Lo que necesitamos es una manera de ejecutar un fragmento de código múltiples veces. Esta forma de control de flujo se llama <em>bucle</em>.</p><figure><img src="img/controlflow-loop.svg" alt="Diagrama que muestra una flecha que apunta a un punto que tiene una flecha cíclica que regresa a sí mismo y otra flecha que continúa"></figure> +<p><a class="p_ident" id="p-tMc5CDndOi" href="#p-tMc5CDndOi" tabindex="-1" role="presentation"></a>Y eso funciona, pero la idea de escribir un programa es hacer <em>menos</em> trabajo, no más. Si necesitáramos todos los números pares menores que 1000, este enfoque sería inviable. Lo que necesitamos es una manera de ejecutar un fragmento de código múltiples veces. Esta forma de flujo de control se llama <em>bucle</em>.</p><figure><img src="img/controlflow-loop.svg" alt="Diagrama que muestra una flecha que apunta a un punto que tiene una flecha cíclica que regresa a sí mismo y otra flecha que continúa"></figure> -<p><a class="p_ident" id="p-2Ohb6yv25X" href="#p-2Ohb6yv25X" tabindex="-1" role="presentation"></a>El control de flujo mediante bucles nos permite regresar a algún punto en el programa donde estábamos antes y repetirlo con nuestro estado de programa actual. Si combinamos esto con una variable que cuente, podemos hacer algo como esto:</p> +<p><a class="p_ident" id="p-KOYU5zesRQ" href="#p-KOYU5zesRQ" tabindex="-1" role="presentation"></a>El flujo de control mediante bucles nos permite regresar a algún punto en el programa donde estábamos antes y repetirlo con nuestro estado de programa actual. Si combinamos esto con una variable contadora, podemos hacer algo como esto:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-4dT1JqWY0K" href="#c-4dT1JqWY0K" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">numero</span> = <span class="tok-number">0</span>; <span class="tok-keyword">while</span> (numero <= <span class="tok-number">12</span>) { @@ -234,26 +237,26 @@ <h2 id="loops"><a class="h_ident" id="h-n6ktDf8NGQ" href="#h-n6ktDf8NGQ" tabinde <span class="tok-comment">// → 2</span> <span class="tok-comment">// … etcétera</span></pre> -<p><a class="p_ident" id="p-36iEUfXxKy" href="#p-36iEUfXxKy" tabindex="-1" role="presentation"></a>Una sentencia que comienza con la palabra clave <code>while</code> crea un bucle. La palabra <code>while</code> va seguida de una expresión entre paréntesis y luego un enunciado, similar a <code>if</code>. El bucle sigue ejecutando ese enunciado mientras la expresión produzca un valor que se convierta en <code>true</code> al convertirse a Booleano.</p> +<p><a class="p_ident" id="p-36iEUfXxKy" href="#p-36iEUfXxKy" tabindex="-1" role="presentation"></a>Una sentencia que empiece con la palabra clave <code>while</code> crea un bucle. La palabra <code>while</code> va seguida de una expresión entre paréntesis y luego una sentencia, como con el <code>if</code>. El bucle sigue ejecutando esa sentencia mientras la expresión del paréntesis produzca un valor que dé <code>true</code> al convertirse a Booleano.</p> -<p><a class="p_ident" id="p-zTOy1B7/aJ" href="#p-zTOy1B7/aJ" tabindex="-1" role="presentation"></a>El enlace ‘numero’ demuestra la forma en que un enlace puede seguir el progreso de un programa. Cada vez que se repite el bucle, ‘numero’ obtiene un valor que es 2 más que su valor anterior. Al comienzo de cada repetición, se compara con el número 12 para decidir si el trabajo del programa ha terminado.</p> +<p><a class="p_ident" id="p-G4Zb2m4vDY" href="#p-G4Zb2m4vDY" tabindex="-1" role="presentation"></a>El enlace <code>numero</code> demuestra la forma en que un enlace puede seguir el progreso de un programa. Cada vez que se repite el bucle, <code>numero</code> obtiene un valor que es 2 unidades más que su valor anterior. Al comienzo de cada repetición, se compara con el número 12 para decidir si el trabajo del programa ha terminado.</p> <p><a class="p_ident" id="p-IhKlP4aMVK" href="#p-IhKlP4aMVK" tabindex="-1" role="presentation"></a>Como ejemplo de algo realmente útil, ahora podemos escribir un programa que calcule y muestre el valor de 2<sup>10</sup> (2 elevado a la 10ª potencia). Usamos dos enlaces: uno para llevar un seguimiento de nuestro resultado y otro para contar cuántas veces hemos multiplicado este resultado por 2. El bucle comprueba si el segundo enlace ya ha alcanzado 10 y, si no, actualiza ambos enlaces.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9AxUm9n4M9" href="#c-9AxUm9n4M9" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">result</span> = <span class="tok-number">1</span>; -<span class="tok-keyword">let</span> <span class="tok-definition">counter</span> = <span class="tok-number">0</span>; -<span class="tok-keyword">while</span> (counter < <span class="tok-number">10</span>) { - result = result * <span class="tok-number">2</span>; - counter = counter + <span class="tok-number">1</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9qGtVj9Vr2" href="#c-9qGtVj9Vr2" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">resultado</span> = <span class="tok-number">1</span>; +<span class="tok-keyword">let</span> <span class="tok-definition">contador</span> = <span class="tok-number">0</span>; +<span class="tok-keyword">while</span> (contador < <span class="tok-number">10</span>) { + resultado = resultado * <span class="tok-number">2</span>; + contador = contador + <span class="tok-number">1</span>; } -console.log(result); +console.log(resultado); <span class="tok-comment">// → 1024</span></pre> -<p><a class="p_ident" id="p-Ca1o09ICca" href="#p-Ca1o09ICca" tabindex="-1" role="presentation"></a>El contador también podría haber comenzado en <code>1</code> y haber comprobado si era <code><= 10</code>, pero por razones que se harán evidentes en el <a href="04_data.html#array_indexing">Capítulo 4</a>, es buena idea acostumbrarse a contar desde 0.</p> +<p><a class="p_ident" id="p-Ca1o09ICca" href="#p-Ca1o09ICca" tabindex="-1" role="presentation"></a>El contador también podría haber comenzado en <code>1</code> y haber comprobado si era <code><= 10</code>, pero por razones que se harán evidentes en el <a href="04_data.html#array_indexing">Capítulo 4</a>, conviene acostumbrarse a contar desde 0.</p> -<p><a class="p_ident" id="p-/WWtFe+R3M" href="#p-/WWtFe+R3M" tabindex="-1" role="presentation"></a>Ten en cuenta que JavaScript también tiene un operador para la potencia (<code>2 ** 10</code>), que usarías para calcular esto en un código real, pero eso habría arruinado el ejemplo.</p> +<p><a class="p_ident" id="p-gN82FHEguV" href="#p-gN82FHEguV" tabindex="-1" role="presentation"></a>Ten en cuenta que JavaScript también tiene un operador para la potencia (<code>2 ** 10</code>), que sería lo que usarías para calcular esto en un código real —pero entonces nos quedaríamos sin ejemplo.</p> -<p><a class="p_ident" id="p-0oKajUiBua" href="#p-0oKajUiBua" tabindex="-1" role="presentation"></a>Un bucle <code>do</code> es una estructura de control similar a un bucle <code>while</code>. La única diferencia radica en que un bucle <code>do</code> siempre ejecuta su cuerpo al menos una vez, y comienza a probar si debe detenerse solo después de esa primera ejecución. Para reflejar esto, la prueba aparece después del cuerpo del bucle:</p> +<p><a class="p_ident" id="p-0oKajUiBua" href="#p-0oKajUiBua" tabindex="-1" role="presentation"></a>Un bucle <code>do</code> es una estructura de control similar a un bucle <code>while</code>. La única diferencia radica en que un bucle <code>do</code> siempre ejecuta su cuerpo al menos una vez, y comienza a probar si debe detenerse solo después de esa primera ejecución. Para reflejar esto, podemos hacer una comprobación después del cuerpo del bucle:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-o9i6UWcm3g" href="#c-o9i6UWcm3g" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">tuNombre</span>; <span class="tok-keyword">do</span> { @@ -261,28 +264,28 @@ <h2 id="loops"><a class="h_ident" id="h-n6ktDf8NGQ" href="#h-n6ktDf8NGQ" tabinde } <span class="tok-keyword">while</span> (!tuNombre); console.log(<span class="tok-string">"Hola "</span> + tuNombre);</pre> -<p><a class="p_ident" id="p-DuLfPxV3a1" href="#p-DuLfPxV3a1" tabindex="-1" role="presentation"></a>Este programa te obligará a ingresar un nombre. Preguntará una y otra vez hasta que obtenga algo que no sea una cadena vacía. Aplicar el operador <code>!</code> convertirá un valor al tipo Booleano antes de negarlo, y todas las cadenas excepto <code>""</code> se convierten en <code>true</code>. Esto significa que el bucle continúa hasta que proporciones un nombre no vacío.</p> +<p><a class="p_ident" id="p-DuLfPxV3a1" href="#p-DuLfPxV3a1" tabindex="-1" role="presentation"></a>Este programa te obligará a introducir un nombre. Preguntará una y otra vez hasta que obtenga algo que no sea una cadena vacía. Aplicar el operador <code>!</code> convertirá un valor al tipo Booleano antes de negarlo, y todas las cadenas excepto <code>""</code> se convierten en <code>true</code>. Esto significa que el bucle continúa hasta que proporciones un nombre no vacío.</p> <h2><a class="h_ident" id="h-Ii0ETl2x9J" href="#h-Ii0ETl2x9J" tabindex="-1" role="presentation"></a>Sangrado de Código</h2> -<p><a class="p_ident" id="p-Xy21oyeH2U" href="#p-Xy21oyeH2U" tabindex="-1" role="presentation"></a>En los ejemplos, he estado agregando espacios delante de las sentencias que son parte de alguna otra sentencia más grande. Estos espacios no son necesarios: la computadora aceptará el programa perfectamente sin ellos. De hecho, incluso los saltos de línea en los programas son opcionales. Podrías escribir un programa como una sola línea larga si así lo deseas.</p> +<p><a class="p_ident" id="p-EIxizf1CMX" href="#p-EIxizf1CMX" tabindex="-1" role="presentation"></a>En los ejemplos, he estado agregando espacios delante de cada sentencia que forma parte de alguna otra sentencia más grande. Estos espacios no son necesarios: la computadora aceptará el programa perfectamente sin ellos. De hecho, incluso los saltos de línea en los programas son opcionales. Podrías escribir un programa como una sola línea larga si quisieras.</p> -<p><a class="p_ident" id="p-znDKK7dtkr" href="#p-znDKK7dtkr" tabindex="-1" role="presentation"></a>El papel de este sangrado dentro de los bloques es hacer que la estructura del código resalte para los lectores humanos. En el código donde se abren nuevos bloques dentro de otros bloques, puede volverse difícil ver dónde termina un bloque y comienza otro. Con un sangrado adecuado, la forma visual de un programa corresponde a la forma de los bloques dentro de él. A mí me gusta usar dos espacios para cada bloque abierto, pero los gustos difieren: algunas personas usan cuatro espacios y otras usan caracteres de tabulación. Lo importante es que cada nuevo bloque agregue la misma cantidad de espacio.</p> +<p><a class="p_ident" id="p-znDKK7dtkr" href="#p-znDKK7dtkr" tabindex="-1" role="presentation"></a>El papel de este sangrado dentro de los bloques es resaltar la estructura del código para los lectores humanos. En código donde se abren nuevos bloques dentro de otros bloques, puede hacerse complicado ver dónde termina un bloque y comienza otro. Con un sangrado adecuado, la forma visual de un programa corresponde a la forma de los bloques dentro de él. A mí me gusta usar dos espacios para cada bloque abierto, pero los gustos difieren: algunas personas usan cuatro espacios y otras usan caracteres de tabulación. Lo importante es que cada nuevo bloque agregue la misma cantidad de espacio.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-W90w2N6dBd" href="#c-W90w2N6dBd" tabindex="-1" role="presentation"></a><span class="tok-keyword">if</span> (false != true) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-l+Egm3hhDK" href="#c-l+Egm3hhDK" tabindex="-1" role="presentation"></a><span class="tok-keyword">if</span> (false != true) { console.log(<span class="tok-string">"Tiene sentido."</span>); <span class="tok-keyword">if</span> (<span class="tok-number">1</span> < <span class="tok-number">2</span>) { - console.log(<span class="tok-string">"No hay sorpresas ahí."</span>); + console.log(<span class="tok-string">"Sin sorpresas."</span>); } }</pre> <p><a class="p_ident" id="p-FgMgQYGV7f" href="#p-FgMgQYGV7f" tabindex="-1" role="presentation"></a>La mayoría de los programas de edición (incluido el de este libro) ayudarán automáticamente con la sangría adecuada al escribir nuevas líneas.</p> -<h2><a class="h_ident" id="h-k4AXnmqH4l" href="#h-k4AXnmqH4l" tabindex="-1" role="presentation"></a>bucles for</h2> +<h2><a class="h_ident" id="h-mgPDSpUcUD" href="#h-mgPDSpUcUD" tabindex="-1" role="presentation"></a>Bucles for</h2> -<p><a class="p_ident" id="p-b/v1NpkLGq" href="#p-b/v1NpkLGq" tabindex="-1" role="presentation"></a>Muchos bucles siguen el patrón mostrado en los ejemplos de <code>while</code>. Primero se crea una variable de “contador” para rastrear el progreso del bucle. Luego viene un bucle <code>while</code>, generalmente con una expresión de prueba que verifica si el contador ha alcanzado su valor final. Al final del cuerpo del bucle, el contador se actualiza para rastrear el progreso.</p> +<p><a class="p_ident" id="p-b/v1NpkLGq" href="#p-b/v1NpkLGq" tabindex="-1" role="presentation"></a>Muchos bucles siguen el patrón mostrado en los ejemplos de <code>while</code>. Primero se crea una variable “contador” para rastrear el progreso del bucle. Luego viene un bucle <code>while</code>, generalmente con una expresión de prueba que verifica si el contador ha alcanzado su valor final. Al final del cuerpo del bucle, el contador se actualiza para rastrear el progreso.</p> -<p><a class="p_ident" id="p-087KFJUF4R" href="#p-087KFJUF4R" tabindex="-1" role="presentation"></a>Debido a que este patrón es tan común, JavaScript y lenguajes similares proporcionan una forma ligeramente más corta y completa, el bucle <code>for</code>:</p> +<p><a class="p_ident" id="p-dIfowzePhR" href="#p-dIfowzePhR" tabindex="-1" role="presentation"></a>Debido a que este patrón es tan común, JavaScript y lenguajes similares proporcionan una forma ligeramente más corta y entendible, el bucle <code>for</code>:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-luM90AtsUL" href="#c-luM90AtsUL" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">numero</span> = <span class="tok-number">0</span>; numero <= <span class="tok-number">12</span>; numero = numero + <span class="tok-number">2</span>) { console.log(numero); @@ -291,9 +294,9 @@ <h2><a class="h_ident" id="h-k4AXnmqH4l" href="#h-k4AXnmqH4l" tabindex="-1" role <span class="tok-comment">// → 2</span> <span class="tok-comment">// … etcétera</span></pre> -<p><a class="p_ident" id="p-fN0Wqpo2bS" href="#p-fN0Wqpo2bS" tabindex="-1" role="presentation"></a>Este programa es exactamente equivalente al <a href="02_program_structure.html#loops">anterior</a> ejemplo de impresión de números pares. La única diferencia es que todas las declaraciones relacionadas con el “estado” del bucle están agrupadas después de <code>for</code>.</p> +<p><a class="p_ident" id="p-fN0Wqpo2bS" href="#p-fN0Wqpo2bS" tabindex="-1" role="presentation"></a>Este programa es exactamente equivalente al <a href="02_program_structure.html#loops">anterior</a> ejemplo de impresión de números pares en la consola. La única diferencia es que todas las declaraciones relacionadas con el “estado” del bucle están agrupadas después de <code>for</code>.</p> -<p><a class="p_ident" id="p-9Dk5fa1UtV" href="#p-9Dk5fa1UtV" tabindex="-1" role="presentation"></a>Los paréntesis después de la palabra clave <code>for</code> deben contener dos punto y coma. La parte antes del primer punto y coma <em>inicializa</em> el bucle, generalmente definiendo una variable. La segunda parte es la expresión que <em>verifica</em> si el bucle debe continuar. La parte final <em>actualiza</em> el estado del bucle después de cada iteración. En la mayoría de los casos, esto es más corto y claro que un <code>while</code> tradicional.</p> +<p><a class="p_ident" id="p-9Dk5fa1UtV" href="#p-9Dk5fa1UtV" tabindex="-1" role="presentation"></a>Los paréntesis después de la palabra clave <code>for</code> deben contener dos punto y coma. La parte antes del primer punto y coma <em>inicializa</em> el bucle, normalmente definiendo una variable. La segunda parte es la expresión que <em>verifica</em> si el bucle debe continuar. La parte final <em>actualiza</em> el estado del bucle después de cada iteración. En la mayoría de los casos, esto es más corto y claro que un <code>while</code> tradicional.</p> <p><a class="p_ident" id="p-q5iPDIA0KE" href="#p-q5iPDIA0KE" tabindex="-1" role="presentation"></a>Este es el código que calcula 2<sup>10</sup> usando <code>for</code> en lugar de <code>while</code>:</p> @@ -306,7 +309,7 @@ <h2><a class="h_ident" id="h-k4AXnmqH4l" href="#h-k4AXnmqH4l" tabindex="-1" role <h2><a class="h_ident" id="h-SKY+ZrYARv" href="#h-SKY+ZrYARv" tabindex="-1" role="presentation"></a>Saliendo de un bucle</h2> -<p><a class="p_ident" id="p-hzS/J9owHF" href="#p-hzS/J9owHF" tabindex="-1" role="presentation"></a>Hacer que la condición del bucle produzca <code>false</code> no es la única forma en que un bucle puede terminar. La instrucción <code>break</code> tiene el efecto de salir inmediatamente del bucle que la contiene. Su uso se demuestra en el siguiente programa, que encuentra el primer número que es mayor o igual a 20 y divisible por 7:</p> +<p><a class="p_ident" id="p-hzS/J9owHF" href="#p-hzS/J9owHF" tabindex="-1" role="presentation"></a>Hacer que la condición del bucle produzca <code>false</code> no es la única forma en que un bucle puede terminar. La instrucción <code>break</code> tiene el efecto de salir inmediatamente del bucle que la contiene. Su uso se demuestra en el siguiente programa, que encuentra el primer número mayor o igual a 20 que es divisible por 7:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-zHPHsO2cGF" href="#c-zHPHsO2cGF" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">actual</span> = <span class="tok-number">20</span>; ; actual = actual + <span class="tok-number">1</span>) { <span class="tok-keyword">if</span> (actual % <span class="tok-number">7</span> == <span class="tok-number">0</span>) { @@ -320,9 +323,9 @@ <h2><a class="h_ident" id="h-SKY+ZrYARv" href="#h-SKY+ZrYARv" tabindex="-1" role <p><a class="p_ident" id="p-3Dsko/ipk1" href="#p-3Dsko/ipk1" tabindex="-1" role="presentation"></a>La construcción <code>for</code> en el ejemplo no tiene una parte que verifique el final del bucle. Esto significa que el bucle nunca se detendrá a menos que se ejecute la instrucción <code>break</code> dentro de él.</p> -<p><a class="p_ident" id="p-c8yS1V0KKb" href="#p-c8yS1V0KKb" tabindex="-1" role="presentation"></a>Si eliminaras esa declaración <code>break</code> o escribieses accidentalmente una condición final que siempre produzca <code>true</code>, tu programa quedaría atrapado en un <em>bucle infinito</em>. Un programa atrapado en un bucle infinito nunca terminará de ejecutarse, lo cual suele ser algo malo.</p> +<p><a class="p_ident" id="p-q4qJ+93ZrC" href="#p-q4qJ+93ZrC" tabindex="-1" role="presentation"></a>Si eliminaras esa declaración <code>break</code> o escribieses accidentalmente una condición final que siempre produzca <code>true</code>, tu programa quedaría atrapado en un <em>bucle infinito</em>. Un programa atrapado en un bucle infinito nunca terminará de ejecutarse, lo cual suele ser malo.</p> -<p><a class="p_ident" id="p-pInh8lhdpP" href="#p-pInh8lhdpP" tabindex="-1" role="presentation"></a>Si creas un bucle infinito en uno de los ejemplos en estas páginas, generalmente se te preguntará si deseas detener el script después de unos segundos. Si eso falla, deberás cerrar la pestaña en la que estás trabajando para recuperarte.</p> +<p><a class="p_ident" id="p-QiJsr1f8Ma" href="#p-QiJsr1f8Ma" tabindex="-1" role="presentation"></a>Si creas un bucle infinito en uno de los ejemplos en estas páginas, generalmente se te preguntará si deseas detener el script después de unos segundos. Si eso falla, deberás cerrar la pestaña en la que estás trabajando para pararlo.</p> <p><a class="p_ident" id="p-SpplRbvKVL" href="#p-SpplRbvKVL" tabindex="-1" role="presentation"></a>La palabra clave <code>continue</code> es similar a <code>break</code> en que influye en el progreso de un bucle. Cuando se encuentra <code>continue</code> en el cuerpo de un bucle, el control salta fuera del cuerpo y continúa con la siguiente iteración del bucle.</p> @@ -330,39 +333,39 @@ <h2><a class="h_ident" id="h-+CnY1WFDZS" href="#h-+CnY1WFDZS" tabindex="-1" role <p><a class="p_ident" id="p-OmbzX4bGUg" href="#p-OmbzX4bGUg" tabindex="-1" role="presentation"></a>Especialmente al hacer bucles, un programa a menudo necesita “actualizar” un enlace para que contenga un valor basado en el valor anterior de ese enlace.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-aegdj8V5XM" href="#c-aegdj8V5XM" tabindex="-1" role="presentation"></a>counter = counter + <span class="tok-number">1</span>;</pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-VGviBLlVDb" href="#c-VGviBLlVDb" tabindex="-1" role="presentation"></a>contador = contador + <span class="tok-number">1</span>;</pre> <p><a class="p_ident" id="p-vQO+HtxGdF" href="#p-vQO+HtxGdF" tabindex="-1" role="presentation"></a>JavaScript proporciona un atajo para esto:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-/XU8FyoU4+" href="#c-/XU8FyoU4+" tabindex="-1" role="presentation"></a>counter += <span class="tok-number">1</span>;</pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-VzxL+w9AHp" href="#c-VzxL+w9AHp" tabindex="-1" role="presentation"></a>contador += <span class="tok-number">1</span>;</pre> -<p><a class="p_ident" id="p-csY/fb4wsk" href="#p-csY/fb4wsk" tabindex="-1" role="presentation"></a>Atajos similares funcionan para muchos otros operadores, como <code>result *= 2</code> para duplicar <code>result</code> o <code>counter -= 1</code> para contar hacia atrás.</p> +<p><a class="p_ident" id="p-k9sYK52irQ" href="#p-k9sYK52irQ" tabindex="-1" role="presentation"></a>Para muchos otros operadores hay atajos similares, como <code>resultado *= 2</code> para duplicar <code>resultado</code> o <code>contador -= 1</code> para contar hacia atrás.</p> <p><a class="p_ident" id="p-2jbBcAR3za" href="#p-2jbBcAR3za" tabindex="-1" role="presentation"></a>Esto nos permite acortar aún más nuestro ejemplo de contar:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-h8HkwMM+IM" href="#c-h8HkwMM+IM" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">number</span> = <span class="tok-number">0</span>; number <= <span class="tok-number">12</span>; number += <span class="tok-number">2</span>) { - console.log(number); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-WDAcfdyHyg" href="#c-WDAcfdyHyg" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">número</span> = <span class="tok-number">0</span>; número <= <span class="tok-number">12</span>; número += <span class="tok-number">2</span>) { + console.log(número); }</pre> -<p><a class="p_ident" id="p-HLfmDG26B4" href="#p-HLfmDG26B4" tabindex="-1" role="presentation"></a>Para <code>counter += 1</code> y <code>counter -= 1</code>, existen equivalentes aún más cortos: <code>counter++</code> y <code>counter--</code>.</p> +<p><a class="p_ident" id="p-7MweW6WlSL" href="#p-7MweW6WlSL" tabindex="-1" role="presentation"></a>Para <code>contador += 1</code> y <code>contador -= 1</code>, existen equivalentes más cortos aún: <code>contador++</code> y <code>contador--</code>.</p> -<h2><a class="h_ident" id="h-HJMpDD+dJv" href="#h-HJMpDD+dJv" tabindex="-1" role="presentation"></a>Despachar un valor con switch</h2> +<h2><a class="h_ident" id="h-eZRVlMfTEH" href="#h-eZRVlMfTEH" tabindex="-1" role="presentation"></a>Despachar según un valor con switch</h2> -<p><a class="p_ident" id="p-EC5mRWXF2U" href="#p-EC5mRWXF2U" tabindex="-1" role="presentation"></a>No es raro que el código luzca así:</p> +<p><a class="p_ident" id="p-a7WK2eFLam" href="#p-a7WK2eFLam" tabindex="-1" role="presentation"></a>No es raro encontrar código con esta pinta:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ydQ9zTKexk" href="#c-ydQ9zTKexk" tabindex="-1" role="presentation"></a><span class="tok-keyword">if</span> (x == <span class="tok-string">"valor1"</span>) accion1(); <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (x == <span class="tok-string">"valor2"</span>) accion2(); <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (x == <span class="tok-string">"valor3"</span>) accion3(); <span class="tok-keyword">else</span> accionPredeterminada();</pre> -<p><a class="p_ident" id="p-TN80MDBMtL" href="#p-TN80MDBMtL" tabindex="-1" role="presentation"></a>Existe una construcción llamada <code>switch</code> que está destinada a expresar dicho “despacho” de una manera más directa. Desafortunadamente, la sintaxis que JavaScript utiliza para esto (heredada de la línea de lenguajes de programación C/Java) es algo incómoda; una cadena de declaraciones <code>if</code> puede verse mejor. Aquí hay un ejemplo:</p> +<p><a class="p_ident" id="p-TN80MDBMtL" href="#p-TN80MDBMtL" tabindex="-1" role="presentation"></a>Existe una construcción llamada <code>switch</code> que está destinada a expresar dicho “despacho” de una manera más directa. Desafortunadamente, la sintaxis que JavaScript utiliza para esto (heredada de lenguajes de programación en la línea de C/Java) es algo incómoda —una cadena de declaraciones <code>if</code> podría quedar mejor. Aquí hay un ejemplo:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-LHfl9T/hCT" href="#c-LHfl9T/hCT" tabindex="-1" role="presentation"></a><span class="tok-keyword">switch</span> (prompt(<span class="tok-string">"¿Cómo está el clima?"</span>)) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Se77UtUd8a" href="#c-Se77UtUd8a" tabindex="-1" role="presentation"></a><span class="tok-keyword">switch</span> (prompt(<span class="tok-string">"¿Cómo está el clima?"</span>)) { <span class="tok-keyword">case</span> <span class="tok-string">"lluvioso"</span>: console.log(<span class="tok-string">"Recuerda llevar un paraguas."</span>); <span class="tok-keyword">break</span>; <span class="tok-keyword">case</span> <span class="tok-string">"soleado"</span>: - console.log(<span class="tok-string">"Vístete ligero."</span>); + console.log(<span class="tok-string">"Vístete con ropa ligera."</span>); <span class="tok-keyword">case</span> <span class="tok-string">"nublado"</span>: console.log(<span class="tok-string">"Sal al exterior."</span>); <span class="tok-keyword">break</span>; @@ -371,41 +374,41 @@ <h2><a class="h_ident" id="h-HJMpDD+dJv" href="#h-HJMpDD+dJv" tabindex="-1" role <span class="tok-keyword">break</span>; }</pre> -<p><a class="p_ident" id="p-Ebda3Pwhl6" href="#p-Ebda3Pwhl6" tabindex="-1" role="presentation"></a>Puedes colocar cualquier cantidad de etiquetas <code>case</code> dentro del bloque abierto por <code>switch</code>. El programa comenzará a ejecutarse en la etiqueta que corresponda al valor que se le dio a <code>switch</code>, o en <code>default</code> si no se encuentra ningún valor coincidente. Continuará ejecutándose, incluso a través de otras etiquetas, hasta que alcance una declaración <code>break</code>. En algunos casos, como el caso <code>"soleado"</code> en el ejemplo, esto se puede usar para compartir algo de código entre casos (recomienda salir al exterior tanto para el clima soleado como para el nublado). Sin embargo, ten cuidado, es fácil olvidar un <code>break</code> de este tipo, lo que hará que el programa ejecute código que no deseas ejecutar.</p> +<p><a class="p_ident" id="p-Ebda3Pwhl6" href="#p-Ebda3Pwhl6" tabindex="-1" role="presentation"></a>Puedes colocar cualquier cantidad de etiquetas <code>case</code> dentro del cuerpo de <code>switch</code>. El programa comenzará a ejecutarse en la etiqueta que corresponda al valor que se le dio a <code>switch</code>, o en <code>default</code>, si no se encuentra ningún valor coincidente. Continuará ejecutándose, incluso a través de otras etiquetas, hasta que alcance una declaración <code>break</code>. En algunos casos, como el caso <code>"soleado"</code> del ejemplo, esto se puede usar para compartir algo de código entre casos (recomienda salir al exterior tanto para el clima soleado como para el nublado). Pero ten cuidado, es fácil olvidar un <code>break</code> de este tipo, lo que hará que el programa ejecute código que no deseas ejecutar.</p> <h2><a class="h_ident" id="h-uaN9rBKlOx" href="#h-uaN9rBKlOx" tabindex="-1" role="presentation"></a>Uso de mayúsculas</h2> -<p><a class="p_ident" id="p-LPEByvMVpz" href="#p-LPEByvMVpz" tabindex="-1" role="presentation"></a>Los nombres de los enlaces no pueden contener espacios, sin embargo, 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:</p> +<p><a class="p_ident" id="p-LPEByvMVpz" href="#p-LPEByvMVpz" tabindex="-1" role="presentation"></a>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:</p> <pre class="snippet" data-language="null" ><a class="c_ident" id="c-HtUFwP9TF9" href="#c-HtUFwP9TF9" tabindex="-1" role="presentation"></a>fuzzylittleturtle fuzzy_little_turtle FuzzyLittleTurtle fuzzyLittleTurtle</pre> -<p><a class="p_ident" id="p-p1W8F/2VE7" href="#p-p1W8F/2VE7" tabindex="-1" role="presentation"></a>El primer estilo puede ser difícil de leer. Personalmente me gusta más la apariencia de los guiones bajos, aunque ese estilo es un poco difícil de escribir. Las funciones estándar de JavaScript y la mayoría de los programadores de JavaScript siguen el último estilo: escriben con mayúscula cada palabra excepto la primera. No es difícil acostumbrarse a pequeñas cosas como esa, y el código con estilos de nombrado mixtos puede resultar molesto de leer, así que seguimos esta convención.</p> +<p><a class="p_ident" id="p-7f2h/V6EQk" href="#p-7f2h/V6EQk" tabindex="-1" role="presentation"></a>El primer estilo puede ser difícil de leer. Personalmente me gusta más la apariencia de los guiones bajos, aunque ese estilo es un poco difícil de escribir. Las funciones estándar de JavaScript y la mayoría de los programadores de JavaScript siguen el último estilo: escriben con mayúscula cada palabra excepto la primera. No es difícil adoptar pequeñas costumbres como esta, y el código con estilos de nombrado mixtos puede resultar molesto de leer, así que seguiremos esta última convención.</p> <p><a class="p_ident" id="p-jCGMhIRLKD" href="#p-jCGMhIRLKD" tabindex="-1" role="presentation"></a>En algunos casos, como en la función <code>Number</code>, la primera letra de un enlace también está en mayúscula. Esto se hizo para marcar esta función como un constructor. Quedará claro lo que es un constructor en el <a href="06_object.html#constructors">Capítulo 6</a>. Por ahora, lo importante es no molestarse por esta aparente falta de consistencia.</p> <h2><a class="h_ident" id="h-BU3lBD6Cl5" href="#h-BU3lBD6Cl5" tabindex="-1" role="presentation"></a>Comentarios</h2> -<p><a class="p_ident" id="p-HxjnUCWytj" href="#p-HxjnUCWytj" tabindex="-1" role="presentation"></a>A menudo, el código sin formato no transmite toda la información que deseas que un programa transmita a los lectores humanos, o lo hace de una manera tan críptica que las personas podrían no entenderlo. En otros momentos, es posible que solo quieras incluir algunos pensamientos relacionados como parte de tu programa. Para eso sirven los <em>comentarios</em>.</p> +<p><a class="p_ident" id="p-HxjnUCWytj" href="#p-HxjnUCWytj" tabindex="-1" role="presentation"></a>A menudo, el código sin formato no transmite toda la información que quieres que un programa transmita a los lectores humanos, o lo hace de una manera tan críptica que la gente podría no entenderlo. Otras veces, es posible que solo quieras incluir algunos pensamientos relacionados como parte de tu programa. Para eso sirven los <em>comentarios</em>.</p> <p><a class="p_ident" id="p-7Y56DjyRCG" href="#p-7Y56DjyRCG" tabindex="-1" role="presentation"></a>Un comentario es un fragmento de texto que forma parte de un programa pero que es completamente ignorado por la computadora. JavaScript tiene dos formas de escribir comentarios. Para escribir un comentario de una sola línea, puedes usar dos caracteres de barra (<code>//</code>) y luego el texto del comentario después de eso:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Jwoo61SRx6" href="#c-Jwoo61SRx6" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">saldoCuenta</span> = calcularSaldo(cuenta); -<span class="tok-comment">// Es un hueco verde donde canta un río</span> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-GA8ZZh0lVD" href="#c-GA8ZZh0lVD" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">saldoCuenta</span> = calcularSaldo(cuenta); +<span class="tok-comment">// Al olmo viejo, hendido por el rayo</span> saldoCuenta.ajustar(); -<span class="tok-comment">// Atrapando locamente pedazos blancos en la hierba.</span> +<span class="tok-comment">// y en su mitad podrido,</span> <span class="tok-keyword">let</span> <span class="tok-definition">informe</span> = <span class="tok-keyword">new</span> Informe(); -<span class="tok-comment">// Donde el sol en la orgullosa montaña resuena:</span> +<span class="tok-comment">// con las lluvias de abril y el sol de mayo</span> agregarAInforme(saldoCuenta, informe); -<span class="tok-comment">// Es un valle pequeño, espumoso como la luz en un vaso.</span></pre> +<span class="tok-comment">// algunas hojas verdes le han salido.</span></pre> -<p><a class="p_ident" id="p-RiQt53UNig" href="#p-RiQt53UNig" tabindex="-1" role="presentation"></a>Un comentario con <code>//</code> solo va hasta el final de la línea. Una sección de texto entre <code>/*</code> y <code>*/</code> será ignorada por completo, independientemente de si contiene saltos de línea. Esto es útil para agregar bloques de información sobre un archivo o un fragmento de programa:</p> +<p><a class="p_ident" id="p-RiQt53UNig" href="#p-RiQt53UNig" tabindex="-1" role="presentation"></a>Un comentario con <code>//</code> solo va hasta el final de la línea. Una sección de texto entre <code>/*</code> y <code>*/</code> será ignorada por completo, independientemente de si contiene saltos de línea o no. Esto es útil para agregar bloques de información sobre un archivo o un fragmento de programa:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-JrXwf+0z7J" href="#c-JrXwf+0z7J" tabindex="-1" role="presentation"></a><span class="tok-comment">/*</span> -<span class="tok-comment"> Encontré este número por primera vez garabateado en la parte posterior de un viejo</span> -<span class="tok-comment"> cuaderno. Desde entonces, a menudo ha aparecido, mostrándose en</span> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-TErmbVw8Jm" href="#c-TErmbVw8Jm" tabindex="-1" role="presentation"></a><span class="tok-comment">/*</span> +<span class="tok-comment"> Encontré este número por primera vez garabateado en la parte de atrás de un viejo</span> +<span class="tok-comment"> cuaderno. Desde entonces, ha aparecido con frecuencia en</span> <span class="tok-comment"> números de teléfono y números de serie de productos que he</span> <span class="tok-comment"> comprado. Obviamente le gusto, así que he decidido quedármelo.</span> <span class="tok-comment">*/</span> @@ -413,17 +416,17 @@ <h2><a class="h_ident" id="h-BU3lBD6Cl5" href="#h-BU3lBD6Cl5" tabindex="-1" role <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-f/OQzkkDiz" href="#p-f/OQzkkDiz" tabindex="-1" role="presentation"></a>Ahora sabes que un programa está construido a partir de declaraciones, que a veces contienen más declaraciones. Las declaraciones tienden a contener expresiones, que a su vez pueden estar construidas a partir de expresiones más pequeñas. Poner declaraciones una después de la otra te da un programa que se ejecuta de arriba hacia abajo. Puedes introducir alteraciones en el flujo de control usando declaraciones condicionales (<code>if</code>, <code>else</code> y <code>switch</code>) y bucles (<code>while</code>, <code>do</code> y <code>for</code>).</p> +<p><a class="p_ident" id="p-f/OQzkkDiz" href="#p-f/OQzkkDiz" tabindex="-1" role="presentation"></a>Ahora sabes que un programa está construido a partir de declaraciones (o sentencias), que a veces contienen más declaraciones. Las declaraciones tienden a contener expresiones, que a su vez pueden estar construidas a partir de expresiones más pequeñas. Poner declaraciones una después de la otra te da un programa que se ejecuta de arriba hacia abajo. Puedes introducir alteraciones en el flujo de control usando sentencias condicionales (<code>if</code>, <code>else</code> y <code>switch</code>) y bucles (<code>while</code>, <code>do</code> y <code>for</code>).</p> -<p><a class="p_ident" id="p-rcZwELYOMD" href="#p-rcZwELYOMD" tabindex="-1" role="presentation"></a>Los enlaces se pueden usar para guardar fragmentos de datos bajo un nombre, y son útiles para hacer un seguimiento del estado en tu programa. El entorno es el conjunto de enlaces que están definidos. Los sistemas de JavaScript siempre colocan varios enlaces estándar útiles en tu entorno.</p> +<p><a class="p_ident" id="p-rcZwELYOMD" href="#p-rcZwELYOMD" tabindex="-1" role="presentation"></a>Los enlaces se pueden usar para guardar fragmentos de datos bajo un nombre, y son útiles para hacer un seguimiento del estado en tu programa. El entorno es el conjunto de enlaces que están definidos. Los sistemas de JavaScript siempre ponen varios enlaces estándar útiles en tu entorno.</p> <p><a class="p_ident" id="p-lh+UoMXkIt" href="#p-lh+UoMXkIt" tabindex="-1" role="presentation"></a>Las funciones son valores especiales que encapsulan un fragmento de programa. Puedes invocarlas escribiendo <code>nombreDeFuncion(argumento1, argumento2)</code>. Dicha llamada a función es una expresión y puede producir un valor.</p> <h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2> -<p><a class="p_ident" id="p-3Wokp0YSAj" href="#p-3Wokp0YSAj" tabindex="-1" role="presentation"></a>Si no estás seguro de cómo probar tus soluciones a los ejercicios, consulta la <a href="00_intro.html">Introducción</a>.</p> +<p><a class="p_ident" id="p-Vw2WvEdyD9" href="#p-Vw2WvEdyD9" tabindex="-1" role="presentation"></a>Si no sabes cómo comprobar tus soluciones a los ejercicios, consulta la <a href="00_intro.html">Introducción</a>.</p> -<p><a class="p_ident" id="p-8sD0jXsHvJ" href="#p-8sD0jXsHvJ" tabindex="-1" role="presentation"></a>Cada ejercicio comienza con una descripción del problema. Lee esta descripción e intenta resolver el ejercicio. Si encuentras problemas, considera leer las pistas después del ejercicio. Puedes encontrar soluciones completas a los ejercicios en línea en <a href="https://eloquentjavascript.net/code#2"><em>https://eloquentjavascript.net/code</em></a>. Si deseas aprender algo de los ejercicios, te recomiendo mirar las soluciones solo después de haber resuelto el ejercicio, o al menos después de haberlo intentado lo suficiente como para tener un ligero dolor de cabeza.</p> +<p><a class="p_ident" id="p-8sD0jXsHvJ" href="#p-8sD0jXsHvJ" tabindex="-1" role="presentation"></a>Cada ejercicio comienza con una descripción del problema. Léela e intenta resolver el ejercicio. Si tienes problemas, considera leer las pistas después del ejercicio. Puedes encontrar soluciones completas a los ejercicios en línea en <a href="https://eloquentjavascript.net/code#2"><em>https://eloquentjavascript.net/code</em></a>. Si quieres aprender algo de los ejercicios, te recomiendo mirar las soluciones solo después de haber resuelto el ejercicio, o al menos después de haberlo intentado lo suficiente como para tener un ligero dolor de cabeza.</p> <h3><a class="i_ident" id="i-ThHo8CNjUY" href="#i-ThHo8CNjUY" tabindex="-1" role="presentation"></a>Haciendo un triángulo con bucles</h3> @@ -437,7 +440,7 @@ <h3><a class="i_ident" id="i-ThHo8CNjUY" href="#i-ThHo8CNjUY" tabindex="-1" role ###### #######</pre> -<p><a class="p_ident" id="p-HnTbcF9d+w" href="#p-HnTbcF9d+w" tabindex="-1" role="presentation"></a>Puede ser útil saber que puedes encontrar la longitud de una cadena escribiendo <code>.length</code> después de ella.</p> +<p><a class="p_ident" id="p-HnTbcF9d+w" href="#p-HnTbcF9d+w" tabindex="-1" role="presentation"></a>Puede ser útil saber que puedes calcular la longitud de una cadena escribiendo <code>.length</code> después de ella.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-qHvXAGuPvV" href="#c-qHvXAGuPvV" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">abc</span> = <span class="tok-string">"abc"</span>; console.log(abc.length); @@ -451,7 +454,7 @@ <h3><a class="i_ident" id="i-ThHo8CNjUY" href="#i-ThHo8CNjUY" tabindex="-1" role <p><a class="p_ident" id="p-DpgPWjXISq" href="#p-DpgPWjXISq" tabindex="-1" role="presentation"></a>Puedes comenzar con un programa que imprime los números del 1 al 7, el cual puedes obtener haciendo algunas modificaciones al ejemplo de impresión de números pares dado anteriormente en el capítulo, donde se introdujo el bucle <code>for</code>.</p> -<p><a class="p_ident" id="p-iFIl1kilIr" href="#p-iFIl1kilIr" tabindex="-1" role="presentation"></a>Ahora considera la equivalencia entre los números y las cadenas de caracteres "#" . Puedes pasar de 1 a 2 sumando 1 (<code>+= 1</code>). Puedes pasar de <code>"#"</code> a <code>"##"</code> agregando un carácter (<code>+= "#"</code>). Por lo tanto, tu solución puede seguir de cerca el programa de impresión de números.</p> +<p><a class="p_ident" id="p-tjWvssIayu" href="#p-tjWvssIayu" tabindex="-1" role="presentation"></a>Luego, considera la equivalencia entre los números y las cadenas de caracteres "#" . Puedes pasar de 1 a 2 sumando 1 (<code>+= 1</code>). Puedes pasar de <code>"#"</code> a <code>"##"</code> agregando un carácter (<code>+= "#"</code>). Por lo tanto, tu solución puede basarse fuertemente en el programa de impresión de números.</p> </div></details> @@ -459,19 +462,19 @@ <h3><a class="i_ident" id="i-rebKE3gdjV" href="#i-rebKE3gdjV" tabindex="-1" role <p><a class="p_ident" id="p-sN81xT6Ls4" href="#p-sN81xT6Ls4" tabindex="-1" role="presentation"></a>Escribe un programa que use <code>console.log</code> para imprimir todos los números del 1 al 100, con dos excepciones. Para los números divisibles por 3, imprime <code>"Fizz"</code> en lugar del número, y para los números divisibles por 5 (y no por 3), imprime <code>"Buzz"</code> en su lugar.</p> -<p><a class="p_ident" id="p-RDYVgc4FLA" href="#p-RDYVgc4FLA" tabindex="-1" role="presentation"></a>Cuando tengas eso funcionando, modifica tu programa para imprimir <code>"FizzBuzz"</code> para los números que son divisibles por 3 y 5 (y sigue imprimiendo <code>"Fizz"</code> o <code>"Buzz"</code> para los números que son divisibles solo por uno de esos).</p> +<p><a class="p_ident" id="p-E9o5g3i4vC" href="#p-E9o5g3i4vC" tabindex="-1" role="presentation"></a>Cuando eso esté listo, modifica tu programa para imprimir <code>"FizzBuzz"</code> para los números que son divisibles por 3 y 5 (y sigue imprimiendo <code>"Fizz"</code> o <code>"Buzz"</code> para los números que son divisibles solo por uno de esos).</p> -<p><a class="p_ident" id="p-xiZio9jEj5" href="#p-xiZio9jEj5" tabindex="-1" role="presentation"></a>(Esto es en realidad una pregunta de entrevista que se ha afirmado que elimina a un porcentaje significativo de candidatos a programadores. Entonces, si lo resolviste, tu valor en el mercado laboral acaba de aumentar.)</p> +<p><a class="p_ident" id="p-xiZio9jEj5" href="#p-xiZio9jEj5" tabindex="-1" role="presentation"></a>(Esto es en realidad una pregunta de entrevista que se ha afirmado que elimina a un porcentaje significativo de candidatos a programadores. Por tanto, si lo resolviste, tu valor en el mercado laboral acaba de aumentar.)</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ROnwerR8AG_1" href="#c-ROnwerR8AG_1" tabindex="-1" role="presentation"></a><span class="tok-comment">// Tu código aquí.</span></pre> <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-FnPgHxuR9J" href="#p-FnPgHxuR9J" tabindex="-1" role="presentation"></a>Claramente, recorrer los números es un trabajo de bucle, y seleccionar qué imprimir es una cuestión de ejecución condicional. Recuerda el truco de usar el operador de resto (<code>%</code>) para verificar si un número es divisible por otro número (tiene un resto de cero).</p> +<p><a class="p_ident" id="p-FnPgHxuR9J" href="#p-FnPgHxuR9J" tabindex="-1" role="presentation"></a>Claramente, recorrer los números es tarea para un bucle, y seleccionar qué imprimir es una cuestión de ejecución condicional. Recuerda el truco de usar el operador de resto (<code>%</code>) para verificar si un número es divisible por otro número (tiene un resto de cero).</p> -<p><a class="p_ident" id="p-DgEYFa1d1o" href="#p-DgEYFa1d1o" tabindex="-1" role="presentation"></a>En la primera versión, hay tres resultados posibles para cada número, por lo que tendrás que crear una cadena <code>if</code>/<code>else if</code>/<code>else</code>.</p> +<p><a class="p_ident" id="p-oYpHxgkxw/" href="#p-oYpHxgkxw/" tabindex="-1" role="presentation"></a>En la primera versión, hay tres resultados posibles para cada número, por lo que tendrás que crear una secuencia <code>if</code>/<code>else if</code>/<code>else</code>.</p> -<p><a class="p_ident" id="p-wu2JrShz/M" href="#p-wu2JrShz/M" tabindex="-1" role="presentation"></a>La segunda versión del programa tiene una solución sencilla y una inteligente. La solución simple es agregar otra “rama” condicional para probar exactamente la condición dada. Para la solución inteligente, construye una cadena que contenga la palabra o palabras a imprimir e imprime esta palabra o el número si no hay palabra, potencialmente haciendo un buen uso del operador <code>||</code>.</p> +<p><a class="p_ident" id="p-wu2JrShz/M" href="#p-wu2JrShz/M" tabindex="-1" role="presentation"></a>La segunda versión del programa tiene una solución sencilla y una inteligente. La solución sencilla es agregar otra “rama” condicional para probar exactamente la condición dada. Para la solución inteligente, construye una cadena que contenga la palabra o palabras a imprimir e imprime esta palabra o el número si no hubiera palabra, potencialmente haciendo un buen uso del operador <code>||</code>.</p> </div></details> @@ -479,7 +482,7 @@ <h3><a class="i_ident" id="i-48yefwrYvK" href="#i-48yefwrYvK" tabindex="-1" role <p><a class="p_ident" id="p-eZy03w3wJ3" href="#p-eZy03w3wJ3" tabindex="-1" role="presentation"></a>Escribe un programa que cree una cadena que represente un tablero de 8x8, usando caracteres de salto de línea para separar las líneas. En cada posición del tablero hay un carácter de espacio o un carácter "#". Los caracteres deben formar un tablero de ajedrez.</p> -<p><a class="p_ident" id="p-yoH2dRdE5H" href="#p-yoH2dRdE5H" tabindex="-1" role="presentation"></a>Al pasar esta cadena a <code>console.log</code> debería mostrar algo como esto:</p> +<p><a class="p_ident" id="p-yoH2dRdE5H" href="#p-yoH2dRdE5H" tabindex="-1" role="presentation"></a>Al pasar esta cadena a <code>console.log</code>, debería mostrar algo como esto:</p> <pre class="snippet" data-language="null" ><a class="c_ident" id="c-7yVx5TrcJ/" href="#c-7yVx5TrcJ/" tabindex="-1" role="presentation"></a> # # # # # # # # @@ -496,9 +499,9 @@ <h3><a class="i_ident" id="i-48yefwrYvK" href="#i-48yefwrYvK" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-Gr7cParUG1" href="#p-Gr7cParUG1" tabindex="-1" role="presentation"></a>Para trabajar con dos dimensiones, necesitarás un bucle dentro de otro bucle. Pon llaves alrededor de los cuerpos de ambos bucles para que sea fácil ver dónde empiezan y terminan. Intenta indentar correctamente estos cuerpos. El orden de los bucles debe seguir el orden en el que construimos la cadena (línea por línea, de izquierda a derecha, de arriba abajo). Entonces el bucle exterior maneja las líneas y el bucle interior maneja los caracteres en una línea.</p> +<p><a class="p_ident" id="p-Gr7cParUG1" href="#p-Gr7cParUG1" tabindex="-1" role="presentation"></a>Para trabajar con dos dimensiones, necesitarás un bucle dentro de otro bucle. Pon llaves alrededor de los cuerpos de ambos bucles para que sea fácil ver dónde empiezan y terminan. Intenta añadir sangrado (o <em>indentar</em>) correctamente a estos cuerpos. El orden de los bucles debe seguir el orden en el que construimos la cadena (línea por línea, de izquierda a derecha, de arriba abajo). Entonces el bucle exterior maneja las líneas y el bucle interior maneja los caracteres en una línea.</p> -<p><a class="p_ident" id="p-QwX2iYYwbo" href="#p-QwX2iYYwbo" tabindex="-1" role="presentation"></a>Necesitarás dos variables para hacer un seguimiento de tu progreso. Para saber si debes colocar un espacio o un signo de hash en una posición determinada, podrías verificar si la suma de los dos contadores es par (<code>% 2</code>).</p> +<p><a class="p_ident" id="p-QwX2iYYwbo" href="#p-QwX2iYYwbo" tabindex="-1" role="presentation"></a>Necesitarás dos variables para hacer un seguimiento de tu progreso. Para saber si debes colocar un espacio o un signo de almohadilla en una posición determinada, podrías verificar si la suma de los dos contadores es par (<code>% 2</code>).</p> <p><a class="p_ident" id="p-YhRlDyis8S" href="#p-YhRlDyis8S" tabindex="-1" role="presentation"></a>Terminar una línea agregando un carácter de salto de línea debe ocurrir después de que se haya construido la línea, así que hazlo después del bucle interno pero dentro del bucle externo.</p> diff --git a/html/03_functions.html b/html/03_functions.html index 2d101209..70cf061f 100644 --- a/html/03_functions.html +++ b/html/03_functions.html @@ -14,59 +14,62 @@ <h1>Funciones</h1> <blockquote> -<p><a class="p_ident" id="p-SZ6RJ5C7yk" href="#p-SZ6RJ5C7yk" tabindex="-1" role="presentation"></a>La gente piensa que la informática es el arte de los genios, pero la realidad actual es la opuesta, simplemente muchas personas haciendo cosas que se construyen unas sobre otras, como un muro de mini piedras.</p> +<p><a class="p_ident" id="p-Bm5bb5wBKe" href="#p-Bm5bb5wBKe" tabindex="-1" role="presentation"></a>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.</p> <footer>Donald Knuth</footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_3.jpg" alt="Ilustración de hojas de helecho con una forma fractal, abejas en el fondo"></figure> -<p><a class="p_ident" id="p-CgSvbtVPMF" href="#p-CgSvbtVPMF" tabindex="-1" role="presentation"></a>Las funciones son una de las herramientas más centrales en la programación en JavaScript. El concepto de envolver un fragmento de programa en un valor tiene muchos usos. 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í.</p> +<p><a class="p_ident" id="p-CgSvbtVPMF" href="#p-CgSvbtVPMF" tabindex="-1" role="presentation"></a>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í.</p> -<p><a class="p_ident" id="p-5S1r7LDeWB" href="#p-5S1r7LDeWB" tabindex="-1" role="presentation"></a>La aplicación más evidente de las funciones es definir nuevo vocabulario. Crear palabras nuevas en el lenguaje escrito suele ser de mal gusto, pero en programación es indispensable.</p> +<p><a class="p_ident" id="p-5S1r7LDeWB" href="#p-5S1r7LDeWB" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-9hTHeO/PZx" href="#p-9hTHeO/PZx" tabindex="-1" role="presentation"></a>Los hablantes de inglés adultos típicos tienen alrededor de 20,000 palabras en su vocabulario. Pocos lenguajes de programación vienen con 20,000 comandos incorporados. Y el vocabulario que <em>está</em> disponible tiende a estar más precisamente definido, y por lo tanto menos flexible, que en el lenguaje humano. Por lo tanto, <em>tenemos</em> que introducir nuevas palabras para evitar la verbosidad excesiva.</p> +<p><a class="p_ident" id="p-VcQzCfaHsj" href="#p-VcQzCfaHsj" tabindex="-1" role="presentation"></a>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 <em>hay</em> 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, <em>tenemos</em> que introducir palabras nuevas para evitar una verbosidad excesiva.</p> <h2><a class="h_ident" id="h-eHXd+T1Ezs" href="#h-eHXd+T1Ezs" tabindex="-1" role="presentation"></a>Definir una función</h2> -<p><a class="p_ident" id="p-5jo0JygqfX" href="#p-5jo0JygqfX" tabindex="-1" role="presentation"></a>Una definición de función es un enlace habitual donde el valor del enlace es una función. Por ejemplo, este código define <code>square</code> para que se refiera a una función que produce el cuadrado de un número dado:</p> +<p><a class="p_ident" id="p-5jo0JygqfX" href="#p-5jo0JygqfX" tabindex="-1" role="presentation"></a>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 <code>cuadrado</code> para referirse a una función que produce el cuadrado de un número dado:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-JW7vLBZMWv" href="#c-JW7vLBZMWv" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">square</span> = <span class="tok-keyword">function</span>(<span class="tok-definition">x</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-G07/lIxTsw" href="#c-G07/lIxTsw" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">cuadrado</span> = <span class="tok-keyword">function</span>(<span class="tok-definition">x</span>) { <span class="tok-keyword">return</span> x * x; }; -console.log(square(<span class="tok-number">12</span>)); +console.log(cuadrado(<span class="tok-number">12</span>)); <span class="tok-comment">// → 144</span></pre> -<p><a class="p_ident" id="p-Wttl/7EpwM" href="#p-Wttl/7EpwM" tabindex="-1" role="presentation"></a>Una función se crea con una expresión que comienza con la palabra clave <code>function</code>. Las funciones tienen un conjunto de <em>parámetros</em> (en este caso, solo <code>x</code>) y un <em>cuerpo</em>, 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 cuando consiste en una única declaración.</p> +<p><a class="p_ident" id="p-Wttl/7EpwM" href="#p-Wttl/7EpwM" tabindex="-1" role="presentation"></a>Una función se crea con una expresión que comienza con la palabra clave <code>function</code>. Las funciones tienen un conjunto de <em>parámetros</em> (en este ejemplo, solo <code>x</code>) y un <em>cuerpo</em>, 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.</p> -<p><a class="p_ident" id="p-vrjpkACz/x" href="#p-vrjpkACz/x" tabindex="-1" role="presentation"></a>Una función puede tener varios parámetros o ninguno en absoluto. En el siguiente ejemplo, <code>makeNoise</code> no enumera nombres de parámetros, mientras que <code>roundTo</code> (que redondea <code>n</code> al múltiplo más cercano de <code>step</code>) enumera dos:</p> +<p><a class="p_ident" id="p-vrjpkACz/x" href="#p-vrjpkACz/x" tabindex="-1" role="presentation"></a>Una función puede tener varios parámetros o ninguno en absoluto. En el siguiente ejemplo, <code>hacerRuido</code> no enumera nombre de parámetro alguno, mientras que <code>redondearA</code> (que redondea <code>n</code> al múltiplo más cercano de <code>paso</code>) enumera dos:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-vbZSOJZXVS" href="#c-vbZSOJZXVS" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">makeNoise</span> = <span class="tok-keyword">function</span>() { - console.log(<span class="tok-string">"¡Pling!"</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-/PX5oGPvDg" href="#c-/PX5oGPvDg" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">hacerRuido</span> = <span class="tok-keyword">function</span>() { + console.log(<span class="tok-string">"¡Cling!"</span>); }; -makeNoise(); -<span class="tok-comment">// → ¡Pling!</span> +hacerRuido(); +<span class="tok-comment">// → ¡Cling!</span> -<span class="tok-keyword">const</span> <span class="tok-definition">roundTo</span> = <span class="tok-keyword">function</span>(<span class="tok-definition">n</span>, <span class="tok-definition">step</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">resto</span> = n % step; - <span class="tok-keyword">return</span> n - resto + (resto < step / <span class="tok-number">2</span> ? <span class="tok-number">0</span> : step); +<span class="tok-keyword">const</span> <span class="tok-definition">redondearA</span> = <span class="tok-keyword">function</span>(<span class="tok-definition">n</span>, <span class="tok-definition">paso</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">resto</span> = n % paso; + <span class="tok-keyword">return</span> n - resto + (resto < paso / <span class="tok-number">2</span> ? <span class="tok-number">0</span> : paso); }; -console.log(roundTo(<span class="tok-number">23</span>, <span class="tok-number">10</span>)); +console.log(redondearA(<span class="tok-number">23</span>, <span class="tok-number">10</span>)); <span class="tok-comment">// → 20</span></pre> -<p><a class="p_ident" id="p-1DcRwWewzX" href="#p-1DcRwWewzX" tabindex="-1" role="presentation"></a>Algunas funciones, como <code>roundTo</code> y <code>square</code>, producen un valor, y otras no, como <code>makeNoise</code>, cuyo único resultado es un efecto secundario. Una instrucción <code>return</code> 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. Una palabra clave <code>return</code> sin una expresión después de ella hará que la función devuelva <code>undefined</code>. Las funciones que no tienen ninguna instrucción <code>return</code> en absoluto, como <code>makeNoise</code>, devuelven igualmente <code>undefined</code>.</p> +<p><a class="p_ident" id="p-SHVIwEo/Wg" href="#p-SHVIwEo/Wg" tabindex="-1" role="presentation"></a>Algunas funciones, como <code>redondearA</code> y <code>cuadrado</code>, producen un valor, y otras no, como <code>hacerRuido</code>, cuyo único resultado es un efecto secundario. Una instrucción <code>return</code> 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 <code>return</code> se usa sin una expresión después de ella, la función devolverá <code>undefined</code>. Las funciones que no tienen ninguna instrucción <code>return</code>, como <code>hacerRuido</code>, también devuelven <code>undefined</code>.</p> -<p><a class="p_ident" id="p-skIegUpOjB" href="#p-skIegUpOjB" tabindex="-1" role="presentation"></a>Los parámetros de una función se comportan como enlaces habituales, pero sus valores iniciales son dados por el <em>llamador</em> de la función, no por el código en la función en sí misma.</p> +<p><a class="p_ident" id="p-ARnrqQSLJF" href="#p-ARnrqQSLJF" tabindex="-1" role="presentation"></a>Los parámetros de una función se comportan como asociaciones habituales, pero sus valores iniciales son dados por el <em>llamador</em> de la función, no por el propio código de la función.</p> -<h2><a class="h_ident" id="h-wxzpzC5WZf" href="#h-wxzpzC5WZf" tabindex="-1" role="presentation"></a>Enlaces y ámbitos</h2> +<h2><a class="h_ident" id="h-tkZdkO6of4" href="#h-tkZdkO6of4" tabindex="-1" role="presentation"></a>Asociaciones y ámbitos</h2> -<p><a class="p_ident" id="p-whOg6+ORvy" href="#p-whOg6+ORvy" tabindex="-1" role="presentation"></a>Cada enlace tiene un <em>ámbito</em>, que es la parte del programa en la que el enlace es visible. Para los enlaces definidos fuera de cualquier función, bloque o módulo (ver <a href="10_modules.html">Capítulo 10</a>), el ámbito es todo el programa—puedes hacer referencia a esos enlaces donde quieras. Estos se llaman <em>globales</em>.</p> +<p><a class="p_ident" id="p-BeL3Sj3y+H" href="#p-BeL3Sj3y+H" tabindex="-1" role="presentation"></a>Cada asociación tiene un <em>ámbito</em>, 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 <a href="10_modules.html">Capítulo 10</a>), el ámbito es todo el programa —puedes hacer referencia a esas asociaciones donde quieras. Estas asociaciones se llaman asociaciones <em>globales</em>.</p> -<p><a class="p_ident" id="p-Otbmqgy70v" href="#p-Otbmqgy70v" tabindex="-1" role="presentation"></a>Los enlaces creados para los parámetros de una función o declarados dentro de una función solo pueden ser referenciados en esa función, por lo que se conocen como enlaces <em>locales</em>. Cada vez que se llama a la función, se crean nuevas instancias de estos enlaces. Esto proporciona cierto aislamiento entre funciones—cada llamada a función actúa en su propio pequeño mundo (su entorno local) y a menudo se puede entender sin saber mucho sobre lo que está sucediendo en el entorno global.</p> +<p><a class="p_ident" id="p-UeCBj+0yHr" href="#p-UeCBj+0yHr" tabindex="-1" role="presentation"></a>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 <em>locales</em>. 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.</p> -<p><a class="p_ident" id="p-a0Kzjls2fD" href="#p-a0Kzjls2fD" tabindex="-1" role="presentation"></a>Los enlaces declarados con <code>let</code> y <code>const</code> en realidad son locales al <em>bloque</em> en el que se declaran, por lo que si creas uno de ellos dentro de un bucle, el código antes y después del bucle no puede “verlo”. En JavaScript anterior a 2015, solo las funciones creaban nuevos ámbitos, por lo que los enlaces de estilo antiguo, creados con la palabra clave <code>var</code>, son visibles en toda función en la que aparecen—o en todo el ámbito global, si no están dentro de una función.</p> +<p><a class="p_ident" id="p-fR26Moft56" href="#p-fR26Moft56" tabindex="-1" role="presentation"></a>Las asociaciones declaradas con <code>let</code> y <code>const</code> en realidad son locales al <em>bloque</em> 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 <code>var</code>, 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.</p> + +<div class="translator-note"><p><strong>N. del T.</strong>: Lo más común al referirse a cualquiera de las entidades definidas mediante las palabras clave <code>let</code>, <code>const</code> y <code>var</code> es utilizar la palabra <strong>variable</strong>. 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 <strong>variable</strong> para referirse a una entidad que se define mediante la palabra clave <code>const</code> —y que, por tanto, es constante— puede resultar confuso. Por este motivo, hemos elegido utilizar la palabra <strong>asociación</strong> para referirnos a estas entidades, aunque <strong>vínculo</strong> o <strong>enlace</strong> 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 <strong>bind</strong>— referirse a estas entidades de una manera unificada que no pueda llevar a confusión.</p> +</div> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-IoDSUUCl/U" href="#c-IoDSUUCl/U" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">x</span> = <span class="tok-number">10</span>; <span class="tok-comment">// global</span> <span class="tok-keyword">if</span> (true) { @@ -74,247 +77,249 @@ <h2><a class="h_ident" id="h-wxzpzC5WZf" href="#h-wxzpzC5WZf" tabindex="-1" role <span class="tok-keyword">var</span> <span class="tok-definition">z</span> = <span class="tok-number">30</span>; <span class="tok-comment">// también global</span> }</pre> -<p><a class="p_ident" id="p-9qwZVGX4rL" href="#p-9qwZVGX4rL" tabindex="-1" role="presentation"></a>Cada ámbito puede “mirar hacia afuera” al ámbito que lo rodea, por lo que <code>x</code> es visible dentro del bloque en el ejemplo. La excepción es cuando múltiples enlaces tienen el mismo nombre—en ese caso, el código solo puede ver el más interno. Por ejemplo, cuando el código dentro de la función <code>halve</code> hace referencia a <code>n</code>, está viendo su <em>propio</em> <code>n</code>, no el <code>n</code> global.</p> +<p><a class="p_ident" id="p-MrrBJy4V0J" href="#p-MrrBJy4V0J" tabindex="-1" role="presentation"></a>Cada ámbito puede “mirar hacia afuera” al ámbito que lo rodea, por lo que <code>x</code> 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 <code>mitad</code> hace referencia a <code>n</code>, está viendo su <em>propia</em> <code>n</code>, no la <code>n</code> global.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5g6POeoiQv" href="#c-5g6POeoiQv" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">halve</span> = <span class="tok-keyword">function</span>(<span class="tok-definition">n</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-fpH/sp9w/P" href="#c-fpH/sp9w/P" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">mitad</span> = <span class="tok-keyword">function</span>(<span class="tok-definition">n</span>) { <span class="tok-keyword">return</span> n / <span class="tok-number">2</span>; }; <span class="tok-keyword">let</span> <span class="tok-definition">n</span> = <span class="tok-number">10</span>; -console.log(halve(<span class="tok-number">100</span>)); +console.log(mitad(<span class="tok-number">100</span>)); <span class="tok-comment">// → 50</span> console.log(n); <span class="tok-comment">// → 10</span></pre> <h2 id="alcance"><a class="h_ident" id="h-Bb1XV2UFNf" href="#h-Bb1XV2UFNf" tabindex="-1" role="presentation"></a>Ámbito anidado</h2> -<p><a class="p_ident" id="p-WQPriPynnr" href="#p-WQPriPynnr" tabindex="-1" role="presentation"></a>JavaScript distingue no solo entre enlaces globales y locales. Bloques y funciones pueden ser creados dentro de otros bloques y funciones, produciendo múltiples grados de localidad.</p> +<p><a class="p_ident" id="p-WQPriPynnr" href="#p-WQPriPynnr" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-ET4L8vHq9N" href="#p-ET4L8vHq9N" tabindex="-1" role="presentation"></a>Por ejemplo, esta función—que muestra los ingredientes necesarios para hacer un lote de hummus—tiene otra función dentro de ella:</p> +<p><a class="p_ident" id="p-ET4L8vHq9N" href="#p-ET4L8vHq9N" tabindex="-1" role="presentation"></a>Por ejemplo, esta función —que muestra los ingredientes necesarios para hacer <code>factor</code> platos de hummus— tiene otra función dentro de ella:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-o16pCCxu1S" href="#c-o16pCCxu1S" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">hummus</span> = <span class="tok-keyword">function</span>(<span class="tok-definition">factor</span>) { - <span class="tok-keyword">const</span> <span class="tok-definition">ingredient</span> = <span class="tok-keyword">function</span>(<span class="tok-definition">amount</span>, <span class="tok-definition">unit</span>, <span class="tok-definition">name</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">ingredientAmount</span> = amount * factor; - <span class="tok-keyword">if</span> (ingredientAmount > <span class="tok-number">1</span>) { - unit += <span class="tok-string">"s"</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-vI0jSLig/7" href="#c-vI0jSLig/7" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">hummus</span> = <span class="tok-keyword">function</span>(<span class="tok-definition">factor</span>) { + <span class="tok-keyword">const</span> <span class="tok-definition">ingrediente</span> = <span class="tok-keyword">function</span>(<span class="tok-definition">cantidad</span>, <span class="tok-definition">unidad</span>, <span class="tok-definition">nombre</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">cantidadDeIngrediente</span> = cantidad * factor; + <span class="tok-keyword">if</span> (cantidadDeIngrediente != <span class="tok-number">1</span>) { + unidad += <span class="tok-string">"s de"</span>; + } <span class="tok-keyword">else</span> { + unidad += <span class="tok-string">" de"</span>; } - console.log(<span class="tok-string2">`</span>${ingredientAmount}<span class="tok-string2"> </span>${unit}<span class="tok-string2"> </span>${name}<span class="tok-string2">`</span>); + console.log(<span class="tok-string2">`</span>${cantidadDeIngrediente}<span class="tok-string2"> </span>${unidad}<span class="tok-string2"> </span>${nombre}<span class="tok-string2">`</span>); }; - ingredient(<span class="tok-number">1</span>, <span class="tok-string">"lata"</span>, <span class="tok-string">"garbanzos"</span>); - ingredient(<span class="tok-number">0.25</span>, <span class="tok-string">"taza"</span>, <span class="tok-string">"tahini"</span>); - ingredient(<span class="tok-number">0.25</span>, <span class="tok-string">"taza"</span>, <span class="tok-string">"jugo de limón"</span>); - ingredient(<span class="tok-number">1</span>, <span class="tok-string">"diente"</span>, <span class="tok-string">"ajo"</span>); - ingredient(<span class="tok-number">2</span>, <span class="tok-string">"cucharada"</span>, <span class="tok-string">"aceite de oliva"</span>); - ingredient(<span class="tok-number">0.5</span>, <span class="tok-string">"cucharadita"</span>, <span class="tok-string">"comino"</span>); + ingrediente(<span class="tok-number">1</span>, <span class="tok-string">"lata"</span>, <span class="tok-string">"garbanzos"</span>); + ingrediente(<span class="tok-number">0.25</span>, <span class="tok-string">"taza"</span>, <span class="tok-string">"tahini"</span>); + ingrediente(<span class="tok-number">0.25</span>, <span class="tok-string">"taza"</span>, <span class="tok-string">"jugo de limón"</span>); + ingrediente(<span class="tok-number">1</span>, <span class="tok-string">"diente"</span>, <span class="tok-string">"ajo"</span>); + ingrediente(<span class="tok-number">2</span>, <span class="tok-string">"cucharada"</span>, <span class="tok-string">"aceite de oliva"</span>); + ingrediente(<span class="tok-number">0.5</span>, <span class="tok-string">"cucharadita"</span>, <span class="tok-string">"comino"</span>); };</pre> -<p><a class="p_ident" id="p-Rmy1dhc5eP" href="#p-Rmy1dhc5eP" tabindex="-1" role="presentation"></a>El código dentro de la función <code>ingredient</code> puede ver el enlace <code>factor</code> de la función exterior, pero sus enlaces locales, como <code>unit</code> o <code>ingredientAmount</code>, no son visibles en la función exterior.</p> +<p><a class="p_ident" id="p-Rmy1dhc5eP" href="#p-Rmy1dhc5eP" tabindex="-1" role="presentation"></a>El código dentro de la función <code>ingrediente</code> puede ver la asociación <code>factor</code> de la función exterior, pero sus asociaciones locales, como <code>unidad</code> o <code>cantidadDeIngrediente</code>, no son visibles en la función exterior.</p> -<p><a class="p_ident" id="p-DoPmP2HGVl" href="#p-DoPmP2HGVl" tabindex="-1" role="presentation"></a>El conjunto de enlaces visibles dentro de un bloque está determinado por el lugar de ese bloque en el texto del programa. Cada bloque local también puede ver todos los bloques locales que lo contienen, y todos los bloques pueden ver el bloque global. Este enfoque de visibilidad de enlaces se llama <em>ámbito léxico</em>.</p> +<p><a class="p_ident" id="p-wEn+t0Kyb2" href="#p-wEn+t0Kyb2" tabindex="-1" role="presentation"></a>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 <em>alcance léxico</em>.</p> <h2><a class="h_ident" id="h-3mNEhIfF61" href="#h-3mNEhIfF61" tabindex="-1" role="presentation"></a>Funciones como valores</h2> -<p><a class="p_ident" id="p-9SOeUnXtuP" href="#p-9SOeUnXtuP" tabindex="-1" role="presentation"></a>Generalmente un enlace de función simplemente actúa como un nombre para una parte específica del programa. Este enlace se define una vez y nunca se cambia. Esto hace que sea fácil confundir la función y su nombre.</p> +<p><a class="p_ident" id="p-l2oL1Ukvbq" href="#p-l2oL1Ukvbq" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-Z3YsDzJ9JG" href="#p-Z3YsDzJ9JG" tabindex="-1" role="presentation"></a>Pero los dos son diferentes. Un valor de función puede hacer todas las cosas que pueden hacer otros valores: se puede utilizar en expresiones arbitrarias, no solo llamarlo. Es posible almacenar un valor de función en un nuevo enlace, pasarlo como argumento a una función, etc. De manera similar, un enlace que contiene una función sigue siendo solo un enlace habitual y, si no es constante, se le puede asignar un nuevo valor, así:</p> +<p><a class="p_ident" id="p-sLA79x7Dgu" href="#p-sLA79x7Dgu" tabindex="-1" role="presentation"></a>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í:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-lz0sp2Utto" href="#c-lz0sp2Utto" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">launchMissiles</span> = <span class="tok-keyword">function</span>() { - missileSystem.launch(<span class="tok-string">"now"</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-h/pTfetciv" href="#c-h/pTfetciv" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">lanzarMisiles</span> = <span class="tok-keyword">function</span>() { + sistemaDeMisil.lanzar(<span class="tok-string">"ahora"</span>); }; -<span class="tok-keyword">if</span> (safeMode) { - launchMissiles = <span class="tok-keyword">function</span>() {<span class="tok-comment">/* no hacer nada */</span>}; +<span class="tok-keyword">if</span> (modoSeguro) { + lanzarMisiles = <span class="tok-keyword">function</span>() {<span class="tok-comment">/* no hacer nada */</span>}; }</pre> <p><a class="p_ident" id="p-mYurbU9/K8" href="#p-mYurbU9/K8" tabindex="-1" role="presentation"></a>En el <a href="05_higher_order.html">Capítulo 5</a>, discutiremos las cosas interesantes que podemos hacer al pasar valores de función a otras funciones.</p> <h2><a class="h_ident" id="h-tewMxJao9I" href="#h-tewMxJao9I" tabindex="-1" role="presentation"></a>Notación de declaración</h2> -<p><a class="p_ident" id="p-8B3Xt7iB35" href="#p-8B3Xt7iB35" tabindex="-1" role="presentation"></a>Hay una manera ligeramente más corta de crear un enlace de función. Cuando se utiliza la palabra clave <code>function</code> al inicio de una declaración, funciona de manera diferente:</p> +<p><a class="p_ident" id="p-DX9N9ry4Gl" href="#p-DX9N9ry4Gl" tabindex="-1" role="presentation"></a>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 <code>function</code> al inicio de una declaración:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-4uHhsg+h7S" href="#c-4uHhsg+h7S" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">square</span>(<span class="tok-definition">x</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-JtX/xEHIYj" href="#c-JtX/xEHIYj" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">cuadrado</span>(<span class="tok-definition">x</span>) { <span class="tok-keyword">return</span> x * x; }</pre> -<p><a class="p_ident" id="p-VyKvVyRYQV" href="#p-VyKvVyRYQV" tabindex="-1" role="presentation"></a>Esta es una función <em>declarativa</em>. La declaración define el enlace <code>square</code> y lo 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.</p> +<p><a class="p_ident" id="p-GvjdBc2WcY" href="#p-GvjdBc2WcY" tabindex="-1" role="presentation"></a>Esto es una función <em>declarativa</em>. La declaración define la asociación <code>cuadrado</code> 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.</p> -<p><a class="p_ident" id="p-xfJkZVJQEh" href="#p-xfJkZVJQEh" tabindex="-1" role="presentation"></a>Hay una sutileza con esta forma de definición de función.</p> +<p><a class="p_ident" id="p-nhHnarVt3A" href="#p-nhHnarVt3A" tabindex="-1" role="presentation"></a>Hay una sutileza con esta forma de definir una función.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Uq3EWZNFOJ" href="#c-Uq3EWZNFOJ" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"El futuro dice:"</span>, future()); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-K15d59JW5m" href="#c-K15d59JW5m" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"El futuro dice:"</span>, futuro()); -<span class="tok-keyword">function</span> <span class="tok-definition">future</span>() { - <span class="tok-keyword">return</span> <span class="tok-string">"Nunca tendrás autos voladores"</span>; +<span class="tok-keyword">function</span> <span class="tok-definition">futuro</span>() { + <span class="tok-keyword">return</span> <span class="tok-string">"Nunca tendrás coches voladores"</span>; }</pre> -<p><a class="p_ident" id="p-4dZZ8ocV0I" href="#p-4dZZ8ocV0I" tabindex="-1" role="presentation"></a>El código anterior funciona, incluso aunque la función esté definida <em>debajo</em> del código que la usa. Las declaraciones de función no forman parte del flujo de control regular de arriba hacia abajo. Conceptualmente se mueven al principio de su alcance y pueden ser utilizadas por todo el código en ese alcance. 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.</p> +<p><a class="p_ident" id="p-4dZZ8ocV0I" href="#p-4dZZ8ocV0I" tabindex="-1" role="presentation"></a>El código anterior funciona, incluso aunque la función esté definida <em>debajo</em> 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.</p> -<h2><a class="h_ident" id="h-7gP0M6nUCF" href="#h-7gP0M6nUCF" tabindex="-1" role="presentation"></a>Funciones de flecha</h2> +<h2><a class="h_ident" id="h-lYx5RZeXoX" href="#h-lYx5RZeXoX" tabindex="-1" role="presentation"></a>Funciones flecha</h2> -<p><a class="p_ident" id="p-qp+SShPV+r" href="#p-qp+SShPV+r" tabindex="-1" role="presentation"></a>Hay una tercera notación para funciones, que se ve muy diferente de las otras. En lugar de la palabra clave <code>function</code>, utiliza una flecha (<code>=></code>) compuesta por un signo igual y un caracter mayor que (no confundir con el operador mayor o igual, que se escribe <code>>=</code>):</p> +<p><a class="p_ident" id="p-qp+SShPV+r" href="#p-qp+SShPV+r" tabindex="-1" role="presentation"></a>Hay una tercera notación para funciones que tiene un aspecto muy diferente a las otras. En lugar de la palabra clave <code>function</code>, utiliza una flecha (<code>=></code>) compuesta por un signo igual y un carácter mayor que (no confundir con el operador mayor o igual, que se escribe <code>>=</code>):</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5zMRAvLyub" href="#c-5zMRAvLyub" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">roundTo</span> = (<span class="tok-definition">n</span>, <span class="tok-definition">step</span>) => { - <span class="tok-keyword">let</span> <span class="tok-definition">remainder</span> = n % step; - <span class="tok-keyword">return</span> n - remainder + (remainder < step / <span class="tok-number">2</span> ? <span class="tok-number">0</span> : step); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-k1xMAvcKB3" href="#c-k1xMAvcKB3" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">redondearA</span> = (<span class="tok-definition">n</span>, <span class="tok-definition">paso</span>) => { + <span class="tok-keyword">let</span> <span class="tok-definition">resto</span> = n % paso; + <span class="tok-keyword">return</span> n - resto + (resto < paso / <span class="tok-number">2</span> ? <span class="tok-number">0</span> : paso); };</pre> -<p><a class="p_ident" id="p-4wRZ32fVtv" href="#p-4wRZ32fVtv" tabindex="-1" role="presentation"></a>La flecha viene <em>después</em> de la lista de parámetros y es seguida por el cuerpo de la función. Expresa algo así como “esta entrada (los parámetros) produce este resultado (el cuerpo)”.</p> +<p><a class="p_ident" id="p-X4r0fxNF74" href="#p-X4r0fxNF74" tabindex="-1" role="presentation"></a>La flecha se escribe <em>después</em> 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)”.</p> -<p><a class="p_ident" id="p-B2W8hY/asG" href="#p-B2W8hY/asG" tabindex="-1" role="presentation"></a>Cuando solo hay un nombre de parámetro, puedes 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, esa expresión será devuelta por la función. Por lo tanto, estas dos definiciones de <code>exponente</code> hacen lo mismo:</p> +<p><a class="p_ident" id="p-B2W8hY/asG" href="#p-B2W8hY/asG" tabindex="-1" role="presentation"></a>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 <code>exponente</code> hacen lo mismo:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-yDV8N1C3lB" href="#c-yDV8N1C3lB" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">exponente1</span> = (<span class="tok-definition">x</span>) => { <span class="tok-keyword">return</span> x * x; }; <span class="tok-keyword">const</span> <span class="tok-definition">exponente2</span> = <span class="tok-definition">x</span> => x * x;</pre> -<p><a class="p_ident" id="p-PvxM25s0zB" href="#p-PvxM25s0zB" tabindex="-1" role="presentation"></a>Cuando una función de flecha no tiene parámetros en absoluto, su lista de parámetros es simplemente un conjunto vacío de paréntesis.</p> +<p><a class="p_ident" id="p-acg+sdG3Pd" href="#p-acg+sdG3Pd" tabindex="-1" role="presentation"></a>Cuando una función flecha no tiene ningún parámetro, su lista de parámetros consiste simplemente en unos paréntesis vacíos.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-WTImn5yQuM" href="#c-WTImn5yQuM" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">cuerno</span> = () => { - console.log(<span class="tok-string">"Toot"</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-0pibIzhiqZ" href="#c-0pibIzhiqZ" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">cuerno</span> = () => { + console.log(<span class="tok-string">"Honk"</span>); };</pre> -<p><a class="p_ident" id="p-q9uDkTN2Zd" href="#p-q9uDkTN2Zd" tabindex="-1" role="presentation"></a>No hay una razón profunda para tener tanto funciones de flecha como expresiones <code>function</code> en el lenguaje. Aparte de un detalle menor, que discutiremos en el <a href="06_object.html">Capítulo 6</a>, hacen lo mismo. Las funciones de flecha se agregaron en 2015, principalmente para hacer posible escribir expresiones de función pequeñas de una manera menos verbosa. Las usaremos a menudo en el <a href="05_higher_order.html">Capítulo 5</a> .</p> +<p><a class="p_ident" id="p-q9uDkTN2Zd" href="#p-q9uDkTN2Zd" tabindex="-1" role="presentation"></a>No hay una razón esencial para tener tanto funciones flecha como expresiones <code>function</code> en el lenguaje. Aparte de un detalle menor, que discutiremos en el <a href="06_object.html">Capítulo 6</a>, 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 <a href="05_higher_order.html">Capítulo 5</a> .</p> <h2 id="pila"><a class="h_ident" id="h-nePB5S+L1Q" href="#h-nePB5S+L1Q" tabindex="-1" role="presentation"></a>La pila de llamadas</h2> -<p><a class="p_ident" id="p-aMmoKkJUw3" href="#p-aMmoKkJUw3" tabindex="-1" role="presentation"></a>La forma en que el control fluye a través de las funciones es un tanto complicada. Echemos un vistazo más de cerca. Aquí hay un programa simple que realiza algunas llamadas de función:</p> +<p><a class="p_ident" id="p-aMmoKkJUw3" href="#p-aMmoKkJUw3" tabindex="-1" role="presentation"></a>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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-qJyPTKyGmO" href="#c-qJyPTKyGmO" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">saludar</span>(<span class="tok-definition">quien</span>) { - console.log(<span class="tok-string">"Hola "</span> + quien); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-gT/IAWNknj" href="#c-gT/IAWNknj" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">saludar</span>(<span class="tok-definition">quién</span>) { + console.log(<span class="tok-string">"Hola "</span> + quién); } saludar(<span class="tok-string">"Harry"</span>); console.log(<span class="tok-string">"Adiós"</span>);</pre> -<p><a class="p_ident" id="p-Dn5Off5HVf" href="#p-Dn5Off5HVf" tabindex="-1" role="presentation"></a>Una ejecución de este programa va más o menos así: la llamada a <code>saludar</code> hace que el control salte al inicio de esa función (línea 2). La función llama a <code>console.log</code>, 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 <code>saludar</code>, por lo que regresa al lugar que la llamó, línea 4. La línea siguiente llama a <code>console.log</code> nuevamente. Después de ese retorno, el programa llega a su fin.</p> +<p><a class="p_ident" id="p-Dn5Off5HVf" href="#p-Dn5Off5HVf" tabindex="-1" role="presentation"></a>Una ejecución de este programa funciona más o menos así: la llamada a <code>saludar</code> hace que el control salte al inicio de esa función (línea 2). La función llama a <code>console.log</code>, 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 <code>saludar</code>, por lo que regresa al lugar desde el que se llamó, línea 4. La línea siguiente llama a <code>console.log</code> de nuevo. En cuanto vuelve de ahí, el programa llega a su fin.</p> -<p><a class="p_ident" id="p-EhJguRxP1B" href="#p-EhJguRxP1B" tabindex="-1" role="presentation"></a>Podríamos mostrar el flujo de control esquemáticamente de esta manera:</p> +<p><a class="p_ident" id="p-BU9Veqvs0q" href="#p-BU9Veqvs0q" tabindex="-1" role="presentation"></a>Podríamos mostrar el flujo de control esquemáticamente como sigue:</p> -<pre class="snippet" data-language="null" ><a class="c_ident" id="c-P67GeEPWC+" href="#c-P67GeEPWC+" tabindex="-1" role="presentation"></a>no en función +<pre class="snippet" data-language="null" ><a class="c_ident" id="c-NBQdvr2TSX" href="#c-NBQdvr2TSX" tabindex="-1" role="presentation"></a>fuera de función en saludar en console.log en saludar -no en función +fuera de función en console.log -no en función</pre> +fuera de función</pre> -<p><a class="p_ident" id="p-M5sgy1Ouv/" href="#p-M5sgy1Ouv/" tabindex="-1" role="presentation"></a>Dado que una función tiene que regresar al lugar que la llamó cuando termina, la computadora debe recordar el contexto desde el cual se realizó la llamada. En un caso, <code>console.log</code> tiene que regresar a la función <code>saludar</code> cuando haya terminado. En el otro caso, regresa al final del programa.</p> +<p><a class="p_ident" id="p-M5sgy1Ouv/" href="#p-M5sgy1Ouv/" tabindex="-1" role="presentation"></a>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, <code>console.log</code> tiene que regresar a la función <code>saludar</code> cuando haya terminado. En el otro caso, regresa al final del programa.</p> -<p><a class="p_ident" id="p-xHR55l4E6j" href="#p-xHR55l4E6j" tabindex="-1" role="presentation"></a>El lugar donde la computadora almacena este contexto es la <em>pila de llamadas</em>. Cada vez que se llama a una función, el contexto actual se almacena en la parte superior de esta pila. Cuando una función devuelve, elimina el contexto superior de la pila y usa ese contexto para continuar la ejecución.</p> +<p><a class="p_ident" id="p-xHR55l4E6j" href="#p-xHR55l4E6j" tabindex="-1" role="presentation"></a>El lugar donde la computadora almacena este contexto es la <em>pila de llamadas</em>. 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.</p> -<p><a class="p_ident" id="p-SB1hzp8Fcw" href="#p-SB1hzp8Fcw" tabindex="-1" role="presentation"></a>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, <em>sería</em> infinito, si la computadora tuviera una pila infinita. Como no la tiene, nos quedaremos sin espacio o “reventaremos la pila”.</p> +<p><a class="p_ident" id="p-r5xCouzQJp" href="#p-r5xCouzQJp" tabindex="-1" role="presentation"></a>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, <em>sería</em> infinito, si la computadora tuviera una pila infinita. Como no es el caso, nos quedaremos sin espacio o “volaremos la pila”.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-u9bb1xDgwe" href="#c-u9bb1xDgwe" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">chicken</span>() { - <span class="tok-keyword">return</span> egg(); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-8v+sCUObB2" href="#c-8v+sCUObB2" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">gallina</span>() { + <span class="tok-keyword">return</span> huevo(); } -<span class="tok-keyword">function</span> <span class="tok-definition">egg</span>() { - <span class="tok-keyword">return</span> chicken(); +<span class="tok-keyword">function</span> <span class="tok-definition">huevo</span>() { + <span class="tok-keyword">return</span> gallina(); } -console.log(chicken() + <span class="tok-string">" salió primero."</span>); +console.log(gallina() + <span class="tok-string">" salió primero."</span>); <span class="tok-comment">// → ??</span></pre> <h2><a class="h_ident" id="h-jNCGCF3rFb" href="#h-jNCGCF3rFb" tabindex="-1" role="presentation"></a>Argumentos Opcionales</h2> <p><a class="p_ident" id="p-H9TL95isqo" href="#p-H9TL95isqo" tabindex="-1" role="presentation"></a>El siguiente código está permitido y se ejecuta sin ningún problema:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-T8m5axnp1h" href="#c-T8m5axnp1h" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">square</span>(<span class="tok-definition">x</span>) { <span class="tok-keyword">return</span> x * x; } -console.log(square(<span class="tok-number">4</span>, true, <span class="tok-string">"erizo"</span>)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-8rBBnZOziA" href="#c-8rBBnZOziA" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">cuadrado</span>(<span class="tok-definition">x</span>) { <span class="tok-keyword">return</span> x * x; } +console.log(cuadrado(<span class="tok-number">4</span>, true, <span class="tok-string">"erizo"</span>)); <span class="tok-comment">// → 16</span></pre> -<p><a class="p_ident" id="p-qSFz2Cu3MP" href="#p-qSFz2Cu3MP" tabindex="-1" role="presentation"></a>Hemos definido <code>square</code> con solo un parámetro. Sin embargo, cuando lo llamamos con tres, el lenguaje no se queja. Ignora los argumentos adicionales y calcula el cuadrado del primero.</p> +<p><a class="p_ident" id="p-qSFz2Cu3MP" href="#p-qSFz2Cu3MP" tabindex="-1" role="presentation"></a>Hemos definido <code>cuadrado</code> 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.</p> -<p><a class="p_ident" id="p-xQJ7gf7nbn" href="#p-xQJ7gf7nbn" tabindex="-1" role="presentation"></a>JavaScript es extremadamente flexible en cuanto al número de argumentos que puedes pasar a una función. Si pasas demasiados, los extras son ignorados. Si pasas muy pocos, los parámetros faltantes se les asigna el valor <code>undefined</code>.</p> +<p><a class="p_ident" id="p-xQJ7gf7nbn" href="#p-xQJ7gf7nbn" tabindex="-1" role="presentation"></a>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 <code>undefined</code>.</p> -<p><a class="p_ident" id="p-+8VZwoywj9" href="#p-+8VZwoywj9" tabindex="-1" role="presentation"></a>El inconveniente de esto es que es posible —incluso probable— que pases accidentalmente el número incorrecto de argumentos a las 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 <code>minus</code> intenta imitar al operador <code>-</code> actuando sobre uno o dos argumentos:</p> +<p><a class="p_ident" id="p-+8VZwoywj9" href="#p-+8VZwoywj9" tabindex="-1" role="presentation"></a>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 <code>menos</code> intenta imitar al operador <code>-</code> al actuar sobre uno o dos argumentos:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-8zGcY0SKdo" href="#c-8zGcY0SKdo" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">minus</span>(<span class="tok-definition">a</span>, <span class="tok-definition">b</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-TqlHJQyAYH" href="#c-TqlHJQyAYH" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">menos</span>(<span class="tok-definition">a</span>, <span class="tok-definition">b</span>) { <span class="tok-keyword">if</span> (b === undefined) <span class="tok-keyword">return</span> -a; <span class="tok-keyword">else</span> <span class="tok-keyword">return</span> a - b; } -console.log(minus(<span class="tok-number">10</span>)); +console.log(menos(<span class="tok-number">10</span>)); <span class="tok-comment">// → -10</span> -console.log(minus(<span class="tok-number">10</span>, <span class="tok-number">5</span>)); +console.log(menos(<span class="tok-number">10</span>, <span class="tok-number">5</span>)); <span class="tok-comment">// → 5</span></pre> -<p id="roundTo"><a class="p_ident" id="p-tcju9YOu56" href="#p-tcju9YOu56" tabindex="-1" role="presentation"></a>Si escribes un operador <code>=</code> después de un parámetro, seguido de una expresión, el valor de esa expresión reemplazará al argumento cuando no se le dé. Por ejemplo, esta versión de <code>roundTo</code> hace que su segundo argumento sea opcional. Si no lo proporcionas o pasas el valor <code>undefined</code>, por defecto será uno:</p> +<p id="roundTo"><a class="p_ident" id="p-tcju9YOu56" href="#p-tcju9YOu56" tabindex="-1" role="presentation"></a>Si escribes un operador <code>=</code> 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 <code>redondearA</code> hace que su segundo argumento sea opcional. Si no lo proporcionas o pasas el valor <code>undefined</code>, por defecto será uno:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-lf5l9+817Y" href="#c-lf5l9+817Y" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">roundTo</span>(<span class="tok-definition">n</span>, <span class="tok-definition">step</span> = <span class="tok-number">1</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">remainder</span> = n % step; - <span class="tok-keyword">return</span> n - remainder + (remainder < step / <span class="tok-number">2</span> ? <span class="tok-number">0</span> : step); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-tt4i6Vpqo4" href="#c-tt4i6Vpqo4" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">redondearA</span>(<span class="tok-definition">n</span>, <span class="tok-definition">paso</span> = <span class="tok-number">1</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">resto</span> = n % paso; + <span class="tok-keyword">return</span> n - resto + (resto < paso / <span class="tok-number">2</span> ? <span class="tok-number">0</span> : paso); }; -console.log(roundTo(<span class="tok-number">4.5</span>)); +console.log(redondearA(<span class="tok-number">4.5</span>)); <span class="tok-comment">// → 5</span> -console.log(roundTo(<span class="tok-number">4.5</span>, <span class="tok-number">2</span>)); +console.log(redondearA(<span class="tok-number">4.5</span>, <span class="tok-number">2</span>)); <span class="tok-comment">// → 4</span></pre> -<p><a class="p_ident" id="p-0QAhU0olKv" href="#p-0QAhU0olKv" tabindex="-1" role="presentation"></a><a href="04_data.html#rest_parameters">El próximo capítulo</a> introducirá una forma en que un cuerpo de función puede acceder a la lista completa de argumentos que se le pasaron. Esto es útil porque le permite a una función aceptar cualquier número de argumentos. Por ejemplo, <code>console.log</code> lo hace, mostrando todos los valores que se le dan:</p> +<p><a class="p_ident" id="p-0QAhU0olKv" href="#p-0QAhU0olKv" tabindex="-1" role="presentation"></a><a href="04_data.html#rest_parameters">El próximo capítulo</a> 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, <code>console.log</code> lo hace, mostrando todos los valores que se le dan:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-RvkwVkcUZ7" href="#c-RvkwVkcUZ7" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"C"</span>, <span class="tok-string">"O"</span>, <span class="tok-number">2</span>); <span class="tok-comment">// → C O 2</span></pre> <h2><a class="h_ident" id="h-KU8YojAGul" href="#h-KU8YojAGul" tabindex="-1" role="presentation"></a>Clausura</h2> -<p><a class="p_ident" id="p-hi0pjIyB2g" href="#p-hi0pjIyB2g" tabindex="-1" role="presentation"></a>La capacidad de tratar las funciones como valores, combinada con el hecho de que los enlaces locales se recrean cada vez que se llama a una función, plantea una pregunta interesante: ¿qué sucede con los enlaces locales cuando la llamada a la función que los creó ya no está activa?El siguiente código muestra un ejemplo de esto. Define una función, <code>wrapValue</code>, que crea un enlace local. Luego devuelve una función que accede a este enlace local y lo devuelve:</p> +<p><a class="p_ident" id="p-bNlcVAQ4/h" href="#p-bNlcVAQ4/h" tabindex="-1" role="presentation"></a>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, <code>envuelveValor</code>, que crea una asociación local. Luego, devuelve una función que accede a esta asociación local y la devuelve:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-DF70h3opbx" href="#c-DF70h3opbx" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">wrapValue</span>(<span class="tok-definition">n</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-KVTZmlbTj7" href="#c-KVTZmlbTj7" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">envuelveValor</span>(<span class="tok-definition">n</span>) { <span class="tok-keyword">let</span> <span class="tok-definition">local</span> = n; <span class="tok-keyword">return</span> () => local; } -<span class="tok-keyword">let</span> <span class="tok-definition">wrap1</span> = wrapValue(<span class="tok-number">1</span>); -<span class="tok-keyword">let</span> <span class="tok-definition">wrap2</span> = wrapValue(<span class="tok-number">2</span>); -console.log(wrap1()); +<span class="tok-keyword">let</span> <span class="tok-definition">envoltura1</span> = envuelveValor(<span class="tok-number">1</span>); +<span class="tok-keyword">let</span> <span class="tok-definition">envoltura2</span> = envuelveValor(<span class="tok-number">2</span>); +console.log(envoltura1()); <span class="tok-comment">// → 1</span> -console.log(wrap2()); +console.log(envoltura2()); <span class="tok-comment">// → 2</span></pre> -<p><a class="p_ident" id="p-YU/l50hrdL" href="#p-YU/l50hrdL" tabindex="-1" role="presentation"></a>Esto está permitido y funciona como esperarías: ambas instancias del enlace aún pueden accederse. Esta situación es una buena demostración de que los enlaces locales se crean nuevamente para cada llamada, y las diferentes llamadas no afectan los enlaces locales de los demás.</p> +<p><a class="p_ident" id="p-J1QOk9eddy" href="#p-J1QOk9eddy" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-HTFaO7CfSD" href="#p-HTFaO7CfSD" tabindex="-1" role="presentation"></a>Esta característica, poder hacer referencia a una instancia específica de un enlace local en un ámbito superior, se llama <em>clausura</em>. Una función que hace referencia a enlaces de ámbitos locales a su alrededor se llama <em>una</em> clausura. Este comportamiento no solo te libera de tener que preocuparte por la vida útil de los enlaces, sino que también hace posible usar valores de función de formas creativas.</p> +<p><a class="p_ident" id="p-HTFaO7CfSD" href="#p-HTFaO7CfSD" tabindex="-1" role="presentation"></a>Esta característica —poder hacer referencia a una instancia específica de una asociación local en un ámbito superior— se llama <em>clausura</em>. Una función que hace referencia a asociaciones de ámbitos locales a su alrededor se llama <em>una</em> 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.</p> -<p><a class="p_ident" id="p-dqvi9Q/A+9" href="#p-dqvi9Q/A+9" tabindex="-1" role="presentation"></a>Con un ligero cambio, podemos convertir el ejemplo anterior en una forma de crear funciones que multiplican por una cantidad arbitraria:</p> +<p><a class="p_ident" id="p-rr1TRvxJbc" href="#p-rr1TRvxJbc" tabindex="-1" role="presentation"></a>Con un pequeño cambio, podemos convertir el ejemplo anterior en una forma de crear funciones que multiplican por una cantidad arbitraria:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-iIlCVmvMSs" href="#c-iIlCVmvMSs" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">multiplier</span>(<span class="tok-definition">factor</span>) { - <span class="tok-keyword">return</span> <span class="tok-definition">number</span> => number * factor; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-bH0A4NxFdU" href="#c-bH0A4NxFdU" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">multiplicador</span>(<span class="tok-definition">factor</span>) { + <span class="tok-keyword">return</span> <span class="tok-definition">número</span> => número * factor; } -<span class="tok-keyword">let</span> <span class="tok-definition">twice</span> = multiplier(<span class="tok-number">2</span>); -console.log(twice(<span class="tok-number">5</span>)); +<span class="tok-keyword">let</span> <span class="tok-definition">doble</span> = multiplicador(<span class="tok-number">2</span>); +console.log(doble(<span class="tok-number">5</span>)); <span class="tok-comment">// → 10</span></pre> -<p><a class="p_ident" id="p-W9UeLQ7s8n" href="#p-W9UeLQ7s8n" tabindex="-1" role="presentation"></a>El enlace explícito <code>local</code> del ejemplo <code>wrapValue</code> realmente no es necesario, ya que un parámetro es en sí mismo un enlace local.</p> +<p><a class="p_ident" id="p-bxkSaYB+66" href="#p-bxkSaYB+66" tabindex="-1" role="presentation"></a>La asociación explícita <code>local</code> del ejemplo <code>envuelveValor</code> realmente no es necesaria, ya que un parámetro es en sí mismo una asociación local.</p> -<p><a class="p_ident" id="p-xoKKzht2cB" href="#p-xoKKzht2cB" tabindex="-1" role="presentation"></a>Pensar en programas de esta manera requiere algo de práctica. Un buen modelo mental es pensar en los valores de función como que contienen tanto el código en su cuerpo como el entorno en el que fueron creados. Cuando se llama, el cuerpo de la función ve el entorno en el que fue creado, no el entorno en el que se llama.</p> +<p><a class="p_ident" id="p-raHxyoKVBw" href="#p-raHxyoKVBw" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-FbEnVK1PeW" href="#p-FbEnVK1PeW" tabindex="-1" role="presentation"></a>En el ejemplo anterior, se llama a <code>multiplier</code> y crea un entorno en el que su parámetro <code>factor</code> está vinculado a 2. El valor de función que devuelve, que se almacena en <code>twice</code>, recuerda este entorno para que cuando se llame, multiplique su argumento por 2.</p> +<p><a class="p_ident" id="p-FbEnVK1PeW" href="#p-FbEnVK1PeW" tabindex="-1" role="presentation"></a>En el ejemplo anterior, se llama a <code>multiplicador</code> y esta crea un entorno en el que su parámetro <code>factor</code> se asocia a 2. El valor de función que devuelve, que se almacena en <code>doble</code>, recuerda este entorno para que, cuando se llame, multiplique su argumento por 2.</p> <h2><a class="h_ident" id="h-ge3a45K3m3" href="#h-ge3a45K3m3" tabindex="-1" role="presentation"></a>Recursión</h2> -<p><a class="p_ident" id="p-OGtTx+fur4" href="#p-OGtTx+fur4" tabindex="-1" role="presentation"></a>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. Una función que se llama a sí misma se llama <em>recursiva</em>. La recursión permite que algunas funciones se escriban de una manera diferente. Toma, por ejemplo, esta función <code>power</code>, que hace lo mismo que el operador <code>**</code> (potenciación):</p> +<p><a class="p_ident" id="p-OGtTx+fur4" href="#p-OGtTx+fur4" tabindex="-1" role="presentation"></a>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 <em>recursiva</em>. La recursión permite escribir funciones con un estilo diferente. Considera, por ejemplo, esta función <code>potencia</code>, que hace lo mismo que el operador <code>**</code> (potenciación):</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-agdELfiRGm" href="#c-agdELfiRGm" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">power</span>(<span class="tok-definition">base</span>, <span class="tok-definition">exponent</span>) { - <span class="tok-keyword">if</span> (exponent == <span class="tok-number">0</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-BGD5jiQUfl" href="#c-BGD5jiQUfl" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">potencia</span>(<span class="tok-definition">base</span>, <span class="tok-definition">exponente</span>) { + <span class="tok-keyword">if</span> (exponente == <span class="tok-number">0</span>) { <span class="tok-keyword">return</span> <span class="tok-number">1</span>; } <span class="tok-keyword">else</span> { - <span class="tok-keyword">return</span> base * power(base, exponent - <span class="tok-number">1</span>); + <span class="tok-keyword">return</span> base * potencia(base, exponente - <span class="tok-number">1</span>); } } -console.log(power(<span class="tok-number">2</span>, <span class="tok-number">3</span>)); +console.log(potencia(<span class="tok-number">2</span>, <span class="tok-number">3</span>)); <span class="tok-comment">// → 8</span></pre> -<p><a class="p_ident" id="p-ZH6kc3lXYd" href="#p-ZH6kc3lXYd" tabindex="-1" role="presentation"></a>Esto se asemeja bastante 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 <a href="02_program_structure.html">Capítulo 2</a>. La función se llama a sí misma varias veces con exponentes cada vez más pequeños para lograr la multiplicación repetida.</p> +<p><a class="p_ident" id="p-e1reYsUjlr" href="#p-e1reYsUjlr" tabindex="-1" role="presentation"></a>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 <a href="02_program_structure.html">Capítulo 2</a>. 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.</p> -<p><a class="p_ident" id="p-vuRF5znTGo" href="#p-vuRF5znTGo" tabindex="-1" role="presentation"></a>Sin embargo, esta implementación tiene un problema: en implementaciones típicas de JavaScript, es aproximadamente tres veces más lenta que una versión que utiliza un bucle <code>for</code>. Recorrer un simple bucle suele ser más económico que llamar a una función múltiples veces.</p> +<p><a class="p_ident" id="p-kyvX6Y+ADT" href="#p-kyvX6Y+ADT" tabindex="-1" role="presentation"></a>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 <code>for</code>. Recorrer un simple bucle suele ser más económico que llamar a una función recursivamente.</p> -<p><a class="p_ident" id="p-HyK1Oztn84" href="#p-HyK1Oztn84" tabindex="-1" role="presentation"></a>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 cualquier programa puede ser acelerado haciendo que sea más extenso y complicado. El programador debe encontrar un equilibrio apropiado.</p> +<p><a class="p_ident" id="p-iDDKQQVkcJ" href="#p-iDDKQQVkcJ" tabindex="-1" role="presentation"></a>El dilema de velocidad <em>versus</em> 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.</p> -<p><a class="p_ident" id="p-UUCfSx41Ze" href="#p-UUCfSx41Ze" tabindex="-1" role="presentation"></a>En el caso de la función <code>power</code>, una versión poco elegante (con bucles) sigue siendo bastante simple y fácil de leer. No tiene mucho sentido reemplazarla con 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.</p> +<p><a class="p_ident" id="p-UUCfSx41Ze" href="#p-UUCfSx41Ze" tabindex="-1" role="presentation"></a>En el caso de la función <code>potencia</code>, 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.</p> -<p><a class="p_ident" id="p-qQ0sf4nZdt" href="#p-qQ0sf4nZdt" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-qQ0sf4nZdt" href="#p-qQ0sf4nZdt" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-W1/mlcain4" href="#p-W1/mlcain4" tabindex="-1" role="presentation"></a>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 lo suficiente como para tomar una cantidad significativa de tiempo—puedes medir después y mejorarlo si es necesario.</p> +<p><a class="p_ident" id="p-W1/mlcain4" href="#p-W1/mlcain4" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-oNzYWF+2hF" href="#p-oNzYWF+2hF" tabindex="-1" role="presentation"></a>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 mayor frecuencia, estos son problemas que requieren explorar o procesar varias “ramas”, cada una de las cuales podría ramificarse nuevamente en aún más ramas.</p> +<p><a class="p_ident" id="p-oNzYWF+2hF" href="#p-oNzYWF+2hF" tabindex="-1" role="presentation"></a>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.</p> -<p id="rompecabezas_recursivo"><a class="p_ident" id="p-z8AgKvwZXc" href="#p-z8AgKvwZXc" tabindex="-1" role="presentation"></a>Considera este rompecabezas: al comenzar desde el número 1 y repetidamente 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 podría alcanzarse en absoluto.</p> +<p id="rompecabezas_recursivo"><a class="p_ident" id="p-2oVOmesFcn" href="#p-2oVOmesFcn" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-HOoTgs7o63" href="#p-HOoTgs7o63" tabindex="-1" role="presentation"></a>Aquí tienes una solución recursiva:</p> @@ -337,9 +342,11 @@ <h2><a class="h_ident" id="h-ge3a45K3m3" href="#h-ge3a45K3m3" tabindex="-1" role <p><a class="p_ident" id="p-pHOyC25qCR" href="#p-pHOyC25qCR" tabindex="-1" role="presentation"></a>Ten en cuenta que este programa no necesariamente encuentra la secuencia de operaciones más <em>corta</em>. Se conforma con encontrar cualquier secuencia.</p> -<p><a class="p_ident" id="p-j6YcJ9M71I" href="#p-j6YcJ9M71I" tabindex="-1" role="presentation"></a>No te preocupes si no ves cómo funciona este código de inmediato. Vamos a trabajar juntos, ya que es un gran ejercicio de pensamiento recursivo. La función interna <code>encontrar</code> 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 comenzando desde este número, devuelve <code>null</code>.</p> +<p><a class="p_ident" id="p-iEpST8XMDQ" href="#p-iEpST8XMDQ" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-bRYy2wuEMy" href="#p-bRYy2wuEMy" tabindex="-1" role="presentation"></a>Para hacer esto, la función realiza una de 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 se devuelve. 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 devuelve <code>null</code>. 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 <code>null</code>, se devuelve. De lo contrario, se devuelve la segunda llamada, independientemente de si produce una cadena o <code>null</code>.</p> +<p><a class="p_ident" id="p-5ONAWwQTpH" href="#p-5ONAWwQTpH" tabindex="-1" role="presentation"></a>La función interna <code>encontrar</code> 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 <code>null</code>.</p> + +<p><a class="p_ident" id="p-bRYy2wuEMy" href="#p-bRYy2wuEMy" tabindex="-1" role="presentation"></a>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 <code>null</code>. 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 <code>null</code>, entonces este es el resultado que se devuelve. De lo contrario, se devuelve la segunda llamada, independientemente de si produce una cadena o <code>null</code>.</p> <p><a class="p_ident" id="p-87C75eNTfr" href="#p-87C75eNTfr" tabindex="-1" role="presentation"></a>Para entender mejor cómo esta función produce el efecto que estamos buscando, veamos todas las llamadas a <code>encontrar</code> que se hacen al buscar una solución para el número 13:</p> @@ -357,24 +364,24 @@ <h2><a class="h_ident" id="h-ge3a45K3m3" href="#h-ge3a45K3m3" tabindex="-1" role encontrar(13, "(((1 * 3) + 5) + 5)") ¡encontrado!</pre> -<p><a class="p_ident" id="p-DHJBtGEOXF" href="#p-DHJBtGEOXF" tabindex="-1" role="presentation"></a>La sangría indica la profundidad de la pila de llamadas. La primera vez que se llama a <code>encontrar</code>, la función comienza llamándose a sí misma para explorar la solución que comienza con <code>(1 + 5)</code>. Esa llamada seguirá recursivamente para explorar <em>cada</em> 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 <code>null</code> a la primera llamada. Allí, el operador <code>??</code> hace que ocurra la llamada que explora <code>(1 * 3)</code>. Esta búsqueda tiene más suerte: su primera llamada recursiva, a través de otra llamada recursiva, alcanza el número objetivo. Esa llamada más interna devuelve una cadena, y cada uno de los operadores <code>??</code> en las llamadas intermedias pasa esa cadena, devolviendo en última instancia la solución.</p> +<p><a class="p_ident" id="p-DHJBtGEOXF" href="#p-DHJBtGEOXF" tabindex="-1" role="presentation"></a>La sangría indica la profundidad de la pila de llamadas. La primera vez que se llama a <code>encontrar</code>, la función comienza llamándose a sí misma para explorar la solución que comienza con <code>(1 + 5)</code>. Esa llamada seguirá recursivamente para explorar <em>cada</em> 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 <code>null</code> en la primera llamada que se hace dentro de <code>encontrar</code>. Allí, el operador <code>??</code> hace que ocurra la llamada que explora la opción <code>(1 * 3)</code>. Esta búsqueda es más exitosa: su primera llamada recursiva, a través de <em>otra</em> llamada recursiva <em>más</em>, alcanza el número objetivo. Esa llamada más interna devuelve una cadena, y cada uno de los operadores <code>??</code> en las llamadas intermedias pasa esa cadena, devolviendo en última instancia la solución.</p> <h2><a class="h_ident" id="h-uZ0juDUWOs" href="#h-uZ0juDUWOs" tabindex="-1" role="presentation"></a>Crecimiento de funciones</h2> -<p><a class="p_ident" id="p-n9cGqWi0db" href="#p-n9cGqWi0db" tabindex="-1" role="presentation"></a>Hay dos formas más o menos naturales de introducir funciones en los programas.</p> +<p><a class="p_ident" id="p-n9cGqWi0db" href="#p-n9cGqWi0db" tabindex="-1" role="presentation"></a>Hay dos formas más o menos naturales de que las funciones aparezcan en los programas.</p> -<p><a class="p_ident" id="p-B8XV+CLjOF" href="#p-B8XV+CLjOF" tabindex="-1" role="presentation"></a>La primera ocurre cuando te encuentras escribiendo código similar varias veces. Preferirías no hacer eso, ya que tener más código significa más espacio para que se escondan los errores y más material para que las personas que intentan entender el programa lo lean. Por lo tanto, tomas la funcionalidad repetida, encuentras un buen nombre para ella y la colocas en una función.</p> +<p><a class="p_ident" id="p-B8XV+CLjOF" href="#p-B8XV+CLjOF" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-cOAhBy6EBv" href="#p-cOAhBy6EBv" tabindex="-1" role="presentation"></a>La segunda forma es que te das cuenta de que necesitas alguna funcionalidad que aún no has escrito y que suena como si mereciera su propia función. Comienzas por nombrar la función, 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í.</p> +<p><a class="p_ident" id="p-cOAhBy6EBv" href="#p-cOAhBy6EBv" tabindex="-1" role="presentation"></a>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í.</p> -<p><a class="p_ident" id="p-YZ4JKo/kXS" href="#p-YZ4JKo/kXS" tabindex="-1" role="presentation"></a>Lo difícil que 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 envolver con ella. Vamos a través de un ejemplo.</p> +<p><a class="p_ident" id="p-mPOh7njS8e" href="#p-mPOh7njS8e" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-eV6abyHMYu" href="#p-eV6abyHMYu" tabindex="-1" role="presentation"></a>Queremos escribir un programa que imprima dos números: el número de vacas y de pollos en una granja, con las palabras <code>Vacas</code> y <code>Pollos</code> después de ellos y ceros rellenados antes de ambos números para que siempre tengan tres dígitos:</p> +<p><a class="p_ident" id="p-eV6abyHMYu" href="#p-eV6abyHMYu" tabindex="-1" role="presentation"></a>Queremos escribir un programa que imprima dos números: el número de vacas y de pollos en una granja, con las palabras <code>Vacas</code> y <code>Pollos</code> 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:</p> <pre class="snippet" data-language="null" ><a class="c_ident" id="c-XuqI7+Jck5" href="#c-XuqI7+Jck5" tabindex="-1" role="presentation"></a>007 Vacas 011 Pollos</pre> -<p><a class="p_ident" id="p-SWekUgpMBV" href="#p-SWekUgpMBV" tabindex="-1" role="presentation"></a>Esto pide una función con dos argumentos: el número de vacas y el número de pollos. ¡Vamos a programar!</p> +<p><a class="p_ident" id="p-JQnh8k06kP" href="#p-JQnh8k06kP" tabindex="-1" role="presentation"></a>Esto está pidiendo una función con dos argumentos: el número de vacas y el número de pollos. ¡Vamos a programar!</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-/gtNyE5+K6" href="#c-/gtNyE5+K6" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">imprimirInventarioGranja</span>(<span class="tok-definition">vacas</span>, <span class="tok-definition">pollos</span>) { <span class="tok-keyword">let</span> <span class="tok-definition">cadenaVaca</span> = String(vacas); @@ -390,14 +397,14 @@ <h2><a class="h_ident" id="h-uZ0juDUWOs" href="#h-uZ0juDUWOs" tabindex="-1" role } imprimirInventarioGranja(<span class="tok-number">7</span>, <span class="tok-number">11</span>);</pre> -<p><a class="p_ident" id="p-WwhdZ7dyBy" href="#p-WwhdZ7dyBy" tabindex="-1" role="presentation"></a>Escribir <code>.length</code> después de una expresión de cadena nos dará la longitud de esa cadena. Por lo tanto, los bucles <code>while</code> siguen añadiendo ceros delante de las cadenas de números hasta que tengan al menos tres caracteres de longitud.</p> +<p><a class="p_ident" id="p-WwhdZ7dyBy" href="#p-WwhdZ7dyBy" tabindex="-1" role="presentation"></a>Escribir <code>.length</code> después de una expresión de cadena nos dará la longitud de esa cadena. Por lo tanto, los bucles <code>while</code> siguen añadiendo ceros delante de las cadenas de números hasta que estas tengan al menos tres caracteres de longitud.</p> -<p><a class="p_ident" id="p-/FAYGLBZga" href="#p-/FAYGLBZga" tabindex="-1" role="presentation"></a>¡Misión cumplida! Pero justo cuando estamos a punto de enviarle a la granjera el código (junto con una jugosa factura), ella llama y nos dice que también ha comenzado a criar cerdos, ¿podríamos extender el software para imprimir también los cerdos?</p> +<p><a class="p_ident" id="p-/FAYGLBZga" href="#p-/FAYGLBZga" tabindex="-1" role="presentation"></a>¡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?</p> -<p><a class="p_ident" id="p-lZT8zKVcfS" href="#p-lZT8zKVcfS" tabindex="-1" role="presentation"></a>¡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í está un primer intento:</p> +<p><a class="p_ident" id="p-lZT8zKVcfS" href="#p-lZT8zKVcfS" tabindex="-1" role="presentation"></a>¡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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-dq2WbXrkyX" href="#c-dq2WbXrkyX" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">imprimirConRellenoYEtiqueta</span>(<span class="tok-definition">numero</span>, <span class="tok-definition">etiqueta</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">cadenaNumero</span> = String(numero); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-asgTN66LEi" href="#c-asgTN66LEi" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">imprimirConRellenoYEtiqueta</span>(<span class="tok-definition">número</span>, <span class="tok-definition">etiqueta</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">cadenaNumero</span> = String(número); <span class="tok-keyword">while</span> (cadenaNumero.length < <span class="tok-number">3</span>) { cadenaNumero = <span class="tok-string">"0"</span> + cadenaNumero; } @@ -412,12 +419,12 @@ <h2><a class="h_ident" id="h-uZ0juDUWOs" href="#h-uZ0juDUWOs" tabindex="-1" role imprimirInventarioGranja(<span class="tok-number">7</span>, <span class="tok-number">11</span>, <span class="tok-number">3</span>);</pre> -<p><a class="p_ident" id="p-t15OmMTGO1" href="#p-t15OmMTGO1" tabindex="-1" role="presentation"></a>¡Funciona! Pero ese nombre, <code>imprimirConRellenoYEtiqueta</code>, es un poco incómodo. Confluye tres cosas: imprimir, rellenar con ceros y añadir una etiqueta, en una sola función.</p> +<p><a class="p_ident" id="p-t15OmMTGO1" href="#p-t15OmMTGO1" tabindex="-1" role="presentation"></a>¡Funciona! Pero ese nombre, <code>imprimirConRellenoYEtiqueta</code>, es un poco raro. Confluye tres cosas: imprimir, rellenar con ceros y añadir una etiqueta, en una sola función.</p> -<p><a class="p_ident" id="p-86HqtiCBvL" href="#p-86HqtiCBvL" tabindex="-1" role="presentation"></a>En lugar de sacar la parte repetida de nuestro programa completamente, intentemos sacar un solo <em>concepto</em>:</p> +<p><a class="p_ident" id="p-86HqtiCBvL" href="#p-86HqtiCBvL" tabindex="-1" role="presentation"></a>En lugar de extraer completamente la parte repetida de nuestro programa, intentemos sacar un solo <em>concepto</em>:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-2LDxPR7isA" href="#c-2LDxPR7isA" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">rellenarConCeros</span>(<span class="tok-definition">numero</span>, <span class="tok-definition">ancho</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">cadena</span> = String(numero); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-eFdNk+mbAU" href="#c-eFdNk+mbAU" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">rellenarConCeros</span>(<span class="tok-definition">número</span>, <span class="tok-definition">ancho</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">cadena</span> = String(número); <span class="tok-keyword">while</span> (cadena.length < ancho) { cadena = <span class="tok-string">"0"</span> + cadena; } @@ -432,25 +439,25 @@ <h2><a class="h_ident" id="h-uZ0juDUWOs" href="#h-uZ0juDUWOs" tabindex="-1" role imprimirInventarioGranja(<span class="tok-number">7</span>, <span class="tok-number">16</span>, <span class="tok-number">3</span>);</pre> -<p><a class="p_ident" id="p-Pp4JWQb2Da" href="#p-Pp4JWQb2Da" tabindex="-1" role="presentation"></a>Una función con un nombre claro y obvio como <code>rellenarConCeros</code> hace que sea más fácil para alguien que lee el código entender qué hace. Además, una función así es útil en más situaciones que solo este programa específico. Por ejemplo, podrías usarla para ayudar a imprimir tablas de números alineadas correctamente.</p> +<p><a class="p_ident" id="p-Pp4JWQb2Da" href="#p-Pp4JWQb2Da" tabindex="-1" role="presentation"></a>Una función con un nombre claro y obvio como <code>rellenarConCeros</code> 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.</p> -<p><a class="p_ident" id="p-Pe3o8sCfTl" href="#p-Pe3o8sCfTl" tabindex="-1" role="presentation"></a>¿Qué tan inteligente y versátil <em>debería</em> 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.</p> +<p><a class="p_ident" id="p-vNdGmtvx+h" href="#p-vNdGmtvx+h" tabindex="-1" role="presentation"></a>¿Cómo de inteligente y versátil <em>debería</em> 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.</p> -<p><a class="p_ident" id="p-CvP+uxDh/X" href="#p-CvP+uxDh/X" tabindex="-1" role="presentation"></a>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” genéricos para cada trozo de funcionalidad que te encuentres. Resiste esa tentación. No lograrás hacer ningún trabajo real: estarás demasiado ocupado escribiendo código que nunca usarás.</p> +<p><a class="p_ident" id="p-CvP+uxDh/X" href="#p-CvP+uxDh/X" tabindex="-1" role="presentation"></a>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.</p> <h2 id="puro"><a class="h_ident" id="h-FYE5dPFb4a" href="#h-FYE5dPFb4a" tabindex="-1" role="presentation"></a>Funciones y efectos secundarios</h2> -<p><a class="p_ident" id="p-WDphrqGwmP" href="#p-WDphrqGwmP" tabindex="-1" role="presentation"></a>Las funciones pueden dividirse aproximadamente en aquellas que se llaman por sus efectos secundarios y aquellas que se llaman por su valor de retorno (aunque también es posible tener efectos secundarios y devolver un valor).</p> +<p><a class="p_ident" id="p-WDphrqGwmP" href="#p-WDphrqGwmP" tabindex="-1" role="presentation"></a>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).</p> -<p><a class="p_ident" id="p-HhXOUWHo2Y" href="#p-HhXOUWHo2Y" tabindex="-1" role="presentation"></a>La primera función auxiliar en el ejemplo de la granja, <code>imprimirConRellenoYEtiqueta</code>, se llama por su efecto secundario: imprime una línea. La segunda versión, <code>rellenarConCeros</code>, 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.</p> +<p><a class="p_ident" id="p-HhXOUWHo2Y" href="#p-HhXOUWHo2Y" tabindex="-1" role="presentation"></a>La primera función auxiliar en el ejemplo de la granja, <code>imprimirConRellenoYEtiqueta</code>, se llama por su efecto secundario: imprimir una línea. La segunda versión, <code>rellenarConCeros</code>, 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.</p> -<p><a class="p_ident" id="p-ZQPtksrZZc" href="#p-ZQPtksrZZc" tabindex="-1" role="presentation"></a>Una función <em>pura</em> es un tipo específico de función productora de valor que no solo no tiene efectos secundarios, sino que tampoco depende de efectos secundarios de otro código, por ejemplo, no lee enlaces 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 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. Las funciones no puras tienden a requerir más andamiaje para probarlas.</p> +<p><a class="p_ident" id="p-ZQPtksrZZc" href="#p-ZQPtksrZZc" tabindex="-1" role="presentation"></a>Una función <em>pura</em> 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.</p> -<p><a class="p_ident" id="p-3s9sqQnXFN" href="#p-3s9sqQnXFN" tabindex="-1" role="presentation"></a>Aún así, no hay necesidad de sentirse mal al escribir funciones que no son puras. Los efectos secundarios a menudo son útiles. No hay forma de escribir una versión pura de <code>console.log</code>, por ejemplo, y es bueno tener <code>console.log</code>. Algunas operaciones también son más fáciles de expresar de manera eficiente cuando usamos efectos secundarios.</p> +<p><a class="p_ident" id="p-3s9sqQnXFN" href="#p-3s9sqQnXFN" tabindex="-1" role="presentation"></a>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 <code>console.log</code>, por ejemplo, y es bueno tener <code>console.log</code>. Algunas operaciones también son más fáciles de expresar de manera eficiente cuando usamos efectos secundarios.</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-/BH7LujdnN" href="#p-/BH7LujdnN" tabindex="-1" role="presentation"></a>Este capítulo te enseñó cómo escribir tus propias funciones. La palabra clave <code>function</code>, 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 de flecha son otra forma de crear funciones.</p> +<p><a class="p_ident" id="p-/BH7LujdnN" href="#p-/BH7LujdnN" tabindex="-1" role="presentation"></a>Este capítulo te enseña cómo escribir tus propias funciones. La palabra clave <code>function</code>, 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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-nraStLDxcy" href="#c-nraStLDxcy" tabindex="-1" role="presentation"></a><span class="tok-comment">// Definir f para contener un valor de función</span> <span class="tok-keyword">const</span> <span class="tok-definition">f</span> = <span class="tok-keyword">function</span>(<span class="tok-definition">a</span>) { @@ -465,15 +472,15 @@ <h2><a class="h_ident" id="h-NUFOUyK+lw" href="#h-NUFOUyK+lw" tabindex="-1" role <span class="tok-comment">// Un valor de función menos verboso</span> <span class="tok-keyword">let</span> <span class="tok-definition">h</span> = <span class="tok-definition">a</span> => a % <span class="tok-number">3</span>;</pre> -<p><a class="p_ident" id="p-0ZGPiC9ulA" href="#p-0ZGPiC9ulA" tabindex="-1" role="presentation"></a>Una parte clave para entender las funciones es comprender los ámbitos (scopes). Cada bloque crea un nuevo ámbito. Los parámetros y los enlaces declarados en un ámbito dado son locales y no son visibles desde el exterior. Los enlaces declarados con <code>var</code> se comportan de manera diferente: terminan en el ámbito de la función más cercana o en el ámbito global.</p> +<p><a class="p_ident" id="p-0ZGPiC9ulA" href="#p-0ZGPiC9ulA" tabindex="-1" role="presentation"></a>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 <code>var</code> se comportan de manera diferente: terminan en el ámbito de la función más cercana o en el ámbito global.</p> -<p><a class="p_ident" id="p-rUVLw2xd6X" href="#p-rUVLw2xd6X" tabindex="-1" role="presentation"></a>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 piezas que hacen cosas específicas.</p> +<p><a class="p_ident" id="p-rUVLw2xd6X" href="#p-rUVLw2xd6X" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2> <h3><a class="i_ident" id="i-mzHH/MSSBA" href="#i-mzHH/MSSBA" tabindex="-1" role="presentation"></a>Mínimo</h3> -<p><a class="p_ident" id="p-F1St/b+Tyk" href="#p-F1St/b+Tyk" tabindex="-1" role="presentation"></a>El <a href="02_program_structure.html#return_values">capítulo previo</a> presentó la función estándar <code>Math.min</code> que devuelve su menor argumento. Ahora podemos escribir una función como esa nosotros mismos. Define la función <code>min</code> que toma dos argumentos y devuelve su mínimo.</p> +<p><a class="p_ident" id="p-xHYMbkCoZJ" href="#p-xHYMbkCoZJ" tabindex="-1" role="presentation"></a>En el <a href="02_program_structure.html#return_values">capítulo previo</a> se introdujo la función estándar <code>Math.min</code> que devuelve el menor de sus argumentos. Ahora podemos escribir una función como esa nosotros mismos. Define la función <code>min</code> que toma dos argumentos y devuelve su mínimo.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-YPzD49JsIQ" href="#c-YPzD49JsIQ" tabindex="-1" role="presentation"></a><span class="tok-comment">// Tu código aquí.</span> @@ -484,7 +491,7 @@ <h3><a class="i_ident" id="i-mzHH/MSSBA" href="#i-mzHH/MSSBA" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-/a3wyCwq6P" href="#p-/a3wyCwq6P" tabindex="-1" role="presentation"></a>Si tienes problemas para colocar 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.</p> +<p><a class="p_ident" id="p-/a3wyCwq6P" href="#p-/a3wyCwq6P" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-lcSp8UwPFN" href="#p-lcSp8UwPFN" tabindex="-1" role="presentation"></a>Una función puede contener múltiples declaraciones <code>return</code>.</p> @@ -506,7 +513,7 @@ <h3><a class="i_ident" id="i-ge3a45K3m3" href="#i-ge3a45K3m3" tabindex="-1" role <li> -<p><a class="p_ident" id="p-7c1vmInqwj" href="#p-7c1vmInqwj" tabindex="-1" role="presentation"></a>Para cualquier otro número <em>N</em>, su paridad es la misma que <em>N</em> - 2.</p></li></ul> +<p><a class="p_ident" id="p-dNxvDqgaxD" href="#p-dNxvDqgaxD" tabindex="-1" role="presentation"></a>Para cualquier otro número <em>N</em>, su paridad es la misma que la de <em>N</em> - 2.</p></li></ul> <p><a class="p_ident" id="p-1AgC7/UeM8" href="#p-1AgC7/UeM8" tabindex="-1" role="presentation"></a>Define una función recursiva <code>isEven</code> que corresponda a esta descripción. La función debe aceptar un solo parámetro (un número entero positivo) y devolver un booleano.</p> @@ -521,23 +528,26 @@ <h3><a class="i_ident" id="i-ge3a45K3m3" href="#i-ge3a45K3m3" tabindex="-1" role console.log(isEven(-<span class="tok-number">1</span>)); <span class="tok-comment">// → ??</span></pre> +<div class="translator-note"><p><strong>N. del T.:</strong> 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.</p> +</div> + <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-9dAuiITSNm" href="#p-9dAuiITSNm" tabindex="-1" role="presentation"></a>Es probable que tu función se parezca en cierta medida a la función interna <code>encontrar</code> en el ejemplo recursivo <code>encontrarSolucion</code> <a href="03_functions.html#recursive_puzzle">ejemplo</a> de este capítulo, con una cadena <code>if</code>/<code>else if</code>/<code>else</code> que prueba cuál de los tres casos aplica. El <code>else</code> final, correspondiente al tercer caso, realiza la llamada recursiva. Cada una de las ramas debe contener una declaración <code>return</code> o de alguna otra manera asegurarse de que se devuelva un valor específico.</p> +<p><a class="p_ident" id="p-9dAuiITSNm" href="#p-9dAuiITSNm" tabindex="-1" role="presentation"></a>Es probable que tu función se parezca en cierta medida a la función interna <code>encontrar</code> en el ejemplo recursivo <code>encontrarSolucion</code> <a href="03_functions.html#recursive_puzzle">ejemplo</a> de este capítulo, con una cadena <code>if</code>/<code>else if</code>/<code>else</code> que prueba cuál de los tres casos aplica. El <code>else</code> final, correspondiente al tercer caso, realiza la llamada recursiva. Cada una de las ramas debe contener una declaración <code>return</code> o de algún modo, asegurarse de que se devuelva un valor específico.</p> -<p><a class="p_ident" id="p-GSq+lWAHif" href="#p-GSq+lWAHif" tabindex="-1" role="presentation"></a>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. Eventualmente se quedará sin espacio en la pila y se abortará.</p> +<p><a class="p_ident" id="p-4x41LzDLlG" href="#p-4x41LzDLlG" tabindex="-1" role="presentation"></a>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á.</p> </div></details> -<h3><a class="i_ident" id="i-zSH1twIVoz" href="#i-zSH1twIVoz" tabindex="-1" role="presentation"></a>Contando frijoles</h3> +<h3><a class="i_ident" id="i-VbRGT/6ZvL" href="#i-VbRGT/6ZvL" tabindex="-1" role="presentation"></a>Contando minuciosamente</h3> -<p><a class="p_ident" id="p-oTwY6Ez8Oj" href="#p-oTwY6Ez8Oj" tabindex="-1" role="presentation"></a>Puedes obtener el *ésimo carácter, o letra, de una cadena escribiendo <code>[N]</code> después de la cadena (por ejemplo, <code>cadena[2]</code>). El valor resultante será una cadena que contiene solo un carácter (por ejemplo, <code>"b"</code>). El primer carácter tiene la posición 0, lo que hace que el último se encuentre en la posición <code>cadena.<wbr>length - 1</code>. En otras palabras, una cadena de dos caracteres tiene longitud 2, y sus caracteres tienen posiciones 0 y 1.</p> +<p><a class="p_ident" id="p-oTwY6Ez8Oj" href="#p-oTwY6Ez8Oj" tabindex="-1" role="presentation"></a>Puedes obtener el N-ésimo carácter, o letra, de una cadena escribiendo <code>[N]</code> después de la cadena (por ejemplo, <code>cadena[2]</code>). El valor resultante será una cadena que contiene solo un carácter (por ejemplo, <code>"b"</code>). El primer carácter tiene la posición 0, lo que hace que el último se encuentre en la posición <code>cadena.<wbr>length - 1</code>. En otras palabras, una cadena de dos caracteres tiene longitud 2, y sus caracteres tienen posiciones 0 y 1.</p> -<p><a class="p_ident" id="p-w7W3kFYAvT" href="#p-w7W3kFYAvT" tabindex="-1" role="presentation"></a>Escribe una función <code>contarBs</code> 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.</p> +<p><a class="p_ident" id="p-w7W3kFYAvT" href="#p-w7W3kFYAvT" tabindex="-1" role="presentation"></a>Escribe una función <code>countBs</code> (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.</p> -<p><a class="p_ident" id="p-ogbJyesqnN" href="#p-ogbJyesqnN" tabindex="-1" role="presentation"></a>A continuación, escribe una función llamada <code>contarCaracter</code> que se comporte como <code>contarBs</code>, 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 <code>contarBs</code> para hacer uso de esta nueva función.</p> +<p><a class="p_ident" id="p-ogbJyesqnN" href="#p-ogbJyesqnN" tabindex="-1" role="presentation"></a>A continuación, escribe una función llamada <code>countChar</code> (contarCaracter) que se comporte como <code>countBs</code>, 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 <code>countBs</code> para hacer uso de esta nueva función.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-fTMxwrQQBV" href="#c-fTMxwrQQBV" tabindex="-1" role="presentation"></a><span class="tok-comment">// Your code here.</span> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-xGa4lbtrtf" href="#c-xGa4lbtrtf" tabindex="-1" role="presentation"></a><span class="tok-comment">// Tu código aquí.</span> console.log(countBs(<span class="tok-string">"BOB"</span>)); <span class="tok-comment">// → 2</span> @@ -546,9 +556,9 @@ <h3><a class="i_ident" id="i-zSH1twIVoz" href="#i-zSH1twIVoz" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-N+elrvhLar" href="#p-N+elrvhLar" tabindex="-1" role="presentation"></a>Tu función necesida un bucle que mire cada carácter en la cadena. Puede ejecutar un índice desde cero hasta uno menos que su longitud (<code>< string.<wbr>length</code>). 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 de contador. Una vez que el bucle ha terminado, el contador puede ser devuelto.</p> +<p><a class="p_ident" id="p-N+elrvhLar" href="#p-N+elrvhLar" tabindex="-1" role="presentation"></a>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 (<code>< cadena.<wbr>length</code>). 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.</p> -<p><a class="p_ident" id="p-DijC5CkxO1" href="#p-DijC5CkxO1" tabindex="-1" role="presentation"></a>Ten cuidado de que todas las vinculaciones utilizadas en la función sean <em>locales</em> a la función, declarándolas correctamente con la palabra clave <code>let</code> o <code>const</code>.</p> +<p><a class="p_ident" id="p-DijC5CkxO1" href="#p-DijC5CkxO1" tabindex="-1" role="presentation"></a>Ten cuidado de que todas las asociaciones utilizadas en la función sean <em>locales</em> a la función, declarándolas correctamente con la palabra clave <code>let</code> o <code>const</code>.</p> </div></details><nav><a href="02_program_structure.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="04_data.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button> </nav> diff --git a/html/04_data.html b/html/04_data.html index b8533d55..d07583a8 100644 --- a/html/04_data.html +++ b/html/04_data.html @@ -14,60 +14,64 @@ <h1>Estructuras de datos: Objetos y Arrays</h1> <blockquote> -<p><a class="p_ident" id="p-uBqkSHTkk2" href="#p-uBqkSHTkk2" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-uBqkSHTkk2" href="#p-uBqkSHTkk2" tabindex="-1" role="presentation"></a>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.</p> <footer>Charles Babbage, <cite>Passages from the Life of a Philosopher (1864)</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_4.jpg" alt="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."></figure> -<p><a class="p_ident" id="p-uZ24L8cwJG" href="#p-uZ24L8cwJG" tabindex="-1" role="presentation"></a>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 <em>objetos</em> nos permiten agrupar valores, incluyendo otros objetos, para construir estructuras más complejas.</p> +<p><a class="p_ident" id="p-uZ24L8cwJG" href="#p-uZ24L8cwJG" tabindex="-1" role="presentation"></a>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 <em>objetos</em> nos permiten agrupar valores —incluyendo otros objetos— para construir estructuras más complejas.</p> -<p><a class="p_ident" id="p-lVDoYbaX21" href="#p-lVDoYbaX21" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-lVDoYbaX21" href="#p-lVDoYbaX21" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-zgIyY7RzKX" href="#p-zgIyY7RzKX" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-/Oa2VS1dKS" href="#p-/Oa2VS1dKS" tabindex="-1" role="presentation"></a>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.</p> -<h2><a class="h_ident" id="h-Yt9O8dBHYO" href="#h-Yt9O8dBHYO" tabindex="-1" role="presentation"></a>El hombreardilla</h2> +<h2><a class="h_ident" id="h-f3JQiZSoj8" href="#h-f3JQiZSoj8" tabindex="-1" role="presentation"></a>El hombre ardilla</h2> -<p><a class="p_ident" id="p-Llg6mgOpjE" href="#p-Llg6mgOpjE" tabindex="-1" role="presentation"></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.</p> +<p><a class="p_ident" id="p-PMkgIe/lwz" href="#p-PMkgIe/lwz" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-JWDybfL/03" href="#p-JWDybfL/03" tabindex="-1" role="presentation"></a>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 (<em>eso</em> 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.</p> +<p><a class="p_ident" id="p-JWDybfL/03" href="#p-JWDybfL/03" tabindex="-1" role="presentation"></a>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 (<em>eso</em> 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.</p> -<p><a class="p_ident" id="p-e7IE8PV9ue" href="#p-e7IE8PV9ue" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-e7IE8PV9ue" href="#p-e7IE8PV9ue" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-pz0x8J1W0J" href="#p-pz0x8J1W0J" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-ECvuhHyvyI" href="#p-ECvuhHyvyI" tabindex="-1" role="presentation"></a>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.</p> + +<p><a class="p_ident" id="p-8Y5Ju5fKpt" href="#p-8Y5Ju5fKpt" tabindex="-1" role="presentation"></a>Lo primero que necesita es una estructura de datos para almacenar esta información.</p> <h2><a class="h_ident" id="h-7lZW6LyfA5" href="#h-7lZW6LyfA5" tabindex="-1" role="presentation"></a>Conjuntos de datos</h2> -<p><a class="p_ident" id="p-970mNfCcKX" href="#p-970mNfCcKX" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-970mNfCcKX" href="#p-970mNfCcKX" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-Q4HkJG7NqQ" href="#p-Q4HkJG7NqQ" tabindex="-1" role="presentation"></a>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 <code>"2 3 5 7 11"</code> 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.</p> +<p><a class="p_ident" id="p-0JXdWfoFby" href="#p-0JXdWfoFby" tabindex="-1" role="presentation"></a>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 <code>"2 3 5 7 11"</code> 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.</p> -<p><a class="p_ident" id="p-n7TWkKijww" href="#p-n7TWkKijww" tabindex="-1" role="presentation"></a>Afortunadamente, JavaScript proporciona un tipo de dato específicamente para almacenar secuencias de valores. Se llama un <em>array</em> y se escribe como una lista de valores entre corchetes, separados por comas:</p> +<p><a class="p_ident" id="p-n7TWkKijww" href="#p-n7TWkKijww" tabindex="-1" role="presentation"></a>Afortunadamente, JavaScript proporciona un tipo de dato específicamente para almacenar secuencias de valores. Se llama <em>array</em> (o arreglo) y se escribe como una lista de valores entre corchetes, separados por comas:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-T9JITA1P7T" href="#c-T9JITA1P7T" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">listaDeNumeros</span> = [<span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">5</span>, <span class="tok-number">7</span>, <span class="tok-number">11</span>]; -console.log(listaDeNumeros[<span class="tok-number">2</span>]); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-RQ8SIi9ZEx" href="#c-RQ8SIi9ZEx" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">listaDeNúmeros</span> = [<span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">5</span>, <span class="tok-number">7</span>, <span class="tok-number">11</span>]; +console.log(listaDeNúmeros[<span class="tok-number">2</span>]); <span class="tok-comment">// → 5</span> -console.log(listaDeNumeros[<span class="tok-number">0</span>]); +console.log(listaDeNúmeros[<span class="tok-number">0</span>]); <span class="tok-comment">// → 2</span> -console.log(listaDeNumeros[<span class="tok-number">2</span> - <span class="tok-number">1</span>]); +console.log(listaDeNúmeros[<span class="tok-number">2</span> - <span class="tok-number">1</span>]); <span class="tok-comment">// → 3</span></pre> <p><a class="p_ident" id="p-ViqdBSDQdu" href="#p-ViqdBSDQdu" tabindex="-1" role="presentation"></a>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 <em>índice</em> dado por la expresión en los corchetes.</p> -<p id="array_indexing"><a class="p_ident" id="p-xa1NXfk7q0" href="#p-xa1NXfk7q0" tabindex="-1" role="presentation"></a>El primer índice de un array es cero, no uno, por lo que el primer elemento se recupera con <code>listaDeNumeros[0]</code>. 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.</p> +<p id="array_indexing"><a class="p_ident" id="p-xa1NXfk7q0" href="#p-xa1NXfk7q0" tabindex="-1" role="presentation"></a>El primer índice de un array es cero, no uno, por lo que el primer elemento se recupera con <code>listaDeNúmeros[0]</code>. 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.</p> <h2 id="propiedades"><a class="h_ident" id="h-jddsYRbSVf" href="#h-jddsYRbSVf" tabindex="-1" role="presentation"></a>Propiedades</h2> -<p><a class="p_ident" id="p-98iY5haEll" href="#p-98iY5haEll" tabindex="-1" role="presentation"></a>Hemos visto algunas expresiones como <code>miCadena.length</code> (para obtener la longitud de una cadena) y <code>Math.max</code> (la función máxima) en capítulos anteriores. Estas expresiones acceden a una <em>propiedad</em> de algún valor. En el primer caso, accedemos a la propiedad <code>length</code> del valor en <code>miCadena</code>. En el segundo, accedemos a la propiedad llamada <code>max</code> en el objeto <code>Math</code> (que es una colección de constantes y funciones relacionadas con matemáticas).</p> +<p><a class="p_ident" id="p-98iY5haEll" href="#p-98iY5haEll" tabindex="-1" role="presentation"></a>Hemos visto algunas expresiones como <code>miCadena.length</code> (para obtener la longitud de una cadena) y <code>Math.max</code> (la función máximo) en capítulos anteriores. Estas expresiones acceden a una <em>propiedad</em> de un valor. En el primer caso, accedemos a la propiedad <code>length</code> del valor en <code>miCadena</code>. En el segundo, accedemos a la propiedad llamada <code>max</code> en el objeto <code>Math</code> (que es una colección de constantes y funciones relacionadas con matemáticas).</p> -<p><a class="p_ident" id="p-+scqPh9V5v" href="#p-+scqPh9V5v" tabindex="-1" role="presentation"></a>Casi todos los valores de JavaScript tienen propiedades. Las excepciones son <code>null</code> y <code>undefined</code>. Si intentas acceder a una propiedad en uno de estos valores no definidos, obtendrás un error:</p> +<p><a class="p_ident" id="p-+scqPh9V5v" href="#p-+scqPh9V5v" tabindex="-1" role="presentation"></a>Casi todos los valores de JavaScript tienen propiedades. Las excepciones son <code>null</code> y <code>undefined</code>. Si intentas acceder a una propiedad de uno de estos valores no definidos, obtendrás un error:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-bkkKcmEF+O" href="#c-bkkKcmEF+O" tabindex="-1" role="presentation"></a><span class="tok-keyword">null</span>.length; <span class="tok-comment">// → TypeError: null no tiene propiedades</span></pre> -<p><a class="p_ident" id="p-y1nMt6QLiv" href="#p-y1nMt6QLiv" tabindex="-1" role="presentation"></a>Las dos formas principales de acceder a propiedades en JavaScript son con un punto y con corchetes. Tanto <code>valor.x</code> como <code>valor[x]</code> acceden a una propiedad en <code>valor</code>, pero no necesariamente a la misma propiedad. La diferencia radica en cómo se interpreta <code>x</code>. 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 <em>evaluada</em> para obtener el nombre de la propiedad. Mientras que <code>valor.x</code> obtiene la propiedad de <code>valor</code> llamada “x”, <code>valor[x]</code> toma el valor de la variable llamada <code>x</code> y lo utiliza, convertido a cadena, como nombre de propiedad. Si sabes que la propiedad en la que estás interesado se llama <em>color</em>, dices <code>valor.color</code>. Si quieres extraer la propiedad nombrada por el valor almacenado en la vinculación <code>i</code>, dices <code>valor[i]</code>. 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 <em>2</em> o <em>John Doe</em>, debes utilizar corchetes: <code>valor[2]</code> o <code>valor["John Doe"]</code>.</p> +<p><a class="p_ident" id="p-ajmThhgyTR" href="#p-ajmThhgyTR" tabindex="-1" role="presentation"></a>Las dos formas principales de acceder a propiedades en JavaScript son con un punto y con corchetes. Tanto <code>valor.x</code> como <code>valor[x]</code> acceden a una propiedad en <code>valor</code>, pero no necesariamente a la misma propiedad. La diferencia radica en cómo se interpreta <code>x</code>. 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 <em>evaluada</em> para obtener el nombre de la propiedad. Mientras que <code>valor.x</code> obtiene la propiedad de <code>valor</code> llamada “x”, <code>valor[x]</code> toma el valor de la variable llamada <code>x</code> y lo utiliza, convertido a cadena, como nombre de propiedad.</p> + +<p><a class="p_ident" id="p-HoJbZI/iUh" href="#p-HoJbZI/iUh" tabindex="-1" role="presentation"></a>Si sabes que la propiedad en la que estás interesado se llama <em>color</em>, dices <code>valor.color</code>. Si quieres extraer la propiedad nombrada por el valor almacenado en la asociación <code>i</code>, dices <code>valor[i]</code>. 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 <em>2</em> o <em>John Doe</em>, tendrás que usar corchetes: <code>valor[2]</code> o <code>valor["John Doe"]</code>.</p> -<p><a class="p_ident" id="p-bwCgxJ/4pU" href="#p-bwCgxJ/4pU" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-bwCgxJ/4pU" href="#p-bwCgxJ/4pU" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-ChTe2pKBga" href="#p-ChTe2pKBga" tabindex="-1" role="presentation"></a>Al igual que las cadenas de texto, los arrays tienen una propiedad <code>length</code> que nos dice cuántos elementos tiene el array.</p> @@ -81,13 +85,13 @@ <h2 id="métodos"><a class="h_ident" id="h-OYddlNFqr1" href="#h-OYddlNFqr1" tabi console.log(doh.toUpperCase()); <span class="tok-comment">// → DOH</span></pre> -<p><a class="p_ident" id="p-ZCGgfo//AN" href="#p-ZCGgfo//AN" tabindex="-1" role="presentation"></a>Cada cadena de texto tiene una propiedad <code>toUpperCase</code>. Cuando se llama, devolverá una copia de la cadena en la que todas las letras se han convertido a mayúsculas. También existe <code>toLowerCase</code>, que hace lo contrario.</p> +<p><a class="p_ident" id="p-wVfElfjday" href="#p-wVfElfjday" tabindex="-1" role="presentation"></a>Toda cadena de texto tiene una propiedad <code>toUpperCase</code>. Cuando se llama, devolverá una copia de la cadena en la que todas las letras se han convertido a mayúsculas. También existe <code>toLowerCase</code>, que hace lo contrario.</p> <p><a class="p_ident" id="p-Nx0cynjaXa" href="#p-Nx0cynjaXa" tabindex="-1" role="presentation"></a>Curiosamente, aunque la llamada a <code>toUpperCase</code> no pasa argumentos, de alguna manera la función tiene acceso a la cadena <code>"Doh"</code>, el valor cuya propiedad llamamos. Descubrirás cómo funciona esto en el <a href="06_object.html#obj_methods">Capítulo 6</a>.</p> -<p><a class="p_ident" id="p-weOH3L+kzO" href="#p-weOH3L+kzO" tabindex="-1" role="presentation"></a>Las propiedades que contienen funciones generalmente se llaman <em>métodos</em> del valor al que pertenecen, como en “<code>toUpperCase</code> es un método de una cadena”.</p> +<p><a class="p_ident" id="p-weOH3L+kzO" href="#p-weOH3L+kzO" tabindex="-1" role="presentation"></a>Las propiedades que contienen funciones generalmente se llaman <em>métodos</em> del valor al que pertenecen. Por ejemplo, <code>toUpperCase</code> es un método de una cadena.</p> -<p id="métodos_de_array"><a class="p_ident" id="p-jt+3lZPIHN" href="#p-jt+3lZPIHN" tabindex="-1" role="presentation"></a>Este ejemplo demuestra dos métodos que puedes utilizar para manipular arrays:</p> +<p id="métodos_de_array"><a class="p_ident" id="p-mx0+5l8+WL" href="#p-mx0+5l8+WL" tabindex="-1" role="presentation"></a>Este ejemplo muestra dos métodos que puedes utilizar para manipular arrays:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-hdp5Ku4pqK" href="#c-hdp5Ku4pqK" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">secuencia</span> = [<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>]; secuencia.push(<span class="tok-number">4</span>); @@ -101,42 +105,42 @@ <h2 id="métodos"><a class="h_ident" id="h-OYddlNFqr1" href="#h-OYddlNFqr1" tabi <p><a class="p_ident" id="p-2CKsdnwOqg" href="#p-2CKsdnwOqg" tabindex="-1" role="presentation"></a>El método <code>push</code> agrega valores al final de un array. El método <code>pop</code> hace lo opuesto, eliminando el último valor en el array y devolviéndolo.</p> -<p><a class="p_ident" id="p-0vFVgtd2FT" href="#p-0vFVgtd2FT" tabindex="-1" role="presentation"></a>Estos nombres un tanto tontos son términos tradicionales para operaciones en una <em>pila</em>. 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 <a href="03_functions.html#stack">capítulo anterior</a>, que es una instancia de la misma idea.</p> +<p><a class="p_ident" id="p-oQFu6pVX4r" href="#p-oQFu6pVX4r" tabindex="-1" role="presentation"></a>Estos nombres medio tontos son términos tradicionales (en inglés) para operaciones en una <em>pila</em>. 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 <a href="03_functions.html#stack">capítulo anterior</a>, que es un ejemplo de esta idea.</p> <h2><a class="h_ident" id="h-WSNvRpk0Lx" href="#h-WSNvRpk0Lx" tabindex="-1" role="presentation"></a>Objetos</h2> -<p><a class="p_ident" id="p-5YPu3IsKrc" href="#p-5YPu3IsKrc" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-5YPu3IsKrc" href="#p-5YPu3IsKrc" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-ClPqABLBeQ" href="#p-ClPqABLBeQ" tabindex="-1" role="presentation"></a>Los valores del tipo object son colecciones arbitrarias de propiedades. Una forma de crear un objeto es usando llaves como una expresión:</p> +<p><a class="p_ident" id="p-tII7zf4qbZ" href="#p-tII7zf4qbZ" tabindex="-1" role="presentation"></a>Los valores del tipo object son colecciones arbitrarias de propiedades. Una forma de crear un objeto es usando llaves como expresión.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-N18hBgwOzu" href="#c-N18hBgwOzu" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">dia1</span> = { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-G0cDiogPnq" href="#c-G0cDiogPnq" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">día1</span> = { <span class="tok-definition">hombreArdilla</span>: false, <span class="tok-definition">eventos</span>: [<span class="tok-string">"trabajo"</span>, <span class="tok-string">"tocó árbol"</span>, <span class="tok-string">"pizza"</span>, <span class="tok-string">"correr"</span>] }; -console.log(dia1.hombreArdilla); +console.log(día1.hombreArdilla); <span class="tok-comment">// → false</span> -console.log(dia1.lobo); +console.log(día1.lobo); <span class="tok-comment">// → undefined</span> -dia1.lobo = false; -console.log(dia1.lobo); +día1.lobo = false; +console.log(día1.lobo); <span class="tok-comment">// → false</span></pre> -<p><a class="p_ident" id="p-jJo545SDUg" href="#p-jJo545SDUg" tabindex="-1" role="presentation"></a>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:</p> +<p><a class="p_ident" id="p-jJo545SDUg" href="#p-jJo545SDUg" tabindex="-1" role="presentation"></a>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:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-U3ztim3/FH" href="#c-U3ztim3/FH" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">descripciones</span> = { <span class="tok-definition">trabajo</span>: <span class="tok-string">"Fui a trabajar"</span>, <span class="tok-string">"tocó árbol"</span>: <span class="tok-string">"Tocó un árbol"</span> };</pre> -<p><a class="p_ident" id="p-iI/V4RR9x1" href="#p-iI/V4RR9x1" tabindex="-1" role="presentation"></a>Esto significa que las llaves tienen <em>dos</em> 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 <code>n => {prop: n}</code>, 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.</p> +<p><a class="p_ident" id="p-iI/V4RR9x1" href="#p-iI/V4RR9x1" tabindex="-1" role="presentation"></a>Esto significa que las llaves tienen <em>dos</em> 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 <code>n => {prop: n}</code>, 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.</p> -<p><a class="p_ident" id="p-pIHwuhmuqu" href="#p-pIHwuhmuqu" tabindex="-1" role="presentation"></a>Al leer una propiedad que no existe, obtendrás el valor <code>undefined</code>.</p> +<p><a class="p_ident" id="p-4jHtI+XGL2" href="#p-4jHtI+XGL2" tabindex="-1" role="presentation"></a>Leer una propiedad que no existe dará como resultado el valor <code>undefined</code>.</p> <p><a class="p_ident" id="p-yY6+cVDGNX" href="#p-yY6+cVDGNX" tabindex="-1" role="presentation"></a>Es posible asignar un valor a una expresión de propiedad con el operador <code>=</code>. Esto reemplazará el valor de la propiedad si ya existía o creará una nueva propiedad en el objeto si no existía.</p> -<p><a class="p_ident" id="p-AT+UTZF2Ri" href="#p-AT+UTZF2Ri" tabindex="-1" role="presentation"></a>Para volver brevemente a nuestro modelo de tentáculos de enlaces, los enlaces de propiedad son similares. <em>Agarran</em> 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.</p> +<p><a class="p_ident" id="p-9T1BYF+r/E" href="#p-9T1BYF+r/E" tabindex="-1" role="presentation"></a>Por volver un momento a nuestro modelo de tentáculos para las asociaciones —las asociaciones de propiedad son parecidas. Estas <em>agarran</em> 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.</p> -<p><a class="p_ident" id="p-K/iJrYXRNI" href="#p-K/iJrYXRNI" tabindex="-1" role="presentation"></a>El operador <code>delete</code> 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.</p> +<p><a class="p_ident" id="p-UjOfcliCbh" href="#p-UjOfcliCbh" tabindex="-1" role="presentation"></a>El operador <code>delete</code> 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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-799dLRRJtg" href="#c-799dLRRJtg" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">unObjeto</span> = {<span class="tok-definition">izquierda</span>: <span class="tok-number">1</span>, <span class="tok-definition">derecha</span>: <span class="tok-number">2</span>}; console.log(unObjeto.izquierda); @@ -149,9 +153,9 @@ <h2><a class="h_ident" id="h-WSNvRpk0Lx" href="#h-WSNvRpk0Lx" tabindex="-1" role console.log(<span class="tok-string">"derecha"</span> <span class="tok-keyword">in</span> unObjeto); <span class="tok-comment">// → true</span></pre> -<p><a class="p_ident" id="p-iXX++nL6lz" href="#p-iXX++nL6lz" tabindex="-1" role="presentation"></a>El operador binario <code>in</code>, 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 <code>undefined</code> y realmente borrarla es que, en el primer caso, el objeto todavía <em>tiene</em> la propiedad (simplemente no tiene un valor muy interesante), mientras que en el segundo caso la propiedad ya no está presente y <code>in</code> devolverá <code>false</code>.</p> +<p><a class="p_ident" id="p-pkTJeUlOOd" href="#p-pkTJeUlOOd" tabindex="-1" role="presentation"></a>El operador binario <code>in</code>, 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 <code>undefined</code> y realmente borrarla es que, en el primer caso, el objeto todavía <em>tiene</em> la propiedad (simplemente no tiene un valor muy interesante), mientras que en el segundo caso la propiedad ya no está presente e <code>in</code> devolverá <code>false</code>.</p> -<p><a class="p_ident" id="p-GjyIt1sgGP" href="#p-GjyIt1sgGP" tabindex="-1" role="presentation"></a>Para averiguar qué propiedades tiene un objeto, puedes utilizar la función <code>Object.keys</code>. Al darle la función un objeto, devolverá un array de cadenas: los nombres de las propiedades del objeto:</p> +<p><a class="p_ident" id="p-GjyIt1sgGP" href="#p-GjyIt1sgGP" tabindex="-1" role="presentation"></a>Para averiguar qué propiedades tiene un objeto, puedes utilizar la función <code>Object.keys</code>. Dale a la función un objeto, y te devolverá un array de cadenas: los nombres de las propiedades del objeto.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-0oaalmpqn6" href="#c-0oaalmpqn6" tabindex="-1" role="presentation"></a>console.log(Object.keys({<span class="tok-definition">x</span>: <span class="tok-number">0</span>, <span class="tok-definition">y</span>: <span class="tok-number">0</span>, <span class="tok-definition">z</span>: <span class="tok-number">2</span>})); <span class="tok-comment">// → ["x", "y", "z"]</span></pre> @@ -163,9 +167,9 @@ <h2><a class="h_ident" id="h-WSNvRpk0Lx" href="#h-WSNvRpk0Lx" tabindex="-1" role console.log(objetoA); <span class="tok-comment">// → {a: 1, b: 3, c: 4}</span></pre> -<p><a class="p_ident" id="p-yixpDsnrLp" href="#p-yixpDsnrLp" tabindex="-1" role="presentation"></a>Los arrays, entonces, son solo un tipo de objeto especializado para almacenar secuencias de cosas. Si evalúas <code>typeof []</code>, producirá <code>"object"</code>. Puedes visualizar los arrays como pulpos largos y planos con todos sus tentáculos en una fila ordenada, etiquetados con números.</p> +<p><a class="p_ident" id="p-iZ8Ti1I/Dh" href="#p-iZ8Ti1I/Dh" tabindex="-1" role="presentation"></a>Los arrays, por tanto, no son más que un tipo de objeto especializado para almacenar secuencias de cosas. Si evalúas <code>typeof []</code>, producirá <code>"object"</code>. Puedes visualizar los arrays como pulpos largos y planos con todos sus tentáculos en una fila ordenada, etiquetados con números.</p> -<p><a class="p_ident" id="p-zY69/t647Q" href="#p-zY69/t647Q" tabindex="-1" role="presentation"></a>Jacques representará el diario que lleva como un array de objetos:</p> +<p><a class="p_ident" id="p-pkv3WUTYe5" href="#p-pkv3WUTYe5" tabindex="-1" role="presentation"></a>Jacques va a representar el diario que lleva como un array de objetos:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-uk7UhBlzB/" href="#c-uk7UhBlzB/" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">diario</span> = [ {<span class="tok-definition">eventos</span>: [<span class="tok-string">"trabajo"</span>, <span class="tok-string">"tocó árbol"</span>, <span class="tok-string">"pizza"</span>, @@ -182,32 +186,32 @@ <h2><a class="h_ident" id="h-WSNvRpk0Lx" href="#h-WSNvRpk0Lx" tabindex="-1" role <h2><a class="h_ident" id="h-/KYBvi2eEd" href="#h-/KYBvi2eEd" tabindex="-1" role="presentation"></a>Mutabilidad</h2> -<p><a class="p_ident" id="p-Geqnk1YpW9" href="#p-Geqnk1YpW9" tabindex="-1" role="presentation"></a>Pronto llegaremos a la programación real, pero primero, hay una pieza más de teoría para entender.</p> +<p><a class="p_ident" id="p-F8Ov2OLVET" href="#p-F8Ov2OLVET" tabindex="-1" role="presentation"></a>Pronto llegaremos a la programación real, pero primero, hay una cosa más de la teoría que hay que saber.</p> -<p><a class="p_ident" id="p-oZnjXQEJ5O" href="#p-oZnjXQEJ5O" tabindex="-1" role="presentation"></a>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 <em>inmutables</em>—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 <code>"gato"</code>, no es posible que otro código cambie un carácter en tu cadena para que diga <code>"rata"</code>.</p> +<p><a class="p_ident" id="p-LvCZ1+vDOD" href="#p-LvCZ1+vDOD" tabindex="-1" role="presentation"></a>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 <em>inmutables</em> —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 <code>"gato"</code>, no es posible que otro código cambie un carácter en tu cadena para que diga <code>"rata"</code>.</p> -<p><a class="p_ident" id="p-qBpnB+cTrk" href="#p-qBpnB+cTrk" tabindex="-1" role="presentation"></a>Los objetos funcionan de manera diferente. <em>Puedes</em> cambiar sus propiedades, lo que hace que un valor de objeto tenga un contenido diferente en momentos diferentes.</p> +<p><a class="p_ident" id="p-7QlDYWn3D0" href="#p-7QlDYWn3D0" tabindex="-1" role="presentation"></a>Los objetos funcionan de manera distinta. <em>Puedes</em> cambiar sus propiedades, lo que hace que un valor de objeto vaya cambiando su contenido con el tiempo.</p> -<p><a class="p_ident" id="p-ulxBQ5Ge7V" href="#p-ulxBQ5Ge7V" tabindex="-1" role="presentation"></a>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:</p> +<p><a class="p_ident" id="p-ulxBQ5Ge7V" href="#p-ulxBQ5Ge7V" tabindex="-1" role="presentation"></a>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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-I1Dv6D46/p" href="#c-I1Dv6D46/p" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">object1</span> = {<span class="tok-definition">value</span>: <span class="tok-number">10</span>}; -<span class="tok-keyword">let</span> <span class="tok-definition">object2</span> = object1; -<span class="tok-keyword">let</span> <span class="tok-definition">object3</span> = {<span class="tok-definition">value</span>: <span class="tok-number">10</span>}; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-HNiJbrxWf1" href="#c-HNiJbrxWf1" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">objeto1</span> = {<span class="tok-definition">valor</span>: <span class="tok-number">10</span>}; +<span class="tok-keyword">let</span> <span class="tok-definition">objeto2</span> = objeto1; +<span class="tok-keyword">let</span> <span class="tok-definition">objeto3</span> = {<span class="tok-definition">valor</span>: <span class="tok-number">10</span>}; -console.log(object1 == object2); +console.log(objeto1 == objeto2); <span class="tok-comment">// → true</span> -console.log(object1 == object3); +console.log(objeto1 == objeto3); <span class="tok-comment">// → false</span> -object1.value = <span class="tok-number">15</span>; -console.log(object2.value); +objeto1.valor = <span class="tok-number">15</span>; +console.log(objeto2.valor); <span class="tok-comment">// → 15</span> -console.log(object3.value); +console.log(objeto3.valor); <span class="tok-comment">// → 10</span></pre> -<p><a class="p_ident" id="p-3BhnYICwfS" href="#p-3BhnYICwfS" tabindex="-1" role="presentation"></a>Las asignaciones <code>object1</code> y <code>object2</code> contienen la <em>misma</em> referencia al objeto, por lo que al cambiar <code>object1</code> también se cambia el valor de <code>object2</code>. Se dice que tienen la misma <em>identidad</em>. La asignación <code>object3</code> apunta a un objeto diferente, que inicialmente contiene las mismas propiedades que <code>object1</code> pero vive una vida separada.</p> +<p><a class="p_ident" id="p-jaVBjxdFME" href="#p-jaVBjxdFME" tabindex="-1" role="presentation"></a>Las asignaciones <code>object1</code> y <code>object2</code> referencian al <em>mismo</em> objeto, por lo que al cambiar <code>object1</code> también se cambia el valor de <code>object2</code>. Se dice que tienen la misma <em>identidad</em>. La asignación <code>object3</code> apunta a un objeto diferente, que inicialmente contiene las mismas propiedades que <code>object1</code> pero tiene su vida por separado.</p> -<p><a class="p_ident" id="p-ArMAPB/ERK" href="#p-ArMAPB/ERK" tabindex="-1" role="presentation"></a>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 <code>let</code> 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 <code>const</code> a un objeto en sí no puede cambiarse y seguirá apuntando al mismo objeto, los <em>contenidos</em> de ese objeto pueden cambiar.</p> +<p><a class="p_ident" id="p-ehV7iyXdZo" href="#p-ehV7iyXdZo" tabindex="-1" role="presentation"></a>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 <code>let</code> 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 <code>const</code> en sí no puede cambiarse y seguirá apuntando siempre al mismo objeto, los <em>contenidos</em> de ese objeto sí que pueden cambiar.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5fiEcoTq3V" href="#c-5fiEcoTq3V" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">score</span> = {<span class="tok-definition">visitors</span>: <span class="tok-number">0</span>, <span class="tok-definition">home</span>: <span class="tok-number">0</span>}; <span class="tok-comment">// Esto está bien</span> @@ -215,133 +219,141 @@ <h2><a class="h_ident" id="h-/KYBvi2eEd" href="#h-/KYBvi2eEd" tabindex="-1" role <span class="tok-comment">// Esto no está permitido</span> score = {<span class="tok-definition">visitors</span>: <span class="tok-number">1</span>, <span class="tok-definition">home</span>: <span class="tok-number">1</span>};</pre> -<p><a class="p_ident" id="p-PlnbB6uWyK" href="#p-PlnbB6uWyK" tabindex="-1" role="presentation"></a>Cuando se comparan objetos con el operador <code>==</code> de JavaScript, se compara por identidad: producirá <code>true</code> solo si ambos objetos son exactamente el mismo valor. Comparar objetos diferentes devolverá <code>false</code>, 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 <a href="04_data.html#exercise_deep_compare">ejercicios</a> al final de este capítulo).</p> +<p><a class="p_ident" id="p-PlnbB6uWyK" href="#p-PlnbB6uWyK" tabindex="-1" role="presentation"></a>Cuando se comparan objetos con el operador <code>==</code> de JavaScript, se compara por identidad: obtendremos <code>true</code> solo si ambos objetos son exactamente el mismo valor. Comparar objetos diferentes devolverá <code>false</code>, 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 <a href="04_data.html#exercise_deep_compare">ejercicios</a> al final de este capítulo).</p> <h2><a class="h_ident" id="h-x8cT2wC35n" href="#h-x8cT2wC35n" tabindex="-1" role="presentation"></a>El diario del licántropo</h2> <p><a class="p_ident" id="p-B8MQWFaGy3" href="#p-B8MQWFaGy3" tabindex="-1" role="presentation"></a>Jacques inicia su intérprete de JavaScript y configura el entorno que necesita para mantener su diario:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-CI+dtzXW/x" href="#c-CI+dtzXW/x" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">journal</span> = []; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-c4yFR55AX6" href="#c-c4yFR55AX6" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">diario</span> = []; -<span class="tok-keyword">function</span> <span class="tok-definition">addEntry</span>(<span class="tok-definition">events</span>, <span class="tok-definition">squirrel</span>) { - journal.push({<span class="tok-definition">events</span>, <span class="tok-definition">squirrel</span>}); +<span class="tok-keyword">function</span> <span class="tok-definition">añadirEntrada</span>(<span class="tok-definition">eventos</span>, <span class="tok-definition">ardilla</span>) { + diario.push({<span class="tok-definition">eventos</span>, <span class="tok-definition">ardilla</span>}); }</pre> -<p><a class="p_ident" id="p-4cIfCsPkJW" href="#p-4cIfCsPkJW" tabindex="-1" role="presentation"></a>Observa que el objeto agregado al diario luce un poco extraño. En lugar de declarar propiedades como <code>events: events</code>, simplemente se da un nombre de propiedad: <code>events</code>. 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.</p> +<p><a class="p_ident" id="p-kjP20DzW2W" href="#p-kjP20DzW2W" tabindex="-1" role="presentation"></a>Fíjate en que el objeto agregado al diario tiene una pinta un poco rara. En vez de declarar propiedades como <code>eventos: eventos</code>, simplemente se da un nombre de propiedad: <code>eventos</code>. 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.</p> + +<p><a class="p_ident" id="p-yaT5wSI8Bw" href="#p-yaT5wSI8Bw" tabindex="-1" role="presentation"></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:</p> + +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-dQ6nhXIPSx" href="#c-dQ6nhXIPSx" tabindex="-1" role="presentation"></a>añadirEntrada([<span class="tok-string">"trabajo"</span>, <span class="tok-string">"tocó árbol"</span>, <span class="tok-string">"pizza"</span>, + <span class="tok-string">"correr"</span>, <span class="tok-string">"televisión"</span>], false); +añadirEntrada([<span class="tok-string">"trabajo"</span>, <span class="tok-string">"helado"</span>, <span class="tok-string">"coliflor"</span>, <span class="tok-string">"lasaña"</span>, + <span class="tok-string">"tocó árbol"</span>, <span class="tok-string">"se cepilló los dientes"</span>], false); +añadirEntrada([<span class="tok-string">"fin de semana"</span>, <span class="tok-string">"ciclismo"</span>, <span class="tok-string">"descanso"</span>, <span class="tok-string">"cacahuetes"</span>, + <span class="tok-string">"cerveza"</span>], true);</pre> -<p><a class="p_ident" id="p-yaT5wSI8Bw" href="#p-yaT5wSI8Bw" tabindex="-1" role="presentation"></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:</p> +<p><a class="p_ident" id="p-hgWPzcGGmo" href="#p-hgWPzcGGmo" tabindex="-1" role="presentation"></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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-S+F+R2oIfR" href="#c-S+F+R2oIfR" tabindex="-1" role="presentation"></a>addEntry([<span class="tok-string">"work"</span>, <span class="tok-string">"touched tree"</span>, <span class="tok-string">"pizza"</span>, <span class="tok-string">"running"</span>, - <span class="tok-string">"television"</span>], false); -addEntry([<span class="tok-string">"work"</span>, <span class="tok-string">"ice cream"</span>, <span class="tok-string">"cauliflower"</span>, <span class="tok-string">"lasagna"</span>, - <span class="tok-string">"touched tree"</span>, <span class="tok-string">"brushed teeth"</span>], false); -addEntry([<span class="tok-string">"weekend"</span>, <span class="tok-string">"cycling"</span>, <span class="tok-string">"break"</span>, <span class="tok-string">"peanuts"</span>, - <span class="tok-string">"beer"</span>], true);</pre> +<p><a class="p_ident" id="p-M1p8TuDLQo" href="#p-M1p8TuDLQo" tabindex="-1" role="presentation"></a>La <em>correlación</em> 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 <em>mediciones</em>. 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.</p> -<p><a class="p_ident" id="p-Hx333U8ZNd" href="#p-Hx333U8ZNd" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-F2XylLr+TX" href="#p-F2XylLr+TX" tabindex="-1" role="presentation"></a>Para calcular la correlación entre dos variables booleanas, podemos utilizar el <em>coeficiente phi</em> (<em>ϕ</em>). 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.</p> -<p><a class="p_ident" id="p-M1p8TuDLQo" href="#p-M1p8TuDLQo" tabindex="-1" role="presentation"></a>La <em>correlación</em> 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 <em>mediciones</em>, 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.</p> +<div class="translator-note"><p><strong>N. del T.:</strong> Aquí estamos usando la palabra <strong>función</strong> 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 <strong>fórmula</strong>, o un cálculo matemático.</p> +</div> -<p><a class="p_ident" id="p-F2XylLr+TX" href="#p-F2XylLr+TX" tabindex="-1" role="presentation"></a>Para calcular la medida de correlación entre dos variables booleanas, podemos utilizar el <em>coeficiente phi</em> (<em>ϕ</em>). 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.</p> +<p><a class="p_ident" id="p-Gs71HDyLpD" href="#p-Gs71HDyLpD" tabindex="-1" role="presentation"></a>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.</p><figure><img src="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."></figure> -<p><a class="p_ident" id="p-kLIOeWMysR" href="#p-kLIOeWMysR" tabindex="-1" role="presentation"></a>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.</p><figure><img src="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."></figure> +<div class="translator-note"><p><strong>N. del T.:</strong> 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.</p> +</div> -<p><a class="p_ident" id="p-1jn5homKY/" href="#p-1jn5homKY/" tabindex="-1" role="presentation"></a>Si llamamos a esa tabla <em>n</em>, podemos calcular <em>ϕ</em> utilizando la siguiente fórmula:</p><div> <table style="border-collapse: collapse; margin-left: 1em;"><tr> <td style="vertical-align: middle"><em>ϕ</em> =</td> <td style="padding-left: .5em"> <div style="border-bottom: 1px solid black; padding: 0 7px;"><em>n</em><sub>11</sub><em>n</em><sub>00</sub> − <em>n</em><sub>10</sub><em>n</em><sub>01</sub></div> <div style="padding: 0 7px;">√<span style="border-top: 1px solid black; position: relative; top: 2px;"> <span style="position: relative; top: -4px"><em>n</em><sub>1•</sub><em>n</em><sub>0•</sub><em>n</em><sub>•1</sub><em>n</em><sub>•0</sub></span> </span></div> </td> </tr></table> </div> +<p><a class="p_ident" id="p-KLQDSBBx0M" href="#p-KLQDSBBx0M" tabindex="-1" role="presentation"></a>Si llamamos <em>n</em> a esta tabla, podemos calcular el <em>ϕ</em> asociado utilizando la siguiente fórmula:</p><div> <table style="border-collapse: collapse; margin-left: 1em;"><tr> <td style="vertical-align: middle"><em>ϕ</em> =</td> <td style="padding-left: .5em"> <div style="border-bottom: 1px solid black; padding: 0 7px;"><em>n</em><sub>11</sub><em>n</em><sub>00</sub> − <em>n</em><sub>10</sub><em>n</em><sub>01</sub></div> <div style="padding: 0 7px;">√<span style="border-top: 1px solid black; position: relative; top: 2px;"> <span style="position: relative; top: -4px"><em>n</em><sub>1•</sub><em>n</em><sub>0•</sub><em>n</em><sub>•1</sub><em>n</em><sub>•0</sub></span> </span></div> </td> </tr></table> </div> -<p><a class="p_ident" id="p-8WAx00BPWT" href="#p-8WAx00BPWT" tabindex="-1" role="presentation"></a>(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).</p> +<p><a class="p_ident" id="p-TOLMjlJVHE" href="#p-TOLMjlJVHE" tabindex="-1" role="presentation"></a>—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.</p> -<p><a class="p_ident" id="p-oaZqmzwWNd" href="#p-oaZqmzwWNd" tabindex="-1" role="presentation"></a>La notación <em>n</em><sub>01</sub> 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, <em>n</em><sub>01</sub> es 9.El valor <em>n</em><sub>1•</sub> 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, <em>n</em><sub>•0</sub> se refiere a la suma de las mediciones donde la segunda variable es falsa.</p> +<p><a class="p_ident" id="p-Fe+71ceVSy" href="#p-Fe+71ceVSy" tabindex="-1" role="presentation"></a>La notación <em>n</em><sub>01</sub> 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, <em>n</em><sub>01</sub> es 9.</p> -<p><a class="p_ident" id="p-sqPMROtj1n" href="#p-sqPMROtj1n" tabindex="-1" role="presentation"></a>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 <em>ϕ</em> ≈ 0.069, que es muy pequeño. Comer pizza no parece tener influencia en las transformaciones.</p> +<p><a class="p_ident" id="p-6JDZ7pY+nU" href="#p-6JDZ7pY+nU" tabindex="-1" role="presentation"></a>El valor <em>n</em><sub>1•</sub> 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, <em>n</em><sub>•0</sub> se refiere a la suma de las mediciones donde la segunda variable es falsa.</p> + +<p><a class="p_ident" id="p-2RxEbX413+" href="#p-2RxEbX413+" tabindex="-1" role="presentation"></a>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 <em>ϕ</em> ≈ 0.069, que es un valor muy pequeño. Comer pizza no parece influir en las transformaciones.</p> <h2><a class="h_ident" id="h-BLimv2lbPx" href="#h-BLimv2lbPx" tabindex="-1" role="presentation"></a>Calculando la correlación</h2> -<p><a class="p_ident" id="p-Qw3t7bWwW/" href="#p-Qw3t7bWwW/" tabindex="-1" role="presentation"></a>Podemos representar una tabla dos por dos en JavaScript con un array de cuatro elementos (<code>[76, 9, 4, 1]</code>). También podríamos usar otras representaciones, como un array que contiene dos arrays de dos elementos cada uno (<code>[[76, 9], [4, 1]]</code>) o un objeto con nombres de propiedades como <code>"11"</code> y <code>"01"</code>, 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 <code>10</code> se refiere al caso donde Jacques se transformó en ardilla, pero el evento (digamos, “pizza”) no ocurrió. Esto sucedió cuatro veces. Y como <code>10</code> en binario es 2 en notación decimal, almacenaremos este número en el índice 2 del array.</p> +<p><a class="p_ident" id="p-Qw3t7bWwW/" href="#p-Qw3t7bWwW/" tabindex="-1" role="presentation"></a>Podemos representar una tabla dos por dos en JavaScript con un array de cuatro elementos (<code>[76, 9, 4, 1]</code>). También podríamos usar otras representaciones, como un array que contiene dos arrays de dos elementos cada uno (<code>[[76, 9], [4, 1]]</code>) o un objeto con nombres de propiedades como <code>"11"</code> y <code>"01"</code>, 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 <code>10</code> se refiere al caso donde Jacques se transforma en ardilla, pero el evento (digamos, “pizza”) no ocurre. Esto sucede cuatro veces. Como el <code>10</code> es la representación en binario del número 2, almacenaremos este número en el índice 2 del array.</p> <p id="phi_function"><a class="p_ident" id="p-zXjXHRNq0M" href="#p-zXjXHRNq0M" tabindex="-1" role="presentation"></a>Esta es la función que calcula el coeficiente <em>ϕ</em> a partir de dicho array:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-AnoNjFldkv" href="#c-AnoNjFldkv" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">phi</span>(<span class="tok-definition">table</span>) { - <span class="tok-keyword">return</span> (table[<span class="tok-number">3</span>] * table[<span class="tok-number">0</span>] - table[<span class="tok-number">2</span>] * table[<span class="tok-number">1</span>]) / - Math.sqrt((table[<span class="tok-number">2</span>] + table[<span class="tok-number">3</span>]) * - (table[<span class="tok-number">0</span>] + table[<span class="tok-number">1</span>]) * - (table[<span class="tok-number">1</span>] + table[<span class="tok-number">3</span>]) * - (table[<span class="tok-number">0</span>] + table[<span class="tok-number">2</span>])); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-edYxX6jJqv" href="#c-edYxX6jJqv" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">phi</span>(<span class="tok-definition">tabla</span>) { + <span class="tok-keyword">return</span> (tabla[<span class="tok-number">3</span>] * tabla[<span class="tok-number">0</span>] - tabla[<span class="tok-number">2</span>] * tabla[<span class="tok-number">1</span>]) / + Math.sqrt((tabla[<span class="tok-number">2</span>] + tabla[<span class="tok-number">3</span>]) * + (tabla[<span class="tok-number">0</span>] + tabla[<span class="tok-number">1</span>]) * + (tabla[<span class="tok-number">1</span>] + tabla[<span class="tok-number">3</span>]) * + (tabla[<span class="tok-number">0</span>] + tabla[<span class="tok-number">2</span>])); } console.log(phi([<span class="tok-number">76</span>, <span class="tok-number">9</span>, <span class="tok-number">4</span>, <span class="tok-number">1</span>])); <span class="tok-comment">// → 0.068599434</span></pre> -<p><a class="p_ident" id="p-zOgNCXF6Sv" href="#p-zOgNCXF6Sv" tabindex="-1" role="presentation"></a>Esta es una traducción directa de la fórmula de <em>ϕ</em> a JavaScript. <code>Math.sqrt</code> es la función de raíz cuadrada, como se provee en el objeto <code>Math</code> en un entorno estándar de JavaScript. Debemos agregar dos campos de la tabla para obtener campos como n<sub>1•</sub> porque las sumas de filas o columnas no se almacenan directamente en nuestra estructura de datos.</p> +<p><a class="p_ident" id="p-zOgNCXF6Sv" href="#p-zOgNCXF6Sv" tabindex="-1" role="presentation"></a>Esta es una traducción directa de la fórmula de <em>ϕ</em> a JavaScript. <code>Math.sqrt</code> es la función de raíz cuadrada, que viene incluida en el objeto <code>Math</code> en un entorno estándar de JavaScript. Para obtener campos de la forma n<sub>1•</sub> 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.</p> -<p><a class="p_ident" id="p-H+PUtmvv+G" href="#p-H+PUtmvv+G" tabindex="-1" role="presentation"></a>Jacques mantiene su diario por tres meses. El conjunto de datos resultante está disponible en el <a href="https://eloquentjavascript.net/code#4">sandbox de código</a> para este capítulo, donde se almacena en el vínculo <code>JOURNAL</code>, y en un archivo descargable <a href="https://eloquentjavascript.net/code/journal.js">aquí</a>.</p> +<p><a class="p_ident" id="p-CDkfSc0nWc" href="#p-CDkfSc0nWc" tabindex="-1" role="presentation"></a>Jacques escribe en su diario durante tres meses. El conjunto de datos resultante está disponible en el <a href="https://eloquentjavascript.net/code#4">sandbox de código</a> para este capítulo —donde se almacena en la variable <code>JOURNAL</code>— y en un <a href="https://eloquentjavascript.net/code/journal.js">archivo descargable</a>.</p> -<p><a class="p_ident" id="p-xl4xeJ0C9q" href="#p-xl4xeJ0C9q" tabindex="-1" role="presentation"></a>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:</p> +<p><a class="p_ident" id="p-ybhPNmSAby" href="#p-ybhPNmSAby" tabindex="-1" role="presentation"></a>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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-rfea+FwMb5" href="#c-rfea+FwMb5" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">tableFor</span>(<span class="tok-definition">event</span>, <span class="tok-definition">journal</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">table</span> = [<span class="tok-number">0</span>, <span class="tok-number">0</span>, <span class="tok-number">0</span>, <span class="tok-number">0</span>]; - <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 < journal.length; i++) { - <span class="tok-keyword">let</span> <span class="tok-definition">entry</span> = journal[i], <span class="tok-definition">index</span> = <span class="tok-number">0</span>; - <span class="tok-keyword">if</span> (entry.events.includes(event)) index += <span class="tok-number">1</span>; - <span class="tok-keyword">if</span> (entry.squirrel) index += <span class="tok-number">2</span>; - table[index] += <span class="tok-number">1</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-GEzlBMLXu1" href="#c-GEzlBMLXu1" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">tablaPara</span>(<span class="tok-definition">evento</span>, <span class="tok-definition">diario</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">tabla</span> = [<span class="tok-number">0</span>, <span class="tok-number">0</span>, <span class="tok-number">0</span>, <span class="tok-number">0</span>]; + <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 < diario.length; i++) { + <span class="tok-keyword">let</span> <span class="tok-definition">entrada</span> = diario[i], <span class="tok-definition">índice</span> = <span class="tok-number">0</span>; + <span class="tok-keyword">if</span> (entrada.eventos.includes(evento)) índice += <span class="tok-number">1</span>; + <span class="tok-keyword">if</span> (entrada.ardilla) índice += <span class="tok-number">2</span>; + tabla[índice] += <span class="tok-number">1</span>; } - <span class="tok-keyword">return</span> table; + <span class="tok-keyword">return</span> tabla; } -console.log(tableFor(<span class="tok-string">"pizza"</span>, JOURNAL)); +console.log(tablaPara(<span class="tok-string">"pizza"</span>, JOURNAL)); <span class="tok-comment">// → [76, 9, 4, 1]</span></pre> -<p><a class="p_ident" id="p-eAvvkz4cOx" href="#p-eAvvkz4cOx" tabindex="-1" role="presentation"></a>Los arrays tienen un método <code>includes</code> 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.</p> +<p><a class="p_ident" id="p-eAvvkz4cOx" href="#p-eAvvkz4cOx" tabindex="-1" role="presentation"></a>Los arrays tienen un método <code>includes</code> 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.</p> -<p><a class="p_ident" id="p-ZpHXjONXhz" href="#p-ZpHXjONXhz" tabindex="-1" role="presentation"></a>El cuerpo del bucle en <code>tableFor</code> 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.</p> +<p><a class="p_ident" id="p-ZpHXjONXhz" href="#p-ZpHXjONXhz" tabindex="-1" role="presentation"></a>El cuerpo del bucle en la función <code>tablaPara</code> 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.</p> -<p><a class="p_ident" id="p-Qng1Vzkfh5" href="#p-Qng1Vzkfh5" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-R4mz9SYgcO" href="#p-R4mz9SYgcO" tabindex="-1" role="presentation"></a>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.</p> <h2 id="for_of_loop"><a class="h_ident" id="h-eKoIunsb5g" href="#h-eKoIunsb5g" tabindex="-1" role="presentation"></a>Bucles de Array</h2> -<p><a class="p_ident" id="p-A0wc2jwdJF" href="#p-A0wc2jwdJF" tabindex="-1" role="presentation"></a>En la función <code>tableFor</code>, hay un bucle como este:</p> +<p><a class="p_ident" id="p-A0wc2jwdJF" href="#p-A0wc2jwdJF" tabindex="-1" role="presentation"></a>En la función <code>tablaPara</code>, hay un bucle como este:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-lSIvapHPx/" href="#c-lSIvapHPx/" tabindex="-1" role="presentation"></a><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 < JOURNAL.length; i++) { - <span class="tok-keyword">let</span> <span class="tok-definition">entry</span> = JOURNAL[i]; - <span class="tok-comment">// Hacer algo con entry</span> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Qg9VJbrnO/" href="#c-Qg9VJbrnO/" tabindex="-1" role="presentation"></a><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 < JOURNAL.length; i++) { + <span class="tok-keyword">let</span> <span class="tok-definition">entrada</span> = JOURNAL[i]; + <span class="tok-comment">// Hacer algo con entrada</span> }</pre> -<p><a class="p_ident" id="p-qcEYSvPpPp" href="#p-qcEYSvPpPp" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-tbw7ZIgOr6" href="#p-tbw7ZIgOr6" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-I6RpywbSC2" href="#p-I6RpywbSC2" tabindex="-1" role="presentation"></a>Hay una forma más sencilla de escribir tales bucles en JavaScript moderno:</p> +<p><a class="p_ident" id="p-DY1h4uHZbJ" href="#p-DY1h4uHZbJ" tabindex="-1" role="presentation"></a>En JavaScript moderno hay una forma más sencilla de escribir tales bucles:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-fZUSWxKfrE" href="#c-fZUSWxKfrE" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">entry</span> <span class="tok-keyword">of</span> JOURNAL) { - console.log(<span class="tok-string2">`</span>${entry.events.length}<span class="tok-string2"> eventos.`</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-zTgfAKwNbb" href="#c-zTgfAKwNbb" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">entrada</span> <span class="tok-keyword">of</span> JOURNAL) { + console.log(<span class="tok-string2">`</span>${entrada.eventos.length}<span class="tok-string2"> eventos.`</span>); }</pre> <p><a class="p_ident" id="p-IzpupnuFGC" href="#p-IzpupnuFGC" tabindex="-1" role="presentation"></a>Cuando un bucle <code>for</code> usa la palabra <code>of</code> después de la definición de su variable, recorrerá los elementos del valor dado después de <code>of</code>. Esto no solo funciona para arrays, sino también para cadenas y algunas otras estructuras de datos. Discutiremos <em>cómo</em> funciona en el <a href="06_object.html">Capítulo 6</a>.</p> <h2 id="analysis"><a class="h_ident" id="h-Wb1c9APydD" href="#h-Wb1c9APydD" tabindex="-1" role="presentation"></a>El análisis final</h2> -<p><a class="p_ident" id="p-53cmjcL1el" href="#p-53cmjcL1el" tabindex="-1" role="presentation"></a>Necesitamos calcular una correlación para cada tipo de evento que ocurre en el conjunto de datos. Para hacerlo, primero necesitamos <em>encontrar</em> cada tipo de evento.</p> +<p><a class="p_ident" id="p-FVIVeu2XOZ" href="#p-FVIVeu2XOZ" tabindex="-1" role="presentation"></a>Necesitamos calcular una correlación para cada tipo de evento que aparece en el conjunto de datos. Para hacerlo, primero necesitamos <em>encontrar</em> todos los tipos de evento.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-FwRlgr2/i2" href="#c-FwRlgr2/i2" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">journalEvents</span>(<span class="tok-definition">journal</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">events</span> = []; - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">entry</span> <span class="tok-keyword">of</span> journal) { - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">event</span> <span class="tok-keyword">of</span> entry.events) { - <span class="tok-keyword">if</span> (!events.includes(event)) { - events.push(event); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5bfIlv0k8Q" href="#c-5bfIlv0k8Q" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">eventosDiario</span>(<span class="tok-definition">diario</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">eventos</span> = []; + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">entrada</span> <span class="tok-keyword">of</span> diario) { + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">evento</span> <span class="tok-keyword">of</span> entrada.eventos) { + <span class="tok-keyword">if</span> (!eventos.includes(evento)) { + eventos.push(evento); } } } - <span class="tok-keyword">return</span> events; + <span class="tok-keyword">return</span> eventos; } -console.log(journalEvents(JOURNAL)); +console.log(eventosDiario(JOURNAL)); <span class="tok-comment">// → ["zanahoria", "ejercicio", "fin de semana", "pan", …]</span></pre> -<p><a class="p_ident" id="p-dtQt+LnThZ" href="#p-dtQt+LnThZ" tabindex="-1" role="presentation"></a>Agregando los nombres de cualquier evento que no estén en él al array <code>events</code>, la función recopila todos los tipos de eventos.</p> +<p><a class="p_ident" id="p-LZN9hz8uxT" href="#p-LZN9hz8uxT" tabindex="-1" role="presentation"></a>La función recopila todos los tipos de evento añadiendo los nombres de cualquier evento que no esté ya en el array <code>events</code>.</p> <p><a class="p_ident" id="p-s4bZe5jKvi" href="#p-s4bZe5jKvi" tabindex="-1" role="presentation"></a>Usando esa función, podemos ver todas las correlaciones:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-G4p9qs25MP" href="#c-G4p9qs25MP" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">event</span> <span class="tok-keyword">of</span> journalEvents(JOURNAL)) { - console.log(event + <span class="tok-string">":"</span>, phi(tableFor(event, JOURNAL))); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-3NKAZ0fxnn" href="#c-3NKAZ0fxnn" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">evento</span> <span class="tok-keyword">of</span> eventosDiario(JOURNAL)) { + console.log(evento + <span class="tok-string">":"</span>, phi(tablaPara(evento, JOURNAL))); } <span class="tok-comment">// → zanahoria: 0.0140970969</span> <span class="tok-comment">// → ejercicio: 0.0685994341</span> @@ -350,12 +362,12 @@ <h2 id="analysis"><a class="h_ident" id="h-Wb1c9APydD" href="#h-Wb1c9APydD" tabi <span class="tok-comment">// → pudín: -0.0648203724</span> <span class="tok-comment">// y así sucesivamente...</span></pre> -<p><a class="p_ident" id="p-faxM9AZjy9" href="#p-faxM9AZjy9" tabindex="-1" role="presentation"></a>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:</p> +<p><a class="p_ident" id="p-faxM9AZjy9" href="#p-faxM9AZjy9" tabindex="-1" role="presentation"></a>La mayoría de las correlaciones parecen estar cerca de cero. Comer zanahorias, pan o pudín aparentemente no desencadenan la <em>ardillolicantropía</em>. 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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ezWFQYT/Ls" href="#c-ezWFQYT/Ls" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">event</span> <span class="tok-keyword">of</span> journalEvents(JOURNAL)) { - <span class="tok-keyword">let</span> <span class="tok-definition">correlation</span> = phi(tableFor(event, JOURNAL)); - <span class="tok-keyword">if</span> (correlation > <span class="tok-number">0.1</span> || correlation < -<span class="tok-number">0.1</span>) { - console.log(event + <span class="tok-string">":"</span>, correlation); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-pYehX4lqf4" href="#c-pYehX4lqf4" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">evento</span> <span class="tok-keyword">of</span> eventosDiario(JOURNAL)) { + <span class="tok-keyword">let</span> <span class="tok-definition">correlación</span> = phi(tablaPara(evento, JOURNAL)); + <span class="tok-keyword">if</span> (correlación > <span class="tok-number">0.1</span> || correlación < -<span class="tok-number">0.1</span>) { + console.log(evento + <span class="tok-string">":"</span>, correlación); } } <span class="tok-comment">// → fin de semana: 0.1371988681</span> @@ -366,30 +378,30 @@ <h2 id="analysis"><a class="h_ident" id="h-Wb1c9APydD" href="#h-Wb1c9APydD" tabi <span class="tok-comment">// → lectura: 0.1106828054</span> <span class="tok-comment">// → cacahuetes: 0.5902679812</span></pre> -<p><a class="p_ident" id="p-2xjaJscfnx" href="#p-2xjaJscfnx" tabindex="-1" role="presentation"></a>¡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.</p> +<p><a class="p_ident" id="p-8gi0Oe2QNa" href="#p-8gi0Oe2QNa" tabindex="-1" role="presentation"></a>¡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.</p> <p><a class="p_ident" id="p-cgCNMNPXP9" href="#p-cgCNMNPXP9" tabindex="-1" role="presentation"></a>Interesante. Intentemos algo:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-qIdmuAxpxa" href="#c-qIdmuAxpxa" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">entry</span> <span class="tok-keyword">of</span> JOURNAL) { - <span class="tok-keyword">if</span> (entry.events.includes(<span class="tok-string">"cacahuetes"</span>) && - !entry.events.includes(<span class="tok-string">"cepillarse los dientes"</span>)) { - entry.events.push(<span class="tok-string">"dientes de cacahuate"</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-FT0Ikpmut4" href="#c-FT0Ikpmut4" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">entrada</span> <span class="tok-keyword">of</span> JOURNAL) { + <span class="tok-keyword">if</span> (entrada.eventos.includes(<span class="tok-string">"cacahuetes"</span>) && + !entrada.eventos.includes(<span class="tok-string">"cepillarse los dientes"</span>)) { + entrada.eventos.push(<span class="tok-string">"cacahuate en los dientes"</span>); } } -console.log(phi(tableFor(<span class="tok-string">"dientes de cacahuate"</span>, JOURNAL))); +console.log(phi(tablaPara(<span class="tok-string">"cacahuate en los dientes"</span>, JOURNAL))); <span class="tok-comment">// → 1</span></pre> -<p><a class="p_ident" id="p-Cw79Gtea7t" href="#p-Cw79Gtea7t" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-6C1Fw4X/AP" href="#p-6C1Fw4X/AP" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-4EG9xMCj76" href="#p-4EG9xMCj76" tabindex="-1" role="presentation"></a>Sabiendo esto, Jacques deja de comer cacahuetes por completo y descubre que sus transformaciones se detienen.</p> -<p><a class="p_ident" id="p-m8DOtvbRjS" href="#p-m8DOtvbRjS" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-m8DOtvbRjS" href="#p-m8DOtvbRjS" tabindex="-1" role="presentation"></a>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.</p> -<h2><a class="h_ident" id="h-P+h+US14Fn" href="#h-P+h+US14Fn" tabindex="-1" role="presentation"></a>Más arreología</h2> +<h2><a class="h_ident" id="h-ZjqsQxkhsM" href="#h-ZjqsQxkhsM" tabindex="-1" role="presentation"></a>Más arreglología</h2> -<p><a class="p_ident" id="p-GGlGmQORfd" href="#p-GGlGmQORfd" tabindex="-1" role="presentation"></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.</p> +<p><a class="p_ident" id="p-w5cAmB2rO/" href="#p-w5cAmB2rO/" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-JBcBPLN/56" href="#p-JBcBPLN/56" tabindex="-1" role="presentation"></a>Vimos <code>push</code> y <code>pop</code>, que agregan y eliminan elementos al final de un array, <a href="04_data.html#array_methods">anteriormente</a> en este capítulo. Los métodos correspondientes para agregar y eliminar cosas al principio de un array se llaman <code>unshift</code> y <code>shift</code>.</p> +<p><a class="p_ident" id="p-9u/3ySUYsO" href="#p-9u/3ySUYsO" tabindex="-1" role="presentation"></a><a href="04_data.html#array_methods">Anteriormente</a> en este capítulo, vimos <code>push</code> y <code>pop</code>, 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 <code>unshift</code> y <code>shift</code>.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-62X7PYoPac" href="#c-62X7PYoPac" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">listaDeTareas</span> = []; <span class="tok-keyword">function</span> <span class="tok-definition">recordar</span>(<span class="tok-definition">tarea</span>) { @@ -402,9 +414,9 @@ <h2><a class="h_ident" id="h-P+h+US14Fn" href="#h-P+h+US14Fn" tabindex="-1" role listaDeTareas.unshift(tarea); }</pre> -<p><a class="p_ident" id="p-UPPtsIQIjO" href="#p-UPPtsIQIjO" tabindex="-1" role="presentation"></a>Este programa gestiona una cola de tareas. Agregas tareas al final de la cola llamando a <code>recordar("comestibles")</code>, y cuando estás listo para hacer algo, llamas a <code>obtenerTarea()</code> para obtener (y eliminar) el primer elemento de la cola. La función <code>recordarUrgente</code> también agrega una tarea pero la agrega al principio en lugar de al final de la cola.</p> +<p><a class="p_ident" id="p-UPPtsIQIjO" href="#p-UPPtsIQIjO" tabindex="-1" role="presentation"></a>Este programa gestiona una cola de tareas. Agregas tareas al final de la cola llamando a <code>recordar("compras")</code>, y cuando estás listo para hacer algo, llamas a <code>obtenerTarea()</code> para obtener (y eliminar) el primer elemento de la cola. La función <code>recordarUrgente</code> también agrega una tarea pero la agrega al principio en lugar de al final de la cola.</p> -<p><a class="p_ident" id="p-CPj55o+sBM" href="#p-CPj55o+sBM" tabindex="-1" role="presentation"></a>Para buscar un valor específico, los arrays proporcionan un método <code>indexOf</code>. 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 <code>lastIndexOf</code>:</p> +<p><a class="p_ident" id="p-CPj55o+sBM" href="#p-CPj55o+sBM" tabindex="-1" role="presentation"></a>Para buscar un valor específico, los arrays proporcionan un método <code>indexOf</code>. 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 <code>lastIndexOf</code>:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-N+G0EtTfto" href="#c-N+G0EtTfto" tabindex="-1" role="presentation"></a>console.log([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">2</span>, <span class="tok-number">1</span>].indexOf(<span class="tok-number">2</span>)); <span class="tok-comment">// → 1</span> @@ -413,7 +425,7 @@ <h2><a class="h_ident" id="h-P+h+US14Fn" href="#h-P+h+US14Fn" tabindex="-1" role <p><a class="p_ident" id="p-mG2d/86G/u" href="#p-mG2d/86G/u" tabindex="-1" role="presentation"></a>Tanto <code>indexOf</code> como <code>lastIndexOf</code> admiten un segundo argumento opcional que indica dónde comenzar la búsqueda.</p> -<p><a class="p_ident" id="p-xrQvGpo6uZ" href="#p-xrQvGpo6uZ" tabindex="-1" role="presentation"></a>Otro método fundamental de los arrays es <code>slice</code>, 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.</p> +<p><a class="p_ident" id="p-xrQvGpo6uZ" href="#p-xrQvGpo6uZ" tabindex="-1" role="presentation"></a>Otro método fundamental de los arrays es <code>slice</code>, 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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-zCBzPnMpIk" href="#c-zCBzPnMpIk" tabindex="-1" role="presentation"></a>console.log([<span class="tok-number">0</span>, <span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">4</span>].slice(<span class="tok-number">2</span>, <span class="tok-number">4</span>)); <span class="tok-comment">// → [2, 3]</span> @@ -424,58 +436,58 @@ <h2><a class="h_ident" id="h-P+h+US14Fn" href="#h-P+h+US14Fn" tabindex="-1" role <p><a class="p_ident" id="p-pvqlqB/TlJ" href="#p-pvqlqB/TlJ" tabindex="-1" role="presentation"></a>El método <code>concat</code> se puede usar para concatenar arrays y crear un nuevo array, similar a lo que el operador <code>+</code> hace para las strings.</p> -<p><a class="p_ident" id="p-svxsa3r4jx" href="#p-svxsa3r4jx" tabindex="-1" role="presentation"></a>El siguiente ejemplo muestra tanto <code>concat</code> como <code>slice</code> 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:</p> +<p><a class="p_ident" id="p-nPcYJHEn9x" href="#p-nPcYJHEn9x" tabindex="-1" role="presentation"></a>El siguiente ejemplo muestra tanto <code>concat</code> como <code>slice</code> 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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-q+NBMNgFFy" href="#c-q+NBMNgFFy" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">remove</span>(<span class="tok-definition">array</span>, <span class="tok-definition">index</span>) { - <span class="tok-keyword">return</span> array.slice(<span class="tok-number">0</span>, index) - .concat(array.slice(index + <span class="tok-number">1</span>)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-EfekeW+wt/" href="#c-EfekeW+wt/" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">eliminar</span>(<span class="tok-definition">array</span>, <span class="tok-definition">índice</span>) { + <span class="tok-keyword">return</span> array.slice(<span class="tok-number">0</span>, índice) + .concat(array.slice(índice + <span class="tok-number">1</span>)); } -console.log(remove([<span class="tok-string">"a"</span>, <span class="tok-string">"b"</span>, <span class="tok-string">"c"</span>, <span class="tok-string">"d"</span>, <span class="tok-string">"e"</span>], <span class="tok-number">2</span>)); +console.log(eliminar([<span class="tok-string">"a"</span>, <span class="tok-string">"b"</span>, <span class="tok-string">"c"</span>, <span class="tok-string">"d"</span>, <span class="tok-string">"e"</span>], <span class="tok-number">2</span>)); <span class="tok-comment">// → ["a", "b", "d", "e"]</span></pre> -<p><a class="p_ident" id="p-oMPNwC9WR1" href="#p-oMPNwC9WR1" tabindex="-1" role="presentation"></a>Si le pasas a <code>concat</code> un argumento que no es un array, ese valor se agregará al nuevo array como si fuera un array de un solo elemento.</p> +<p><a class="p_ident" id="p-oMPNwC9WR1" href="#p-oMPNwC9WR1" tabindex="-1" role="presentation"></a>Si le pasas a <code>concat</code> un argumento que no es un array, ese valor se agregará al nuevo array creado por <code>concat</code> como si fuera un array de un solo elemento.</p> <h2><a class="h_ident" id="h-uw9pbLURQQ" href="#h-uw9pbLURQQ" tabindex="-1" role="presentation"></a>Strings y sus propiedades</h2> -<p><a class="p_ident" id="p-dtvgUAdt/p" href="#p-dtvgUAdt/p" tabindex="-1" role="presentation"></a>Podemos acceder a propiedades como <code>length</code> y <code>toUpperCase</code> en valores de tipo string. Pero si intentamos añadir una nueva propiedad, esta no se conserva.</p> +<p><a class="p_ident" id="p-dtvgUAdt/p" href="#p-dtvgUAdt/p" tabindex="-1" role="presentation"></a>Podemos acceder a propiedades como <code>length</code> y <code>toUpperCase</code> en valores de tipo cadena (string). Pero si intentamos añadir una nueva propiedad, esta no se conserva.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-xvzL9wErq7" href="#c-xvzL9wErq7" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">kim</span> = <span class="tok-string">"Kim"</span>; kim.age = <span class="tok-number">88</span>; console.log(kim.age); <span class="tok-comment">// → undefined</span></pre> -<p><a class="p_ident" id="p-02Hw95fCDy" href="#p-02Hw95fCDy" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-02Hw95fCDy" href="#p-02Hw95fCDy" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-zaqfCciyRX" href="#p-zaqfCciyRX" tabindex="-1" role="presentation"></a>Pero estos tipos tienen propiedades integradas. Cada valor string tiene varios métodos. Algunos muy útiles son <code>slice</code> e <code>indexOf</code>, que se parecen a los métodos de arrays del mismo nombre:</p> +<p><a class="p_ident" id="p-zaqfCciyRX" href="#p-zaqfCciyRX" tabindex="-1" role="presentation"></a>Pero estos tipos tienen propiedades integradas. Cada valor de tipo string tiene varios métodos. Algunos muy útiles son <code>slice</code> e <code>indexOf</code>, que se parecen a los métodos de arrays del mismo nombre:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-/Tmq1doYeG" href="#c-/Tmq1doYeG" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"coconuts"</span>.slice(<span class="tok-number">4</span>, <span class="tok-number">7</span>)); -<span class="tok-comment">// → nut</span> -console.log(<span class="tok-string">"coconut"</span>.indexOf(<span class="tok-string">"u"</span>)); -<span class="tok-comment">// → 5</span></pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-D/tC8nfGDt" href="#c-D/tC8nfGDt" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"cocos"</span>.slice(<span class="tok-number">2</span>, <span class="tok-number">4</span>)); +<span class="tok-comment">// → co</span> +console.log(<span class="tok-string">"coco"</span>.indexOf(<span class="tok-string">"o"</span>)); +<span class="tok-comment">// → 1</span></pre> <p><a class="p_ident" id="p-p04HTT0PIT" href="#p-p04HTT0PIT" tabindex="-1" role="presentation"></a>Una diferencia es que el <code>indexOf</code> 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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-xM5/GBnZVG" href="#c-xM5/GBnZVG" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"one two three"</span>.indexOf(<span class="tok-string">"ee"</span>)); -<span class="tok-comment">// → 11</span></pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-tdRZT8+CA7" href="#c-tdRZT8+CA7" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"me gusta leer"</span>.indexOf(<span class="tok-string">"ee"</span>)); +<span class="tok-comment">// → 10</span></pre> -<p><a class="p_ident" id="p-/V2mwaLDMd" href="#p-/V2mwaLDMd" tabindex="-1" role="presentation"></a>El método <code>trim</code> elimina los espacios en blanco (espacios, saltos de línea, tabulaciones y caracteres similares) del principio y final de una cadena:</p> +<p><a class="p_ident" id="p-/V2mwaLDMd" href="#p-/V2mwaLDMd" tabindex="-1" role="presentation"></a>El método <code>trim</code> elimina los espacios en blanco (espacios, saltos de línea, tabulaciones y caracteres similares) del principio y el final de una cadena:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-0AfLRWyjq0" href="#c-0AfLRWyjq0" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">" okay </span><span class="tok-string2">\n</span><span class="tok-string"> "</span>.trim()); -<span class="tok-comment">// → okay</span></pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-TBaXQiCAzd" href="#c-TBaXQiCAzd" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">" de acuerdo </span><span class="tok-string2">\n</span><span class="tok-string"> "</span>.trim()); +<span class="tok-comment">// → de acuerdo</span></pre> -<p id="padStart"><a class="p_ident" id="p-pdg7d9WnCk" href="#p-pdg7d9WnCk" tabindex="-1" role="presentation"></a>La función <code>zeroPad</code> del <a href="03_functions.html">capítulo anterior</a> también existe como un método. Se llama <code>padStart</code> y recibe la longitud deseada y el carácter de relleno como argumentos:</p> +<p id="padStart"><a class="p_ident" id="p-pdg7d9WnCk" href="#p-pdg7d9WnCk" tabindex="-1" role="presentation"></a>La función <code>rellenarConCeros</code> del <a href="03_functions.html">capítulo anterior</a> también existe como un método. Se llama <code>padStart</code> y recibe la longitud deseada y el carácter de relleno como argumentos:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-SJbD9MiYu+" href="#c-SJbD9MiYu+" tabindex="-1" role="presentation"></a>console.log(String(<span class="tok-number">6</span>).padStart(<span class="tok-number">3</span>, <span class="tok-string">"0"</span>)); <span class="tok-comment">// → 006</span></pre> <p id="split"><a class="p_ident" id="p-HTw6HQIwO3" href="#p-HTw6HQIwO3" tabindex="-1" role="presentation"></a>Puedes dividir una cadena en cada ocurrencia de otra cadena con <code>split</code> y unirla nuevamente con <code>join</code>:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-/rV2Ed5e8V" href="#c-/rV2Ed5e8V" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">sentence</span> = <span class="tok-string">"Secretarybirds specialize in stomping"</span>; -<span class="tok-keyword">let</span> <span class="tok-definition">words</span> = sentence.split(<span class="tok-string">" "</span>); -console.log(words); -<span class="tok-comment">// → ["Secretarybirds", "specialize", "in", "stomping"]</span> -console.log(words.join(<span class="tok-string">". "</span>)); -<span class="tok-comment">// → Secretarybirds. specialize. in. stomping</span></pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-JLpLOJuTrR" href="#c-JLpLOJuTrR" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">frase</span> = <span class="tok-string">"El pájaro secretario se especializa en pisotear"</span>; +<span class="tok-keyword">let</span> <span class="tok-definition">palabras</span> = frase.split(<span class="tok-string">" "</span>); +console.log(palabras); +<span class="tok-comment">// → ["El", "pájaro", "secretario", "se", "especializa", "en", "pisotear"]</span> +console.log(palabras.join(<span class="tok-string">". "</span>)); +<span class="tok-comment">// → El. pájaro. secretario. se. especializa. en. pisotear</span></pre> <p><a class="p_ident" id="p-lENUzJ+eVR" href="#p-lENUzJ+eVR" tabindex="-1" role="presentation"></a>Una cadena puede repetirse con el método <code>repeat</code>, que crea una nueva cadena que contiene múltiples copias de la cadena original, pegadas juntas:</p> @@ -484,42 +496,42 @@ <h2><a class="h_ident" id="h-uw9pbLURQQ" href="#h-uw9pbLURQQ" tabindex="-1" role <p><a class="p_ident" id="p-Mzwnz0fPZI" href="#p-Mzwnz0fPZI" tabindex="-1" role="presentation"></a>Ya hemos visto la propiedad <code>length</code> 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 <a href="05_higher_order.html#code_units">Capítulo 5</a>).</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Aeop9AuKAb" href="#c-Aeop9AuKAb" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">string</span> = <span class="tok-string">"abc"</span>; -console.log(string.length); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-czwPzto5Rv" href="#c-czwPzto5Rv" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">cadena</span> = <span class="tok-string">"abc"</span>; +console.log(cadena.length); <span class="tok-comment">// → 3</span> -console.log(string[<span class="tok-number">1</span>]); +console.log(cadena[<span class="tok-number">1</span>]); <span class="tok-comment">// → b</span></pre> -<h2 id="rest_parameters"><a class="h_ident" id="h-sSxXIPU8U/" href="#h-sSxXIPU8U/" tabindex="-1" role="presentation"></a>Parámetros restantes</h2> +<h2 id="rest_parameters"><a class="h_ident" id="h-16ojYY/Smq" href="#h-16ojYY/Smq" tabindex="-1" role="presentation"></a>Parámetros Rest</h2> -<p><a class="p_ident" id="p-wuSlpIk8Cl" href="#p-wuSlpIk8Cl" tabindex="-1" role="presentation"></a>Puede ser útil para una función aceptar cualquier cantidad de argumento). Por ejemplo, <code>Math.max</code> calcula el máximo de <em>todos</em> 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:</p> +<p><a class="p_ident" id="p-wuSlpIk8Cl" href="#p-wuSlpIk8Cl" tabindex="-1" role="presentation"></a>Puede ser útil para una función aceptar una cantidad cualquiera de argumentos. Por ejemplo, <code>Math.max</code> calcula el máximo de entre <em>todos</em> 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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-UXPbyGFVIB" href="#c-UXPbyGFVIB" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">max</span>(...<span class="tok-definition">numbers</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">result</span> = -Infinity; - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">number</span> <span class="tok-keyword">of</span> numbers) { - <span class="tok-keyword">if</span> (number > result) result = number; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-v0+nA8rqER" href="#c-v0+nA8rqER" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">max</span>(...<span class="tok-definition">números</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">resultado</span> = -Infinity; + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">número</span> <span class="tok-keyword">of</span> números) { + <span class="tok-keyword">if</span> (número > resultado) resultado = número; } - <span class="tok-keyword">return</span> result; + <span class="tok-keyword">return</span> resultado; } console.log(max(<span class="tok-number">4</span>, <span class="tok-number">1</span>, <span class="tok-number">9</span>, -<span class="tok-number">2</span>)); <span class="tok-comment">// → 9</span></pre> -<p><a class="p_ident" id="p-MK77lhkEMQ" href="#p-MK77lhkEMQ" tabindex="-1" role="presentation"></a>Cuando se llama a una función así, el <em>parámetro restante</em> 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 <code>max</code>, es el único parámetro, contendrá todos los argumentos.</p> +<p><a class="p_ident" id="p-MK77lhkEMQ" href="#p-MK77lhkEMQ" tabindex="-1" role="presentation"></a>Cuando se llama a una función así, el <em>parámetro rest</em> 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 <code>max</code>, es el único parámetro, contendrá todos los argumentos.</p> <p><a class="p_ident" id="p-q+qiYRmovJ" href="#p-q+qiYRmovJ" tabindex="-1" role="presentation"></a>Puedes usar una notación similar de tres puntos para <em>llamar</em> a una función con un array de argumentos:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-h9hmTe3vix" href="#c-h9hmTe3vix" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">numbers</span> = [<span class="tok-number">5</span>, <span class="tok-number">1</span>, <span class="tok-number">7</span>]; -console.log(max(...numbers)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ywIA/2X7yQ" href="#c-ywIA/2X7yQ" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">numbers</span> = [<span class="tok-number">5</span>, <span class="tok-number">1</span>, <span class="tok-number">7</span>]; +console.log(max(...números)); <span class="tok-comment">// → 7</span></pre> -<p><a class="p_ident" id="p-xw1vqMrbrj" href="#p-xw1vqMrbrj" tabindex="-1" role="presentation"></a>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 <code>max(9, .<wbr>.<wbr>.<wbr>numbers, 2)</code>.</p> +<p><a class="p_ident" id="p-xw1vqMrbrj" href="#p-xw1vqMrbrj" tabindex="-1" role="presentation"></a>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 <code>max(9, .<wbr>.<wbr>.<wbr>números, 2)</code>.</p> <p><a class="p_ident" id="p-y7lBlP3psf" href="#p-y7lBlP3psf" tabindex="-1" role="presentation"></a>La notación de array entre corchetes cuadrados permite al operador de triple punto expandir otro array en el nuevo array:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NxsGC0fWGP" href="#c-NxsGC0fWGP" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">words</span> = [<span class="tok-string">"never"</span>, <span class="tok-string">"fully"</span>]; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-805I5zfNg4" href="#c-805I5zfNg4" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">palabras</span> = [<span class="tok-string">"lo"</span>, <span class="tok-string">"entenderé"</span>]; -console.log([<span class="tok-string">"will"</span>, ...words, <span class="tok-string">"understand"</span>]); -<span class="tok-comment">// → ["will", "never", "fully", "understand"]</span></pre> +console.log([<span class="tok-string">"nunca"</span>, ...palabras, <span class="tok-string">"del todo"</span>]); +<span class="tok-comment">// → ["nunca", "lo", "entenderé", "del todo"]</span></pre> <p><a class="p_ident" id="p-Rg0TFI20SL" href="#p-Rg0TFI20SL" tabindex="-1" role="presentation"></a>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:</p> @@ -529,15 +541,15 @@ <h2 id="rest_parameters"><a class="h_ident" id="h-sSxXIPU8U/" href="#h-sSxXIPU8U <h2><a class="h_ident" id="h-WWC//uRjoK" href="#h-WWC//uRjoK" tabindex="-1" role="presentation"></a>El objeto Math</h2> -<p><a class="p_ident" id="p-cHCP2HN+mo" href="#p-cHCP2HN+mo" tabindex="-1" role="presentation"></a>Como hemos visto, <code>Math</code> es una bolsa de funciones de utilidad relacionadas con números, tales como <code>Math.max</code> (máximo), <code>Math.min</code> (mínimo) y <code>Math.sqrt</code> (raíz cuadrada).</p> +<p><a class="p_ident" id="p-VotFERJ7gZ" href="#p-VotFERJ7gZ" tabindex="-1" role="presentation"></a>Como hemos visto, <code>Math</code> es un saco de funciones tales como <code>Math.max</code> (máximo), <code>Math.min</code> (mínimo) y <code>Math.sqrt</code> (raíz cuadrada), para hacer cosas con números.</p> -<p id="contaminación de espacio de nombres"><a class="p_ident" id="p-AEaIwoBlup" href="#p-AEaIwoBlup" tabindex="-1" role="presentation"></a>El objeto <code>Math</code> se utiliza como un contenedor para agrupar un conjunto de funcionalidades relacionadas. Solo hay un objeto <code>Math</code> y casi nunca es útil como un valor. Más bien, proporciona un <em>espacio de nombres</em> para que todas estas funciones y valores no tengan que ser enlaces globales.</p> +<p id="contaminación de espacio de nombres"><a class="p_ident" id="p-UlbYhsjMFi" href="#p-UlbYhsjMFi" tabindex="-1" role="presentation"></a>El objeto <code>Math</code> se utiliza como un contenedor para agrupar un conjunto de funcionalidades relacionadas. Solo hay un objeto <code>Math</code> y casi nunca es útil como un valor. Más bien, proporciona un <em>espacio de nombres</em> para que todas estas funciones y valores no tengan que ser variables globales.</p> -<p><a class="p_ident" id="p-7DncZUiEME" href="#p-7DncZUiEME" tabindex="-1" role="presentation"></a>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 <code>max</code> en uno de tus programas. Dado que la función <code>max</code> integrada de JavaScript está protegida de forma segura dentro del objeto <code>Math</code>, no tienes que preocuparte por sobrescribirla.</p> +<p><a class="p_ident" id="p-7DncZUiEME" href="#p-7DncZUiEME" tabindex="-1" role="presentation"></a>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 <code>max</code> en uno de tus programas. Dado que la función <code>max</code> integrada de JavaScript está protegida de forma segura dentro del objeto <code>Math</code>, no tienes que preocuparte por sobrescribirla.</p> -<p><a class="p_ident" id="p-GNNxcAnRPt" href="#p-GNNxcAnRPt" tabindex="-1" role="presentation"></a>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 <code>let</code> o <code>const</code>, pero —perversamente— no para enlaces estándar ni para enlaces declarados con <code>var</code> o <code>function</code>.</p> +<p><a class="p_ident" id="p-GNNxcAnRPt" href="#p-GNNxcAnRPt" tabindex="-1" role="presentation"></a>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 <code>let</code> o <code>const</code>, pero —perversamente— no para asociaciones estándar ni para enlaces declarados con <code>var</code> o <code>function</code>.</p> -<p><a class="p_ident" id="p-X/0xnFfyyr" href="#p-X/0xnFfyyr" tabindex="-1" role="presentation"></a>Volviendo al objeto <code>Math</code>. Si necesitas hacer trigonometría, <code>Math</code> puede ayudarte. Contiene <code>cos</code> (coseno), <code>sin</code> (seno) y <code>tan</code> (tangente), así como sus funciones inversas, <code>acos</code>, <code>asin</code> y <code>atan</code>, 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 <code>Math.PI</code>. Existe una antigua tradición de programación que consiste en escribir los nombres de valores constantes en mayúsculas:</p> +<p><a class="p_ident" id="p-X/0xnFfyyr" href="#p-X/0xnFfyyr" tabindex="-1" role="presentation"></a>Volviendo al objeto <code>Math</code>. Si necesitas hacer trigonometría, <code>Math</code> puede ayudarte. Contiene las funciones <code>cos</code> (coseno), <code>sin</code> (seno) y <code>tan</code> (tangente), así como sus funciones inversas, <code>acos</code>, <code>asin</code> y <code>atan</code>, 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 <code>Math.PI</code>. Existe una antigua tradición de programación que consiste en escribir los nombres de valores constantes en mayúsculas:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-QhZJbZ/sh6" href="#c-QhZJbZ/sh6" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">puntoAleatorioEnCirculo</span>(<span class="tok-definition">radio</span>) { <span class="tok-keyword">let</span> <span class="tok-definition">ángulo</span> = Math.random() * <span class="tok-number">2</span> * Math.PI; @@ -549,7 +561,7 @@ <h2><a class="h_ident" id="h-WWC//uRjoK" href="#h-WWC//uRjoK" tabindex="-1" role <p><a class="p_ident" id="p-/lgOH6W7TM" href="#p-/lgOH6W7TM" tabindex="-1" role="presentation"></a>Si no estás familiarizado con senos y cosenos, no te preocupes. Los explicaré cuando se utilicen en este libro, en el <a href="14_dom.html#sin_cos">Capítulo 14</a>.</p> -<p><a class="p_ident" id="p-XmwPEdF3rx" href="#p-XmwPEdF3rx" tabindex="-1" role="presentation"></a>El ejemplo anterior utilizó <code>Math.random</code>. Esta es una función que devuelve un nuevo número pseudoaleatorio entre cero (inclusive) y uno (exclusivo) cada vez que la llamas:</p> +<p><a class="p_ident" id="p-+xalPpsRwX" href="#p-+xalPpsRwX" tabindex="-1" role="presentation"></a>En el ejemplo anterior se ha usado <code>Math.random</code>. Esta es una función que devuelve un nuevo número pseudoaleatorio entre cero (inclusive) y uno (exclusivo) cada vez que la llamas:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-+gqW4B1qk1" href="#c-+gqW4B1qk1" tabindex="-1" role="presentation"></a>console.log(Math.random()); <span class="tok-comment">// → 0.36993729369714856</span> @@ -558,7 +570,7 @@ <h2><a class="h_ident" id="h-WWC//uRjoK" href="#h-WWC//uRjoK" tabindex="-1" role console.log(Math.random()); <span class="tok-comment">// → 0.40180766698904335</span></pre> -<p><a class="p_ident" id="p-zYcoRhgvcg" href="#p-zYcoRhgvcg" tabindex="-1" role="presentation"></a>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 <em>aparentan</em> aleatorios.</p> +<p><a class="p_ident" id="p-31/l+ZobkW" href="#p-31/l+ZobkW" tabindex="-1" role="presentation"></a>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 <em>parecen</em> aleatorios.</p> <p><a class="p_ident" id="p-j33bnKhd+s" href="#p-j33bnKhd+s" tabindex="-1" role="presentation"></a>Si queremos un número entero aleatorio en lugar de uno fraccionario, podemos usar <code>Math.floor</code> (que redondea hacia abajo al número entero más cercano) en el resultado de <code>Math.random</code>:</p> @@ -567,21 +579,21 @@ <h2><a class="h_ident" id="h-WWC//uRjoK" href="#h-WWC//uRjoK" tabindex="-1" role <p><a class="p_ident" id="p-2yz89z6FHF" href="#p-2yz89z6FHF" tabindex="-1" role="presentation"></a>Al multiplicar el número aleatorio por 10, obtenemos un número mayor o igual a 0 y menor que 10. Dado que <code>Math.floor</code> redondea hacia abajo, esta expresión producirá, con igual probabilidad, cualquier número del 0 al 9.</p> -<p><a class="p_ident" id="p-qVdWzJGtTd" href="#p-qVdWzJGtTd" tabindex="-1" role="presentation"></a>También existen las funciones <code>Math.ceil</code> (para “techo”, que redondea hacia arriba al número entero más cercano), <code>Math.round</code> (al número entero más cercano) y <code>Math.abs</code>, que toma el valor absoluto de un número, es decir, niega los valores negativos pero deja los positivos tal como están.</p> +<p><a class="p_ident" id="p-qVdWzJGtTd" href="#p-qVdWzJGtTd" tabindex="-1" role="presentation"></a>También existen las funciones <code>Math.ceil</code> (de “techo”, que redondea hacia arriba al número entero más cercano), <code>Math.round</code> (al número entero más cercano) y <code>Math.abs</code>, que proporciona el valor absoluto de un número, es decir, niega los valores negativos pero deja los positivos tal y como están.</p> <h2><a class="h_ident" id="h-0ZVrqEfTNs" href="#h-0ZVrqEfTNs" tabindex="-1" role="presentation"></a>Desestructuración</h2> <p><a class="p_ident" id="p-DAOeNDJ6LP" href="#p-DAOeNDJ6LP" tabindex="-1" role="presentation"></a>Volviendo por un momento a la función <code>phi</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-8p90ivE8ZI" href="#c-8p90ivE8ZI" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">phi</span>(<span class="tok-definition">table</span>) { - <span class="tok-keyword">return</span> (table[<span class="tok-number">3</span>] * table[<span class="tok-number">0</span>] - table[<span class="tok-number">2</span>] * table[<span class="tok-number">1</span>]) / - Math.sqrt((table[<span class="tok-number">2</span>] + table[<span class="tok-number">3</span>]) * - (table[<span class="tok-number">0</span>] + table[<span class="tok-number">1</span>]) * - (table[<span class="tok-number">1</span>] + table[<span class="tok-number">3</span>]) * - (table[<span class="tok-number">0</span>] + table[<span class="tok-number">2</span>])); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-qwJPk8ymGo" href="#c-qwJPk8ymGo" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">phi</span>(<span class="tok-definition">tabla</span>) { + <span class="tok-keyword">return</span> (tabla[<span class="tok-number">3</span>] * tabla[<span class="tok-number">0</span>] - tabla[<span class="tok-number">2</span>] * tabla[<span class="tok-number">1</span>]) / + Math.sqrt((tabla[<span class="tok-number">2</span>] + tabla[<span class="tok-number">3</span>]) * + (tabla[<span class="tok-number">0</span>] + tabla[<span class="tok-number">1</span>]) * + (tabla[<span class="tok-number">1</span>] + tabla[<span class="tok-number">3</span>]) * + (tabla[<span class="tok-number">0</span>] + tabla[<span class="tok-number">2</span>])); }</pre> -<p><a class="p_ident" id="p-MvhILwj7IW" href="#p-MvhILwj7IW" tabindex="-1" role="presentation"></a>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 <em>elementos</em> del array, es decir, <code>let n00 = table[0]</code> y así sucesivamente. Afortunadamente, hay una forma concisa de hacer esto en JavaScript:</p> +<p><a class="p_ident" id="p-MvhILwj7IW" href="#p-MvhILwj7IW" tabindex="-1" role="presentation"></a>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 <em>elementos</em> del array, es decir, <code>let n00 = table[0]</code> y así sucesivamente. Por suerte, hay una forma concisa de hacer esto en JavaScript:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-z2cboTL/zX" href="#c-z2cboTL/zX" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">phi</span>([<span class="tok-definition">n00</span>, <span class="tok-definition">n01</span>, <span class="tok-definition">n10</span>, <span class="tok-definition">n11</span>]) { <span class="tok-keyword">return</span> (n11 * n00 - n10 * n01) / @@ -589,12 +601,12 @@ <h2><a class="h_ident" id="h-0ZVrqEfTNs" href="#h-0ZVrqEfTNs" tabindex="-1" role (n01 + n11) * (n00 + n10)); }</pre> -<p><a class="p_ident" id="p-9LYXEJ7/Do" href="#p-9LYXEJ7/Do" tabindex="-1" role="presentation"></a>Esto también funciona para asignaciones creadas con <code>let</code>, <code>var</code> o <code>const</code>. Si sabes que el valor que estás asignando es un array, puedes usar corchetes para “mirar dentro” del valor y asignar sus contenidos.</p> +<p><a class="p_ident" id="p-JmhBaXF/Q4" href="#p-JmhBaXF/Q4" tabindex="-1" role="presentation"></a>Esto también funciona para asignaciones creadas con <code>let</code>, <code>var</code> o <code>const</code>. 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.</p> <p><a class="p_ident" id="p-HE0DP2Uh2r" href="#p-HE0DP2Uh2r" tabindex="-1" role="presentation"></a>Un truco similar funciona para objetos, usando llaves en lugar de corchetes:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ZXEdn9Xbfc" href="#c-ZXEdn9Xbfc" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> {name} = {<span class="tok-definition">name</span>: <span class="tok-string">"Faraji"</span>, <span class="tok-definition">age</span>: <span class="tok-number">23</span>}; -console.log(name); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Ik5nrAwi5X" href="#c-Ik5nrAwi5X" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> {nombre} = {<span class="tok-definition">nombre</span>: <span class="tok-string">"Faraji"</span>, <span class="tok-definition">edad</span>: <span class="tok-number">23</span>}; +console.log(nombre); <span class="tok-comment">// → Faraji</span></pre> <p><a class="p_ident" id="p-LIriiOwHJk" href="#p-LIriiOwHJk" tabindex="-1" role="presentation"></a>Ten en cuenta que si intentas desestructurar <code>null</code> o <code>undefined</code>, obtendrás un error, igual que si intentaras acceder directamente a una propiedad de esos valores.</p> @@ -603,72 +615,75 @@ <h2><a class="h_ident" id="h-noCKRaHSM3" href="#h-noCKRaHSM3" tabindex="-1" role <p><a class="p_ident" id="p-wRe9iP9JlM" href="#p-wRe9iP9JlM" tabindex="-1" role="presentation"></a>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: <code>objeto?.<wbr>propiedad</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-8Ro338UioI" href="#c-8Ro338UioI" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">city</span>(<span class="tok-definition">objeto</span>) { - <span class="tok-keyword">return</span> objeto.address?.city; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9UkuLveyom" href="#c-9UkuLveyom" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">ciudad</span>(<span class="tok-definition">objeto</span>) { + <span class="tok-keyword">return</span> objeto.dirección?.ciudad; } -console.log(city({<span class="tok-definition">address</span>: {<span class="tok-definition">city</span>: <span class="tok-string">"Toronto"</span>}})); +console.log(ciudad({<span class="tok-definition">dirección</span>: {<span class="tok-definition">ciudad</span>: <span class="tok-string">"Toronto"</span>}})); <span class="tok-comment">// → Toronto</span> -console.log(city({<span class="tok-definition">name</span>: <span class="tok-string">"Vera"</span>})); +console.log(ciudad({<span class="tok-definition">nombre</span>: <span class="tok-string">"Vera"</span>})); <span class="tok-comment">// → undefined</span></pre> <p><a class="p_ident" id="p-KoJHGM/bL4" href="#p-KoJHGM/bL4" tabindex="-1" role="presentation"></a>La expresión <code>a?.b</code> significa lo mismo que <code>a.b</code> cuando <code>a</code> 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.</p> <p><a class="p_ident" id="p-r0ohki4lB+" href="#p-r0ohki4lB+" tabindex="-1" role="presentation"></a>Una notación similar se puede utilizar con el acceso a corchetes cuadrados, e incluso con llamadas de funciones, colocando <code>?.</code> delante de los paréntesis o corchetes:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Zkj/5F9HnZ" href="#c-Zkj/5F9HnZ" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"string"</span>.notAMethod?.()); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-g+YUGfNb9X" href="#c-g+YUGfNb9X" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"cadena"</span>.metodoNoExistente?.()); <span class="tok-comment">// → undefined</span> -console.log({}.arrayProp?.[<span class="tok-number">0</span>]); +console.log({}.propiedadArray?.[<span class="tok-number">0</span>]); <span class="tok-comment">// → undefined</span></pre> <h2><a class="h_ident" id="h-AxpOdvCznQ" href="#h-AxpOdvCznQ" tabindex="-1" role="presentation"></a>JSON</h2> -<p><a class="p_ident" id="p-02eoQPAcYm" href="#p-02eoQPAcYm" tabindex="-1" role="presentation"></a>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 <em>direcciones</em>—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.</p> +<p><a class="p_ident" id="p-WtyMZGDyrR" href="#p-WtyMZGDyrR" tabindex="-1" role="presentation"></a>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 <em>direcciones</em> —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.</p> -<p><a class="p_ident" id="p-5LWe7n+Ljv" href="#p-5LWe7n+Ljv" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-nPFk61KDwQ" href="#p-nPFk61KDwQ" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-JJOqSqK7YC" href="#p-JJOqSqK7YC" tabindex="-1" role="presentation"></a>Lo que podemos hacer es <em>serializar</em> los datos. Eso significa que se convierten en una descripción plana. Un formato de serialización popular se llama <em>JSON</em> (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.</p> +<p><a class="p_ident" id="p-JJOqSqK7YC" href="#p-JJOqSqK7YC" tabindex="-1" role="presentation"></a>Lo que podemos hacer es <em>serializar</em> los datos. Es decir, convertirlos en una descripción plana. Un formato de serialización popular se llama <em>JSON</em> (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.</p> -<p><a class="p_ident" id="p-aPzc5dnhKk" href="#p-aPzc5dnhKk" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-aPzc5dnhKk" href="#p-aPzc5dnhKk" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-6JGxE+uNL9" href="#p-6JGxE+uNL9" tabindex="-1" role="presentation"></a>Una entrada de diario podría verse así cuando se representa como datos JSON:</p> +<p><a class="p_ident" id="p-kZLqcx6yZM" href="#p-kZLqcx6yZM" tabindex="-1" role="presentation"></a>Una entrada de diario podría verse así cuando se representa como datos en formato JSON:</p> -<pre class="snippet" data-language="json" ><a class="c_ident" id="c-A3jdCqz1Q6" href="#c-A3jdCqz1Q6" tabindex="-1" role="presentation"></a>{ - <span class="tok-string">"squirrel"</span>: false, - <span class="tok-string">"events"</span>: [<span class="tok-string">"work"</span>, <span class="tok-string">"touched tree"</span>, <span class="tok-string">"pizza"</span>, <span class="tok-string">"running"</span>] +<pre class="snippet" data-language="json" ><a class="c_ident" id="c-D5uPOcH1zJ" href="#c-D5uPOcH1zJ" tabindex="-1" role="presentation"></a>{ + <span class="tok-string">"ardilla"</span>: false, + <span class="tok-string">"eventos"</span>: [<span class="tok-string">"trabajo"</span>, <span class="tok-string">"tocar árbol"</span>, <span class="tok-string">"pizza"</span>, <span class="tok-string">"correr"</span>] }</pre> <p><a class="p_ident" id="p-wYsRotvDpk" href="#p-wYsRotvDpk" tabindex="-1" role="presentation"></a>JavaScript nos proporciona las funciones <code>JSON.stringify</code> y <code>JSON.parse</code> 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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-HYCgCsK7z1" href="#c-HYCgCsK7z1" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">string</span> = JSON.stringify({<span class="tok-definition">squirrel</span>: false, - <span class="tok-definition">events</span>: [<span class="tok-string">"weekend"</span>]}); -console.log(string); -<span class="tok-comment">// → {"squirrel":false,"events":["weekend"]}</span> -console.log(JSON.parse(string).events); -<span class="tok-comment">// → ["weekend"]</span></pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-LGbka9c2C+" href="#c-LGbka9c2C+" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">cadena</span> = JSON.stringify({<span class="tok-definition">ardilla</span>: false, + <span class="tok-definition">eventos</span>: [<span class="tok-string">"finde"</span>]}); +console.log(cadena); +<span class="tok-comment">// → {"ardilla":false,"eventos":["finde"]}</span> +console.log(JSON.parse(cadena).eventos); +<span class="tok-comment">// → ["finde"]</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-uklSBjybFG" href="#p-uklSBjybFG" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-nuQQY5Ad0J" href="#p-nuQQY5Ad0J" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-PIujZkn6f6" href="#p-PIujZkn6f6" tabindex="-1" role="presentation"></a>La mayoría de los valores en JavaScript tienen propiedades, con las excepciones de <code>null</code> y <code>undefined</code>. Las propiedades se acceden usando <code>valor.prop</code> o <code>valor["prop"]</code>. 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.</p> +<p><a class="p_ident" id="p-AhM1xLrgWH" href="#p-AhM1xLrgWH" tabindex="-1" role="presentation"></a>La mayoría de los valores en JavaScript tienen propiedades, con las excepciones de <code>null</code> y <code>undefined</code>. Las propiedades se acceden usando <code>valor.prop</code> o <code>valor["prop"]</code>. 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.</p> -<p><a class="p_ident" id="p-2VSYiTPCLX" href="#p-2VSYiTPCLX" tabindex="-1" role="presentation"></a>Sí <em>hay</em> algunas propiedades nombradas en arrays, como <code>length</code> 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.</p> +<p><a class="p_ident" id="p-2VSYiTPCLX" href="#p-2VSYiTPCLX" tabindex="-1" role="presentation"></a>Sí <em>hay</em> algunas propiedades con nombre en arrays, como <code>length</code> 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.</p> <p><a class="p_ident" id="p-tTGfuCa9pg" href="#p-tTGfuCa9pg" tabindex="-1" role="presentation"></a>Puedes iterar sobre arrays usando un tipo especial de bucle <code>for</code>: <code>for (let elemento of array)</code>.</p> <h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2> -<h3><a class="i_ident" id="i-TP9461qub1" href="#i-TP9461qub1" tabindex="-1" role="presentation"></a>La suma de un rango</h3> +<h3><a class="i_ident" id="i-O0mMgQwUN7" href="#i-O0mMgQwUN7" tabindex="-1" role="presentation"></a>La suma de un intervalo</h3> -<p><a class="p_ident" id="p-a8gdUhOUeS" href="#p-a8gdUhOUeS" tabindex="-1" role="presentation"></a>La <a href="00_intro.html">introducción</a> de este libro insinuó lo siguiente como una forma agradable de calcular la suma de un rango de números:</p> +<p><a class="p_ident" id="p-Q1D7ZBKd/V" href="#p-Q1D7ZBKd/V" tabindex="-1" role="presentation"></a>La <a href="00_intro.html">introducción</a> de este libro insinuó lo siguiente como una forma cómoda de calcular la suma de un intervalo (o rango) de números enteros:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-KTbQMmMCli" href="#c-KTbQMmMCli" tabindex="-1" role="presentation"></a>console.log(sum(range(<span class="tok-number">1</span>, <span class="tok-number">10</span>)));</pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-pxsnyeb5dE" href="#c-pxsnyeb5dE" tabindex="-1" role="presentation"></a>console.log(suma(rango(<span class="tok-number">1</span>, <span class="tok-number">10</span>)));</pre> <p><a class="p_ident" id="p-PEI80iV7Oh" href="#p-PEI80iV7Oh" tabindex="-1" role="presentation"></a>Escribe una función <code>range</code> que tome dos argumentos, <code>inicio</code> y <code>fin</code>, y devuelva un array que contenga todos los números desde <code>inicio</code> hasta <code>fin</code>, incluyendo <code>fin</code>.</p> +<div class="translator-note"><p><strong>N. del T.:</strong> 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í, <code>rango</code> será <code>range</code> y <code>suma</code> será <code>sum</code>.</p> +</div> + <p><a class="p_ident" id="p-7dOKdAEA/K" href="#p-7dOKdAEA/K" tabindex="-1" role="presentation"></a>Luego, escribe una función <code>sum</code> 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.</p> -<p><a class="p_ident" id="p-cO4LsVu0AY" href="#p-cO4LsVu0AY" tabindex="-1" role="presentation"></a>Como asignación adicional, modifica tu función <code>range</code> 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 <code>range(1, 10, 2)</code> debería devolver <code>[1, 3, 5, 7, 9]</code>. Asegúrate de que esto también funcione con valores de paso negativos, de modo que <code>range(5, 2, -1)</code> produzca <code>[5, 4, 3, 2]</code>.</p> +<p><a class="p_ident" id="p-kNEh6Yt+J9" href="#p-kNEh6Yt+J9" tabindex="-1" role="presentation"></a>Como bonus, modifica tu función <code>range</code> 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 <code>range(1, 10, 2)</code> debería devolver <code>[1, 3, 5, 7, 9]</code>. Asegúrate de que esto también funcione con valores de paso negativos, de modo que <code>range(5, 2, -1)</code> produzca <code>[5, 4, 3, 2]</code>.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Jpny5hXqcJ" href="#c-Jpny5hXqcJ" tabindex="-1" role="presentation"></a><span class="tok-comment">// Tu código aquí.</span> @@ -685,19 +700,19 @@ <h3><a class="i_ident" id="i-TP9461qub1" href="#i-TP9461qub1" tabindex="-1" role <p><a class="p_ident" id="p-Gm22TMDaw+" href="#p-Gm22TMDaw+" tabindex="-1" role="presentation"></a>Dado que el límite final es inclusivo, necesitarás usar el operador <code><=</code> en lugar de <code><</code> para verificar el final de tu bucle.</p> -<p><a class="p_ident" id="p-ngBadoSP/U" href="#p-ngBadoSP/U" tabindex="-1" role="presentation"></a>El parámetro de paso puede ser un parámetro opcional que por defecto (usando el operador <code>=</code>) es 1.</p> +<p><a class="p_ident" id="p-mRvcHnjARW" href="#p-mRvcHnjARW" tabindex="-1" role="presentation"></a>El parámetro de paso puede ser un parámetro opcional que por defecto (usando el operador <code>=</code>) sea 1.</p> <p><a class="p_ident" id="p-c2U4wD/4+o" href="#p-c2U4wD/4+o" tabindex="-1" role="presentation"></a>Hacer que <code>range</code> 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 <code>>=</code> en lugar de <code><=</code> al contar hacia abajo.</p> -<p><a class="p_ident" id="p-3W6YBIsuyN" href="#p-3W6YBIsuyN" tabindex="-1" role="presentation"></a>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, <code>range(5, 2)</code> 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.</p> +<p><a class="p_ident" id="p-3W6YBIsuyN" href="#p-3W6YBIsuyN" tabindex="-1" role="presentation"></a>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, <code>range(5, 2)</code> 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.</p> </div></details> -<h3><a class="i_ident" id="i-ZA7kEgnlyq" href="#i-ZA7kEgnlyq" tabindex="-1" role="presentation"></a>Reversión de un array</h3> +<h3><a class="i_ident" id="i-IFzJM8Y/aQ" href="#i-IFzJM8Y/aQ" tabindex="-1" role="presentation"></a>Dando la vuelta a un array</h3> -<p><a class="p_ident" id="p-RN/q2v3dpH" href="#p-RN/q2v3dpH" tabindex="-1" role="presentation"></a>Los arrays tienen un método <code>reverse</code> que cambia el array invirtiendo el orden en el que aparecen sus elementos. Para este ejercicio, escribe dos funciones, <code>reverseArray</code> y <code>reverseArrayInPlace</code>. La primera, <code>reverseArray</code>, debería tomar un array como argumento y producir un <em>nuevo</em> array que tenga los mismos elementos en orden inverso. La segunda, <code>reverseArrayInPlace</code>, debería hacer lo que hace el método <code>reverse</code>: <em>modificar</em> el array dado como argumento invirtiendo sus elementos. Ninguna de las funciones puede utilizar el método <code>reverse</code> estándar.</p> +<p><a class="p_ident" id="p-RN/q2v3dpH" href="#p-RN/q2v3dpH" tabindex="-1" role="presentation"></a>Los arrays tienen un método <code>reverse</code> que modifica el array invirtiendo el orden en el que aparecen sus elementos. Para este ejercicio, escribe dos funciones: <code>reverseArray</code> y <code>reverseArrayInPlace</code>. La primera, <code>reverseArray</code>, debería tomar un array como argumento y producir un <em>nuevo</em> array que tenga los mismos elementos pero en orden inverso. La segunda, <code>reverseArrayInPlace</code>, debería hacer lo que hace el método <code>reverse</code>: <em>modificar</em> el array dado como argumento invirtiendo sus elementos. Ninguna de las funciones puede utilizar el método <code>reverse</code> estándar.</p> -<p><a class="p_ident" id="p-dDITyrsPOv" href="#p-dDITyrsPOv" tabindex="-1" role="presentation"></a>Recordando las notas sobre efectos secundarios y funciones puras en el <a href="03_functions.html#pure">capítulo anterior</a>, ¿qué variante esperas que sea útil en más situaciones? ¿Cuál se ejecuta más rápido?</p> +<p><a class="p_ident" id="p-v7RSCOzqiC" href="#p-v7RSCOzqiC" tabindex="-1" role="presentation"></a>Pensando en la parte sobre efectos secundarios y funciones puras del <a href="03_functions.html#pure">capítulo anterior</a>, ¿qué variante esperas que sea útil en más situaciones? ¿Cuál se ejecuta más rápido?</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-PAddgWfJJx" href="#c-PAddgWfJJx" tabindex="-1" role="presentation"></a><span class="tok-comment">// Tu código aquí.</span> @@ -713,11 +728,11 @@ <h3><a class="i_ident" id="i-ZA7kEgnlyq" href="#i-ZA7kEgnlyq" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-wFwjKkG+fd" href="#p-wFwjKkG+fd" tabindex="-1" role="presentation"></a>Hay dos formas obvias de implementar <code>reverseArray</code>. La primera es simplemente recorrer el array de entrada de principio a fin y usar el método <code>unshift</code> 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 <code>push</code>. Iterar sobre un array hacia atrás requiere una especificación de bucle (algo incómoda), como <code>(let i = array.<wbr>length - 1; i >= 0; i--)</code>.</p> +<p><a class="p_ident" id="p-OZ3mlsSrcZ" href="#p-OZ3mlsSrcZ" tabindex="-1" role="presentation"></a>Hay dos formas obvias de implementar <code>reverseArray</code>. La primera es simplemente recorrer el array de entrada de principio a fin y usar el método <code>unshift</code> 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 <code>push</code>. Iterar sobre un array hacia atrás requiere una especificación (un poco rara) de bucle <code>for</code>, como <code>(let i = array.<wbr>length - 1; i >= 0; i--)</code>.</p> -<p><a class="p_ident" id="p-XYnDf63vic" href="#p-XYnDf63vic" tabindex="-1" role="presentation"></a>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 <code>reverseArray</code> o copiar todo el array de otra manera (usar <code>array.slice()</code> es una buena forma de copiar un array) funciona pero es hacer trampa.</p> +<p><a class="p_ident" id="p-XYnDf63vic" href="#p-XYnDf63vic" tabindex="-1" role="presentation"></a>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 <code>reverseArray</code> o copiar todo el array (usar <code>array.slice()</code> es una buena forma de copiar un array) funciona pero es hacer trampa.</p> -<p><a class="p_ident" id="p-Tm3Q5SGSnk" href="#p-Tm3Q5SGSnk" tabindex="-1" role="presentation"></a>El truco consiste en <em>intercambiar</em> 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 <code>Math.floor</code> 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 <code>i</code> con el que está en la posición <code>array.<wbr>length - 1 - i</code>. 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.</p> +<p><a class="p_ident" id="p-5Qo4HWsY8i" href="#p-5Qo4HWsY8i" tabindex="-1" role="presentation"></a>El truco consiste en <em>intercambiar</em> 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 <code>Math.floor</code> 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 <code>i</code> con el que está en la posición <code>array.<wbr>length - 1 - i</code>. 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.</p> </div></details> @@ -738,9 +753,9 @@ <h3 id="list"><a class="i_ident" id="i-5BqUd2Lp+I" href="#i-5BqUd2Lp+I" tabindex <p><a class="p_ident" id="p-wpdtbtf3j+" href="#p-wpdtbtf3j+" tabindex="-1" role="presentation"></a>Los objetos resultantes forman una cadena, como se muestra en el siguiente diagrama:</p><figure><img src="img/linked-list.svg" alt="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."></figure> -<p><a class="p_ident" id="p-yjTdFzi/9b" href="#p-yjTdFzi/9b" tabindex="-1" role="presentation"></a>Una ventaja de las listas es que pueden compartir partes de su estructura. Por ejemplo, si creo dos nuevos valores <code>{value: 0, rest: list}</code> y <code>{value: -1, rest: list}</code> (siendo <code>list</code> 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.</p> +<p><a class="p_ident" id="p-yjTdFzi/9b" href="#p-yjTdFzi/9b" tabindex="-1" role="presentation"></a>Una ventaja de las listas es que pueden compartir partes de su estructura. Por ejemplo, si creo dos nuevos valores <code>{value: 0, rest: list}</code> y <code>{value: -1, rest: list}</code> (siendo <code>list</code> 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).</p> -<p><a class="p_ident" id="p-BZXD/SfJ/p" href="#p-BZXD/SfJ/p" tabindex="-1" role="presentation"></a>Escribe una función <code>arrayToList</code> que construya una estructura de lista como la mostrada cuando se le da <code>[1, 2, 3]</code> como argumento. También escribe una función <code>listToArray</code> que produzca un array a partir de una lista. Agrega las funciones auxiliares <code>prepend</code>, 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 <code>nth</code>, 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 <code>undefined</code> cuando no hay tal elemento.</p> +<p><a class="p_ident" id="p-BZXD/SfJ/p" href="#p-BZXD/SfJ/p" tabindex="-1" role="presentation"></a>Escribe una función <code>arrayToList</code> que construya una estructura de lista como la mostrada cuando se le da <code>[1, 2, 3]</code> como argumento. También escribe una función <code>listToArray</code> que produzca un array a partir de una lista. Agrega las funciones auxiliares <code>prepend</code>, 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 <code>nth</code>, 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 <code>undefined</code> cuando no hay tal elemento.</p> <p><a class="p_ident" id="p-l5pM0S3Ycp" href="#p-l5pM0S3Ycp" tabindex="-1" role="presentation"></a>Si aún no lo has hecho, escribe también una versión recursiva de <code>nth</code>.</p> @@ -757,29 +772,29 @@ <h3 id="list"><a class="i_ident" id="i-5BqUd2Lp+I" href="#i-5BqUd2Lp+I" tabindex <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-Yv5/kokF70" href="#p-Yv5/kokF70" tabindex="-1" role="presentation"></a>Construir una lista es más fácil cuando se hace de atrás hacia adelante. Por lo tanto, <code>arrayToList</code> 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 <code>lista = {value: X, rest: lista}</code> para añadir un elemento.</p> +<p><a class="p_ident" id="p-Yv5/kokF70" href="#p-Yv5/kokF70" tabindex="-1" role="presentation"></a>Construir una lista es más fácil cuando se hace de atrás hacia adelante. Por lo tanto, <code>arrayToList</code> 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 <code>list = {value: X, rest: list}</code> para añadir un elemento.</p> <p><a class="p_ident" id="p-fiLk2ZIMfG" href="#p-fiLk2ZIMfG" tabindex="-1" role="presentation"></a>Para recorrer una lista (en <code>listToArray</code> y <code>nth</code>), se puede utilizar una especificación de bucle <code>for</code> de esta forma:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-f2Ev/YtyZ+" href="#c-f2Ev/YtyZ+" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">nodo</span> = list; nodo; nodo = nodo.rest) {}</pre> -<p><a class="p_ident" id="p-ycL1FPwmJf" href="#p-ycL1FPwmJf" tabindex="-1" role="presentation"></a>¿Puedes ver cómo funciona esto? En cada iteración del bucle, <code>nodo</code> apunta a la sublista actual, y el cuerpo puede leer su propiedad <code>value</code> para obtener el elemento actual. Al final de una iteración, <code>nodo</code> pasa a la siguiente sublista. Cuando eso es nulo, hemos llegado al final de la lista y el bucle ha terminado.</p> +<p><a class="p_ident" id="p-wW6tvS8XNH" href="#p-wW6tvS8XNH" tabindex="-1" role="presentation"></a>¿Entiendes cómo funciona? En cada iteración del bucle, <code>nodo</code> apunta a la sublista actual, y el cuerpo puede leer su propiedad <code>value</code> para obtener el elemento actual. Al final de una iteración, <code>nodo</code> pasa a la siguiente sublista. Cuando esta asignación dé nulo, hemos llegado al final de la lista y el bucle acaba.</p> -<p><a class="p_ident" id="p-bUw9xwVV8F" href="#p-bUw9xwVV8F" tabindex="-1" role="presentation"></a>La versión recursiva de <code>nth</code> 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 <code>value</code> del nodo que está observando. Para obtener el elemento cero de una lista, simplemente tomas la propiedad <code>value</code> de su nodo principal. Para obtener el elemento <em>N</em> + 1, tomas el elemento <em>N</em>-ésimo de la lista que se encuentra en la propiedad <code>rest</code> de esta lista.</p> +<p><a class="p_ident" id="p-bUw9xwVV8F" href="#p-bUw9xwVV8F" tabindex="-1" role="presentation"></a>La versión recursiva de <code>nth</code> 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 <code>value</code> del nodo que está observando. Para obtener el elemento cero de una lista, simplemente tomas la propiedad <code>value</code> de su nodo principal. Para obtener el elemento <em>N</em> + 1, tomas <em>N</em>-ésimo elemento de la lista que se encuentra en la propiedad <code>rest</code> de esta lista.</p> </div></details> <h3 id="exercise_deep_compare"><a class="i_ident" id="i-cW6eoBCFDE" href="#i-cW6eoBCFDE" tabindex="-1" role="presentation"></a>Comparación profunda</h3> -<p><a class="p_ident" id="p-ns4gGvrmtT" href="#p-ns4gGvrmtT" tabindex="-1" role="presentation"></a>El operador <code>==</code> compara objetos por identidad, pero a veces preferirías comparar los valores de sus propiedades reales.</p> +<p><a class="p_ident" id="p-HJal1jteWZ" href="#p-HJal1jteWZ" tabindex="-1" role="presentation"></a>El operador <code>==</code> compara objetos por identidad, pero a veces preferirías comparar los valores de sus propiedades.</p> -<p><a class="p_ident" id="p-TQ44glEie8" href="#p-TQ44glEie8" tabindex="-1" role="presentation"></a>Escribe una función <code>deepEqual</code> 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 <code>deepEqual</code>.</p> +<p><a class="p_ident" id="p-TQ44glEie8" href="#p-TQ44glEie8" tabindex="-1" role="presentation"></a>Escribe una función <code>deepEqual</code> 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 <code>deepEqual</code>.</p> -<p><a class="p_ident" id="p-W8XSwwBFsP" href="#p-W8XSwwBFsP" tabindex="-1" role="presentation"></a>Para saber si los valores deben compararse directamente (usando el operador <code>===</code> para eso) o si sus propiedades deben compararse, puedes usar el operador <code>typeof</code>. Si produce <code>"object"</code> 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, <code>typeof null</code> también produce <code>"object"</code>.</p> +<p><a class="p_ident" id="p-W8XSwwBFsP" href="#p-W8XSwwBFsP" tabindex="-1" role="presentation"></a>Para saber si los valores deben compararse directamente (usando el operador <code>===</code> para eso) o si sus propiedades deben compararse, puedes usar el operador <code>typeof</code>. Si produce <code>"object"</code> para ambos valores, deberías hacer una comparación profunda. Pero debes tener en cuenta una excepción: debido a un accidente histórico, <code>typeof null</code> también produce <code>"object"</code>.</p> <p><a class="p_ident" id="p-OLfCT6PXY9" href="#p-OLfCT6PXY9" tabindex="-1" role="presentation"></a>La función <code>Object.keys</code> será útil cuando necesites recorrer las propiedades de los objetos para compararlas.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-kWPN5i/Y3x" href="#c-kWPN5i/Y3x" tabindex="-1" role="presentation"></a><span class="tok-comment">// Your code here.</span> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ZAPvBH1LEK" href="#c-ZAPvBH1LEK" tabindex="-1" role="presentation"></a><span class="tok-comment">// Tu código aquí.</span> <span class="tok-keyword">let</span> <span class="tok-definition">obj</span> = {<span class="tok-definition">here</span>: {<span class="tok-definition">is</span>: <span class="tok-string">"an"</span>}, <span class="tok-definition">object</span>: <span class="tok-number">2</span>}; console.log(deepEqual(obj, obj)); @@ -791,11 +806,11 @@ <h3 id="exercise_deep_compare"><a class="i_ident" id="i-cW6eoBCFDE" href="#i-cW6 <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-7mOFXJ3b9x" href="#p-7mOFXJ3b9x" tabindex="-1" role="presentation"></a>La prueba para determinar si estás tratando con un objeto real se verá algo así: <code>typeof x == "object" && x != null</code>. Ten cuidado de comparar propiedades solo cuando <em>ambos</em> argumentos sean objetos. En todos los demás casos, simplemente puedes devolver inmediatamente el resultado de aplicar <code>===</code>.</p> +<p><a class="p_ident" id="p-UqEXxyUOA0" href="#p-UqEXxyUOA0" tabindex="-1" role="presentation"></a>Tu comprobación para determinar si estás tratando con un objeto real tendrá una pinta como esta: <code>typeof x == "object" && x != null</code>. Ten cuidado de comparar propiedades solo cuando <em>ambos</em> argumentos sean objetos. En todos los demás casos, simplemente puedes devolver inmediatamente el resultado de aplicar <code>===</code>.</p> -<p><a class="p_ident" id="p-VZeuOZahQK" href="#p-VZeuOZahQK" tabindex="-1" role="presentation"></a>Utiliza <code>Object.keys</code> 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.</p> +<p><a class="p_ident" id="p-VZeuOZahQK" href="#p-VZeuOZahQK" tabindex="-1" role="presentation"></a>Utiliza <code>Object.keys</code> 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.</p> -<p><a class="p_ident" id="p-G0WQPsfcge" href="#p-G0WQPsfcge" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-f8iczv5PFl" href="#p-f8iczv5PFl" tabindex="-1" role="presentation"></a>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.</p> </div></details><nav><a href="03_functions.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="05_higher_order.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button> </nav> diff --git a/html/05_higher_order.html b/html/05_higher_order.html index ab8ffbd4..08565672 100644 --- a/html/05_higher_order.html +++ b/html/05_higher_order.html @@ -12,18 +12,18 @@ <h1>Funciones de Orden Superior</h1> -<p><a class="p_ident" id="p-vkRKropLCL" href="#p-vkRKropLCL" tabindex="-1" role="presentation"></a><em>“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.”</em></p> +<p><a class="p_ident" id="p-eV2Rh7S+DT" href="#p-eV2Rh7S+DT" tabindex="-1" role="presentation"></a><em>“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.”</em></p> <p><a class="p_ident" id="p-rComRYoME7" href="#p-rComRYoME7" tabindex="-1" role="presentation"></a>— C.A.R. Hoare, <em>Discurso de Recepción del Premio Turing de la ACM de 1980</em></p> -<p><a class="p_ident" id="p-UkjkuKBNDn" href="#p-UkjkuKBNDn" tabindex="-1" role="presentation"></a>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 (<em>bugs</em>) en los programas. Un programa grande proporciona mucho espacio para que estos errores se escondan, lo que los hace difíciles de encontrar.</p> +<p><a class="p_ident" id="p-UkjkuKBNDn" href="#p-UkjkuKBNDn" tabindex="-1" role="presentation"></a>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 (<em>bugs</em>) en los programas. Un programa grande da mucho hueco para que estos errores se escondan, lo que los hace difíciles de encontrar.</p> -<p><a class="p_ident" id="p-JH/k/etNiT" href="#p-JH/k/etNiT" tabindex="-1" role="presentation"></a>Volviendo brevemente a los dos ejemplos finales de programas en la introducción. El primero es autocontenido y tiene seis líneas:</p> +<p><a class="p_ident" id="p-Q033k+pXx6" href="#p-Q033k+pXx6" tabindex="-1" role="presentation"></a>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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-uIkg9pj99q" href="#c-uIkg9pj99q" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">total</span> = <span class="tok-number">0</span>, <span class="tok-definition">count</span> = <span class="tok-number">1</span>; -<span class="tok-keyword">while</span> (count <= <span class="tok-number">10</span>) { - total += count; - count += <span class="tok-number">1</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NaiLvX4kR2" href="#c-NaiLvX4kR2" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">total</span> = <span class="tok-number">0</span>, <span class="tok-definition">contador</span> = <span class="tok-number">1</span>; +<span class="tok-keyword">while</span> (contador <= <span class="tok-number">10</span>) { + total += contador; + contador += <span class="tok-number">1</span>; } console.log(total);</pre> @@ -33,35 +33,45 @@ <h1>Funciones de Orden Superior</h1> <p><a class="p_ident" id="p-mQkKtUTj8f" href="#p-mQkKtUTj8f" tabindex="-1" role="presentation"></a>¿Cuál es más probable que contenga un error?</p> -<p><a class="p_ident" id="p-kr8iBt6cDE" href="#p-kr8iBt6cDE" tabindex="-1" role="presentation"></a>Si contamos el tamaño de las definiciones de <code>suma</code> y <code>rango</code>, 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.</p> +<p><a class="p_ident" id="p-kr8iBt6cDE" href="#p-kr8iBt6cDE" tabindex="-1" role="presentation"></a>Si contamos el tamaño de las definiciones de <code>suma</code> y <code>rango</code>, 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.</p> -<p><a class="p_ident" id="p-sdRHhhOglU" href="#p-sdRHhhOglU" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-DC41zE9LVe" href="#p-DC41zE9LVe" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-ZVaOtVwzgJ" href="#p-ZVaOtVwzgJ" tabindex="-1" role="presentation"></a>Las definiciones de este vocabulario (las funciones <code>suma</code> y <code>rango</code>) 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.</p> +<p><a class="p_ident" id="p-ZVaOtVwzgJ" href="#p-ZVaOtVwzgJ" tabindex="-1" role="presentation"></a>Las definiciones de este vocabulario (las funciones <code>suma</code> y <code>rango</code>) 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.</p> <h2><a class="h_ident" id="h-jeqhU5hoax" href="#h-jeqhU5hoax" tabindex="-1" role="presentation"></a>Abstracción</h2> -<p><a class="p_ident" id="p-b96WneVaDi" href="#p-b96WneVaDi" tabindex="-1" role="presentation"></a>En el contexto de la programación, este tipo de vocabularios se suelen llamar <em>abstractions</em>. Las abstracciones nos brindan la capacidad de hablar sobre problemas a un nivel superior (o más abstracto), sin distraernos con detalles no interesantes.</p> +<p><a class="p_ident" id="p-b96WneVaDi" href="#p-b96WneVaDi" tabindex="-1" role="presentation"></a>En el contexto de la programación, este tipo de vocabularios se suelen llamar <em>abstracciones</em>. Las abstracciones nos brindan la capacidad de hablar sobre problemas a un nivel superior (o más abstracto), sin distraernos con detalles no interesantes.</p> <p><a class="p_ident" id="p-vujVr5jCqZ" href="#p-vujVr5jCqZ" tabindex="-1" role="presentation"></a>Como analogía, compara estas dos recetas de sopa de guisantes. La primera es así:</p> -<p><a class="p_ident" id="p-fxkqqXVuPN" href="#p-fxkqqXVuPN" tabindex="-1" role="presentation"></a>_”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:</p> +<blockquote> + +<p><a class="p_ident" id="p-bHDeGVTCo+" href="#p-bHDeGVTCo+" tabindex="-1" role="presentation"></a>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.</p> + +</blockquote> <p><a class="p_ident" id="p-dL6mnA4R6A" href="#p-dL6mnA4R6A" tabindex="-1" role="presentation"></a>Y esta es la segunda receta:</p> +<blockquote> + <p><a class="p_ident" id="p-tZ51jSgceM" href="#p-tZ51jSgceM" tabindex="-1" role="presentation"></a>Por persona: 1 taza de guisantes partidos secos, 4 tazas de agua, media cebolla picada, un tallo de apio y una zanahoria.</p> <p><a class="p_ident" id="p-pWLdoICo3H" href="#p-pWLdoICo3H" tabindex="-1" role="presentation"></a>Remoja los guisantes durante 12 horas. Cocina a fuego lento durante 2 horas. Pica y agrega las verduras. Cocina durante 10 minutos más.</p> -<p><a class="p_ident" id="p-TaRmoVsqmE" href="#p-TaRmoVsqmE" tabindex="-1" role="presentation"></a>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 <em>remojar</em>, <em>cocinar a fuego lento</em>, <em>picar</em>, y, supongo, <em>verdura</em>.</p> +</blockquote> -<p><a class="p_ident" id="p-qhHkdQl/il" href="#p-qhHkdQl/il" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-d9+qq62FnP" href="#p-d9+qq62FnP" tabindex="-1" role="presentation"></a>La segunda es más corta y fácil de interpretar. Pero necesitas entender algunas palabras más relacionadas con la cocina, como <em>remojar</em>, <em>cocinar a fuego lento</em>, <em>picar</em>, y, supongo, <em>verdura</em>.</p> -<p><a class="p_ident" id="p-NUMaFMlENr" href="#p-NUMaFMlENr" tabindex="-1" role="presentation"></a>Abstraer la repetición</p> +<p><a class="p_ident" id="p-qhHkdQl/il" href="#p-qhHkdQl/il" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-+yKMsSOH6s" href="#p-+yKMsSOH6s" tabindex="-1" role="presentation"></a>Las funciones simples, como las hemos visto hasta ahora, son una buena manera de construir abstracciones. Pero a veces se quedan cortas.</p> +<p><a class="p_ident" id="p-k17Wm3bRzR" href="#p-k17Wm3bRzR" tabindex="-1" role="presentation"></a>Una habilidad útil en programación es darse cuenta de cuándo se está trabajando a un muy bajo nivel de abstracción.</p> -<p><a class="p_ident" id="p-S9ZNhpazRV" href="#p-S9ZNhpazRV" tabindex="-1" role="presentation"></a>Es común que un programa haga algo un número determinado de veces. Puedes escribir un <code>for</code> para eso, así:</p> +<h2><a class="h_ident" id="h-3uF6LnSOqd" href="#h-3uF6LnSOqd" tabindex="-1" role="presentation"></a>Abstraer la repetición</h2> + +<p><a class="p_ident" id="p-ycCWB39qtV" href="#p-ycCWB39qtV" tabindex="-1" role="presentation"></a>Funciones simples como las hemos visto hasta ahora son una buena manera de construir abstracciones. Pero a veces se quedan cortas.</p> + +<p><a class="p_ident" id="p-S9ZNhpazRV" href="#p-S9ZNhpazRV" tabindex="-1" role="presentation"></a>Es común que un programa haga algo una cantidad determinada de veces. Puedes escribir un <code>for</code> para eso, así:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5c+C2+9IG1" href="#c-5c+C2+9IG1" tabindex="-1" role="presentation"></a><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 < <span class="tok-number">10</span>; i++) { console.log(i); @@ -69,17 +79,17 @@ <h2><a class="h_ident" id="h-jeqhU5hoax" href="#h-jeqhU5hoax" tabindex="-1" role <p><a class="p_ident" id="p-mYLOzu14uu" href="#p-mYLOzu14uu" tabindex="-1" role="presentation"></a>¿Podemos abstraer “hacer algo <em>N</em> veces” como una función? Bueno, es fácil escribir una función que llame a <code>console.log</code> <em>N</em> veces:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-/gKhlra9P+" href="#c-/gKhlra9P+" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">repeatLog</span>(<span class="tok-definition">n</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-01lxFf/pce" href="#c-01lxFf/pce" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">repetirLog</span>(<span class="tok-definition">n</span>) { <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 < n; i++) { console.log(i); } }</pre> -<p><a class="p_ident" id="p-aHRJYV9dtZ" href="#p-aHRJYV9dtZ" tabindex="-1" role="presentation"></a>¿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:</p> +<p><a class="p_ident" id="p-aHRJYV9dtZ" href="#p-aHRJYV9dtZ" tabindex="-1" role="presentation"></a>¿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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-7IqCeYcKwP" href="#c-7IqCeYcKwP" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">repetir</span>(<span class="tok-definition">n</span>, <span class="tok-definition">action</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-PTLJYIA4FA" href="#c-PTLJYIA4FA" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">repetir</span>(<span class="tok-definition">n</span>, <span class="tok-definition">acción</span>) { <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 < n; i++) { - action(i); + acción(i); } } @@ -90,20 +100,23 @@ <h2><a class="h_ident" id="h-jeqhU5hoax" href="#h-jeqhU5hoax" tabindex="-1" role <p><a class="p_ident" id="p-MZU1WHGlo3" href="#p-MZU1WHGlo3" tabindex="-1" role="presentation"></a>No tenemos que pasar una función predefinida a <code>repetir</code>. A menudo, es más fácil crear un valor de función en el momento:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-s8RST25oGA" href="#c-s8RST25oGA" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">etiquetas</span> = []; -repetir(<span class="tok-number">5</span>, <span class="tok-definition">i</span> => { - etiquetas.push(<span class="tok-string2">`Unidad </span>${i + <span class="tok-number">1</span>}<span class="tok-string2">`</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-61hbFnWHXS" href="#c-61hbFnWHXS" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">etiquetas</span> = []; +repetir(<span class="tok-number">5</span>, <span class="tok-definition">x</span> => { + etiquetas.push(<span class="tok-string2">`Unidad </span>${x + <span class="tok-number">1</span>}<span class="tok-string2">`</span>); }); console.log(etiquetas); <span class="tok-comment">// → ["Unidad 1", "Unidad 2", "Unidad 3", "Unidad 4", "Unidad 5"]</span></pre> -<p><a class="p_ident" id="p-ZaxR2WEHob" href="#p-ZaxR2WEHob" tabindex="-1" role="presentation"></a>Esto está estructurado un poco como un <code>for</code> 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 <code>repetir</code>. 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.</p> +<div class="translator-note"><p><strong>N. del T.:</strong> Con respecto a la versión original del texto, se ha cambiado el nombre del parámetro en la función flecha de <code>i</code> a <code>x</code> 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 <code>repetir</code>.</p> +</div> + +<p><a class="p_ident" id="p-ZaxR2WEHob" href="#p-ZaxR2WEHob" tabindex="-1" role="presentation"></a>Esto está estructurado un poco como un bucle <code>for</code>: 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 <code>repetir</code>. Por eso tiene que cerrarse con el corchete de cierre <em>y</em> 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.</p> -<p><a class="p_ident" id="p-sBm/RoOmVE" href="#p-sBm/RoOmVE" tabindex="-1" role="presentation"></a>Funciones de orden superior</p> +<h2><a class="h_ident" id="h-sBm/RoOmVE" href="#h-sBm/RoOmVE" tabindex="-1" role="presentation"></a>Funciones de orden superior</h2> -<p><a class="p_ident" id="p-mB+p2eklnH" href="#p-mB+p2eklnH" tabindex="-1" role="presentation"></a>Las funciones que operan en otras funciones, ya sea tomandolas como argumentos o devolviéndolas, se llaman <em>funciones de orden superior</em>. 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.</p> +<p><a class="p_ident" id="p-mB+p2eklnH" href="#p-mB+p2eklnH" tabindex="-1" role="presentation"></a>Las funciones que operan sobre otras funciones, ya sea tomándolas como argumentos o devolviéndolas, se llaman <em>funciones de orden superior</em>. 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.</p> -<p><a class="p_ident" id="p-sGlpxAbe6R" href="#p-sGlpxAbe6R" tabindex="-1" role="presentation"></a>Las funciones de orden superior nos permiten abstraer sobre <em>acciones</em>, no solo sobre valores. Vienen en varias formas. Por ejemplo, podemos tener funciones que crean nuevas funciones:</p> +<p><a class="p_ident" id="p-sGlpxAbe6R" href="#p-sGlpxAbe6R" tabindex="-1" role="presentation"></a>Las funciones de orden superior nos permiten abstraer <em>acciones</em>, no solo valores. Las hay de muchas formas. Por ejemplo, podemos tener funciones que crean nuevas funciones:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-yl26zgd/Q+" href="#c-yl26zgd/Q+" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">mayorQue</span>(<span class="tok-definition">n</span>) { <span class="tok-keyword">return</span> <span class="tok-definition">m</span> => m > n; @@ -140,217 +153,217 @@ <h2><a class="h_ident" id="h-jeqhU5hoax" href="#h-jeqhU5hoax" tabindex="-1" role <span class="tok-comment">// → 0 es par</span> <span class="tok-comment">// → 2 es par</span></pre> -<p><a class="p_ident" id="p-Zjga2rFofq" href="#p-Zjga2rFofq" tabindex="-1" role="presentation"></a>Existe un método incorporado de arrays, <code>forEach</code>, que proporciona algo similar a un bucle <code>for</code>/<code>of</code> como una función de orden superior:</p> +<p><a class="p_ident" id="p-Zjga2rFofq" href="#p-Zjga2rFofq" tabindex="-1" role="presentation"></a>Existe un método ya incorporado en los arrays, <code>forEach</code>, que proporciona algo similar a un bucle <code>for</code>/<code>of</code> como una función de orden superior:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-v9jL6NafRj" href="#c-v9jL6NafRj" tabindex="-1" role="presentation"></a>[<span class="tok-string">"A"</span>, <span class="tok-string">"B"</span>].forEach(<span class="tok-definition">l</span> => console.log(l)); <span class="tok-comment">// → A</span> <span class="tok-comment">// → B</span></pre> -<h2 id="scripts"><a class="h_ident" id="h-1WwJcQwjxW" href="#h-1WwJcQwjxW" tabindex="-1" role="presentation"></a>Conjunto de datos de script</h2> +<h2 id="scripts"><a class="h_ident" id="h-OdHcd1haxZ" href="#h-OdHcd1haxZ" tabindex="-1" role="presentation"></a>Conjunto de datos de sistemas de escritura</h2> -<p><a class="p_ident" id="p-nLcvnbV7JQ" href="#p-nLcvnbV7JQ" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-nLcvnbV7JQ" href="#p-nLcvnbV7JQ" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-QWRc3oEeB3" href="#p-QWRc3oEeB3" tabindex="-1" role="presentation"></a>¿Recuerdas Unicode del <a href="01_values.html#unicode">Capítulo 1</a>, 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.</p> +<p><a class="p_ident" id="p-QWRc3oEeB3" href="#p-QWRc3oEeB3" tabindex="-1" role="presentation"></a>¿Recuerdas Unicode del <a href="01_values.html#unicode">Capítulo 1</a>, 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.</p> <p><a class="p_ident" id="p-Y+uBE+NY1X" href="#p-Y+uBE+NY1X" tabindex="-1" role="presentation"></a>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:</p><figure><img src="img/tamil.png" alt="Una línea de verso en escritura Tamil. Los caracteres son relativamente simples y separados ordenadamente, pero completamente diferentes de los caracteres latinos."></figure> -<p><a class="p_ident" id="p-JQFACqGSut" href="#p-JQFACqGSut" tabindex="-1" role="presentation"></a>El ejemplo del conjunto de datos contiene algunas piezas de información sobre los 140 scripts definidos en Unicode. Está disponible en el <a href="https://eloquentjavascript.net/code#5">sandbox de código</a> para este capítulo como el enlace <code>SCRIPTS</code>. El enlace contiene un array de objetos, cada uno describe un script:</p> +<p><a class="p_ident" id="p-ELQZberUtO" href="#p-ELQZberUtO" tabindex="-1" role="presentation"></a>El conjunto de datos de ejemplo contiene información sobre los 140 sistemas de escritura definidos en Unicode. Está disponible en el <a href="https://eloquentjavascript.net/code#5">sandbox de código</a> para este capítulo como la asociación de nombre <code>SCRIPTS</code>. La variable contiene un array de objetos, cada uno describiendo un sistema de escritura:</p> -<pre class="snippet" data-language="json" ><a class="c_ident" id="c-vN789s6onO" href="#c-vN789s6onO" tabindex="-1" role="presentation"></a>{ - <span class="tok-definition">name</span>: <span class="tok-string">"Copto"</span>, - <span class="tok-definition">rangos</span>: [[<span class="tok-number">994</span>, <span class="tok-number">1008</span>], [<span class="tok-number">11392</span>, <span class="tok-number">11508</span>], [<span class="tok-number">11513</span>, <span class="tok-number">11520</span>]], - <span class="tok-definition">dirección</span>: <span class="tok-string">"ltr"</span>, - <span class="tok-definition">año</span>: -<span class="tok-number">200</span>, - <span class="tok-definition">vivo</span>: false, - <span class="tok-definition">enlace</span>: <span class="tok-string">"https://es.wikipedia.org/wiki/Alfabeto_copto"</span> +<pre class="snippet" data-language="json" ><a class="c_ident" id="c-YkfuyBG2fl" href="#c-YkfuyBG2fl" tabindex="-1" role="presentation"></a>{ + <span class="tok-definition">name</span>: <span class="tok-string">"Coptic"</span>, + <span class="tok-definition">ranges</span>: [[<span class="tok-number">994</span>, <span class="tok-number">1008</span>], [<span class="tok-number">11392</span>, <span class="tok-number">11508</span>], [<span class="tok-number">11513</span>, <span class="tok-number">11520</span>]], + <span class="tok-definition">direction</span>: <span class="tok-string">"ltr"</span>, + <span class="tok-definition">year</span>: -<span class="tok-number">200</span>, + <span class="tok-definition">living</span>: false, + <span class="tok-definition">link</span>: <span class="tok-string">"https://en.wikipedia.org/wiki/Coptic_alphabet"</span> }</pre> -<p><a class="p_ident" id="p-ua08rL6DL0" href="#p-ua08rL6DL0" tabindex="-1" role="presentation"></a>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 <code>"ltr"</code> para izquierda a derecha, <code>"rtl"</code> para derecha a izquierda (como se escribe el texto en árabe y hebreo) o <code>"ttb"</code> para arriba hacia abajo (como en la escritura mongola).</p> +<p><a class="p_ident" id="p-ua08rL6DL0" href="#p-ua08rL6DL0" tabindex="-1" role="presentation"></a>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 <code>"ltr"</code> para izquierda a derecha, <code>"rtl"</code> para derecha a izquierda (como se escribe el texto en árabe y hebreo) o <code>"ttb"</code> para arriba hacia abajo (como en la escritura mongola).</p> -<p><a class="p_ident" id="p-DoOVtob5DG" href="#p-DoOVtob5DG" tabindex="-1" role="presentation"></a>La propiedad <code>ranges</code> 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).</p> +<p><a class="p_ident" id="p-DoOVtob5DG" href="#p-DoOVtob5DG" tabindex="-1" role="presentation"></a>La propiedad <code>ranges</code> 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).</p> <h2><a class="h_ident" id="h-MtcnrMCox6" href="#h-MtcnrMCox6" tabindex="-1" role="presentation"></a>Filtrado de arrays</h2> -<p><a class="p_ident" id="p-HjjeDvBqja" href="#p-HjjeDvBqja" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-lJs1/KLSN1" href="#p-lJs1/KLSN1" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-POEf7pMCk0" href="#c-POEf7pMCk0" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">filter</span>(<span class="tok-definition">array</span>, <span class="tok-definition">test</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">passed</span> = []; - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">element</span> <span class="tok-keyword">of</span> array) { - <span class="tok-keyword">if</span> (test(element)) { - passed.push(element); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-lyHwMa/zZ6" href="#c-lyHwMa/zZ6" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">filtrar</span>(<span class="tok-definition">array</span>, <span class="tok-definition">comprobación</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">pasada</span> = []; + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">elemento</span> <span class="tok-keyword">of</span> array) { + <span class="tok-keyword">if</span> (comprobación(elemento)) { + pasada.push(elemento); } } - <span class="tok-keyword">return</span> passed; + <span class="tok-keyword">return</span> pasada; } -console.log(filter(SCRIPTS, <span class="tok-definition">script</span> => script.living)); +console.log(filtrar(SCRIPTS, <span class="tok-definition">sistema</span> => sistema.living)); <span class="tok-comment">// → [{name: "Adlam", …}, …]</span></pre> -<p><a class="p_ident" id="p-vqNV3Gw5Wz" href="#p-vqNV3Gw5Wz" tabindex="-1" role="presentation"></a>La función utiliza el argumento llamado <code>test</code>, un valor de función, para llenar un “vacío” en la computación, el proceso de decidir qué elementos recopilar.</p> +<p><a class="p_ident" id="p-vqNV3Gw5Wz" href="#p-vqNV3Gw5Wz" tabindex="-1" role="presentation"></a>La función utiliza el argumento llamado <code>comprobación</code>, un valor de función, para llenar un “hueco” en el procedimiento de filtrado: el proceso de decidir qué elementos recopilar.</p> -<p><a class="p_ident" id="p-rDTRpRs2b7" href="#p-rDTRpRs2b7" tabindex="-1" role="presentation"></a>Observa cómo la función <code>filter</code>, 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 <em>pura</em>. No modifica la matriz que se le pasa.</p> +<p><a class="p_ident" id="p-rDTRpRs2b7" href="#p-rDTRpRs2b7" tabindex="-1" role="presentation"></a>Observa cómo la función <code>filtrar</code>, 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 <em>pura</em>. No modifica la matriz que se le pasa.</p> -<p><a class="p_ident" id="p-mflQRYwRQo" href="#p-mflQRYwRQo" tabindex="-1" role="presentation"></a>Al igual que <code>forEach</code>, <code>filter</code> 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:</p> +<p><a class="p_ident" id="p-mflQRYwRQo" href="#p-mflQRYwRQo" tabindex="-1" role="presentation"></a>Al igual que con <code>forEach</code>, hay un método estándar para <code>filtrar</code> en los arrays, el método <code>filter</code>. 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:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-x8e0PmGGB1" href="#c-x8e0PmGGB1" tabindex="-1" role="presentation"></a>console.log(SCRIPTS.filter(<span class="tok-definition">s</span> => s.direction == <span class="tok-string">"ttb"</span>)); <span class="tok-comment">// → [{name: "Mongolian", …}, …]</span></pre> <h2 id="map"><a class="h_ident" id="h-S3rmMrmWka" href="#h-S3rmMrmWka" tabindex="-1" role="presentation"></a>Transformación con map</h2> -<p><a class="p_ident" id="p-ydEMpih2aw" href="#p-ydEMpih2aw" tabindex="-1" role="presentation"></a>Digamos que tenemos una matriz de objetos que representan guiones, producida al filtrar la matriz <code>SCRIPTS</code> de alguna manera. Queremos una matriz de nombres en su lugar, que es más fácil de inspeccionar.</p> +<p><a class="p_ident" id="p-ydEMpih2aw" href="#p-ydEMpih2aw" tabindex="-1" role="presentation"></a>Digamos que tenemos un array de objetos que representan sistemas de escritura, producido al filtrar el array <code>SCRIPTS</code> de alguna manera. En su lugar, queremos un array de nombres, que es más fácil de inspeccionar.</p> -<p><a class="p_ident" id="p-eEu8IlZwFm" href="#p-eEu8IlZwFm" tabindex="-1" role="presentation"></a>El método <code>map</code> 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 <em>mapeado</em> a una nueva forma por la función:</p> +<p><a class="p_ident" id="p-eEu8IlZwFm" href="#p-eEu8IlZwFm" tabindex="-1" role="presentation"></a>El método <code>map</code> 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 <em>mapeado</em> a una nueva forma por la función:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-03caQcQElo" href="#c-03caQcQElo" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">map</span>(<span class="tok-definition">array</span>, <span class="tok-definition">transform</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">mapped</span> = []; - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">element</span> <span class="tok-keyword">of</span> array) { - mapped.push(transform(element)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-HNoE2R2koX" href="#c-HNoE2R2koX" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">mapear</span>(<span class="tok-definition">array</span>, <span class="tok-definition">transformación</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">mapeados</span> = []; + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">elemento</span> <span class="tok-keyword">of</span> array) { + mapeados.push(transformación(elemento)); } - <span class="tok-keyword">return</span> mapped; + <span class="tok-keyword">return</span> mapeados; } <span class="tok-keyword">let</span> <span class="tok-definition">rtlScripts</span> = SCRIPTS.filter(<span class="tok-definition">s</span> => s.direction == <span class="tok-string">"rtl"</span>); -console.log(map(rtlScripts, <span class="tok-definition">s</span> => s.name)); +console.log(mapear(rtlScripts, <span class="tok-definition">s</span> => s.name)); <span class="tok-comment">// → ["Adlam", "Arabic", "Imperial Aramaic", …]</span></pre> -<p><a class="p_ident" id="p-CSoEP7cmmy" href="#p-CSoEP7cmmy" tabindex="-1" role="presentation"></a>Al igual que <code>forEach</code> y <code>filter</code>, <code>map</code> es un método de matriz estándar.</p> +<p><a class="p_ident" id="p-0CRJOsl5nV" href="#p-0CRJOsl5nV" tabindex="-1" role="presentation"></a>Al igual que <code>forEach</code> y <code>filter</code>, hay un método estándar para <code>mapear</code> en los arrays, el método <code>map</code>.</p> -<h2><a class="h_ident" id="h-PT5vUNRTlZ" href="#h-PT5vUNRTlZ" tabindex="-1" role="presentation"></a>Resumen con reduce</h2> +<h2><a class="h_ident" id="h-ddK/7bb9/9" href="#h-ddK/7bb9/9" tabindex="-1" role="presentation"></a>Resumiendo con reduce</h2> -<p><a class="p_ident" id="p-0sSdjIXjkE" href="#p-0sSdjIXjkE" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-0sSdjIXjkE" href="#p-0sSdjIXjkE" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-Nbb72CteP6" href="#p-Nbb72CteP6" tabindex="-1" role="presentation"></a>La operación de orden superior que representa este patrón se llama <em>reduce</em> (a veces también llamada <em>fold</em>). 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.</p> +<p><a class="p_ident" id="p-EiDL8H6ZNm" href="#p-EiDL8H6ZNm" tabindex="-1" role="presentation"></a>La operación de orden superior que representa esta idea se llama <em>reduce</em> (a veces también llamada <em>fold</em>). 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.</p> -<p><a class="p_ident" id="p-2kupqkikqu" href="#p-2kupqkikqu" tabindex="-1" role="presentation"></a>Los parámetros de <code>reduce</code> son, además del array, una función de combinación y un valor inicial. Esta función es un poco menos directa que <code>filter</code> y <code>map</code>, así que obsérvala detenidamente:</p> +<p><a class="p_ident" id="p-ON8Z05D/At" href="#p-ON8Z05D/At" tabindex="-1" role="presentation"></a>Los parámetros de <code>reduce</code> son, además del array, una función de combinación y un valor inicial. Esta función es un poco menos directa que <code>filter</code> y <code>map</code>, así que observa detenidamente:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-k5GDHjqpSc" href="#c-k5GDHjqpSc" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">reduce</span>(<span class="tok-definition">array</span>, <span class="tok-definition">combine</span>, <span class="tok-definition">start</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">current</span> = start; - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">element</span> <span class="tok-keyword">of</span> array) { - current = combine(current, element); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-MPEcux0ztP" href="#c-MPEcux0ztP" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">reducir</span>(<span class="tok-definition">array</span>, <span class="tok-definition">combinación</span>, <span class="tok-definition">principio</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">actual</span> = inicio; + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">elemento</span> <span class="tok-keyword">of</span> array) { + actual = combinación(actual, elemento); } - <span class="tok-keyword">return</span> current; + <span class="tok-keyword">return</span> actual; } -console.log(reduce([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">4</span>], (<span class="tok-definition">a</span>, <span class="tok-definition">b</span>) => a + b, <span class="tok-number">0</span>)); +console.log(reducir([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">4</span>], (<span class="tok-definition">a</span>, <span class="tok-definition">b</span>) => a + b, <span class="tok-number">0</span>)); <span class="tok-comment">// → 10</span></pre> -<p><a class="p_ident" id="p-tSi+CnrI8e" href="#p-tSi+CnrI8e" tabindex="-1" role="presentation"></a>El método estándar de arrays <code>reduce</code>, que por supuesto corresponde a esta función, tiene una conveniencia adicional. Si tu array contiene al menos un elemento, puedes omitir el argumento <code>start</code>. El método tomará el primer elemento del array como su valor inicial y comenzará a reducir en el segundo elemento.</p> +<p><a class="p_ident" id="p-tSi+CnrI8e" href="#p-tSi+CnrI8e" tabindex="-1" role="presentation"></a>El método estándar de arrays, <code>reduce</code> —que por supuesto corresponde a esta función— tiene una ventaja adicional. Si tu array contiene al menos un elemento, puedes omitir el argumento <code>start</code>. El método tomará el primer elemento del array como su valor inicial y comenzará a reducir en el segundo elemento.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Dxn7muuMkk" href="#c-Dxn7muuMkk" tabindex="-1" role="presentation"></a>console.log([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">4</span>].reduce((<span class="tok-definition">a</span>, <span class="tok-definition">b</span>) => a + b)); <span class="tok-comment">// → 10</span></pre> -<p><a class="p_ident" id="p-aq58LUBIxu" href="#p-aq58LUBIxu" tabindex="-1" role="presentation"></a>Para usar <code>reduce</code> (dos veces) y encontrar el script con más caracteres, podemos escribir algo así:</p> +<p><a class="p_ident" id="p-aq58LUBIxu" href="#p-aq58LUBIxu" tabindex="-1" role="presentation"></a>Para usar <code>reduce</code> (dos veces) y encontrar el sistema de escritura con más caracteres, podemos escribir algo así:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-x76Ukt5X+H" href="#c-x76Ukt5X+H" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">characterCount</span>(<span class="tok-definition">script</span>) { - <span class="tok-keyword">return</span> script.ranges.reduce((<span class="tok-definition">count</span>, [<span class="tok-definition">from</span>, <span class="tok-definition">to</span>]) => { - <span class="tok-keyword">return</span> count + (to - from); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9muvALBVGr" href="#c-9muvALBVGr" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">contarCaracteres</span>(<span class="tok-definition">sistema</span>) { + <span class="tok-keyword">return</span> sistema.ranges.reduce((<span class="tok-definition">contador</span>, [<span class="tok-definition">desde</span>, <span class="tok-definition">hasta</span>]) => { + <span class="tok-keyword">return</span> contador + (hasta - desde); }, <span class="tok-number">0</span>); } console.log(SCRIPTS.reduce((<span class="tok-definition">a</span>, <span class="tok-definition">b</span>) => { - <span class="tok-keyword">return</span> characterCount(a) < characterCount(b) ? b : a; + <span class="tok-keyword">return</span> contarCaracteres(a) < contarCaracteres(b) ? b : a; })); <span class="tok-comment">// → {name: "Han", …}</span></pre> -<p><a class="p_ident" id="p-h4Jk417+fU" href="#p-h4Jk417+fU" tabindex="-1" role="presentation"></a>La función <code>characterCount</code> 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 <code>reduce</code> luego utiliza esto para encontrar el script más grande comparando repetidamente dos scripts y devolviendo el más grande.</p> +<p><a class="p_ident" id="p-h4Jk417+fU" href="#p-h4Jk417+fU" tabindex="-1" role="presentation"></a>La función <code>contarCaracteres</code> 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 <code>reduce</code> luego utiliza esto para encontrar el sistema de escritura más grande comparando repetidamente dos sistemas y devolviendo el más grande.</p> -<p><a class="p_ident" id="p-g8tYIN4Bkz" href="#p-g8tYIN4Bkz" tabindex="-1" role="presentation"></a>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 <em>unificación Han</em> y todavía molesta a algunas personas.</p> +<p><a class="p_ident" id="p-lRRvnFoqP7" href="#p-lRRvnFoqP7" tabindex="-1" role="presentation"></a>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 <em>unificación Han</em> y aún hay gente que no está muy contenta con ella.</p> <h2><a class="h_ident" id="h-bzPoX82c4F" href="#h-bzPoX82c4F" tabindex="-1" role="presentation"></a>Composabilidad</h2> -<p><a class="p_ident" id="p-gP2bptAN2f" href="#p-gP2bptAN2f" tabindex="-1" role="presentation"></a>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:</p> +<p><a class="p_ident" id="p-077XGN/TLg" href="#p-077XGN/TLg" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-1FmIKHNB24" href="#c-1FmIKHNB24" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">biggest</span> = <span class="tok-keyword">null</span>; -<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">script</span> <span class="tok-keyword">of</span> SCRIPTS) { - <span class="tok-keyword">if</span> (biggest == <span class="tok-keyword">null</span> || - characterCount(biggest) < characterCount(script)) { - biggest = script; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-vfhIpzue2u" href="#c-vfhIpzue2u" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">másGrande</span> = <span class="tok-keyword">null</span>; +<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">sistema</span> <span class="tok-keyword">of</span> SCRIPTS) { + <span class="tok-keyword">if</span> (másGrande == <span class="tok-keyword">null</span> || + contarCaracteres(másGrande) < contarCaracteres(sistema)) { + másGrande = sistema; } } -console.log(biggest); +console.log(másGrande); <span class="tok-comment">// → {name: "Han", …}</span></pre> -<p><a class="p_ident" id="p-EtVmv7i/iI" href="#p-EtVmv7i/iI" tabindex="-1" role="presentation"></a>Hay algunas variables adicionales y el programa tiene cuatro líneas más, pero sigue siendo muy legible.</p> +<p><a class="p_ident" id="p-EtVmv7i/iI" href="#p-EtVmv7i/iI" tabindex="-1" role="presentation"></a>Hay algunas variables más y el programa tiene cuatro líneas más, pero sigue siendo muy legible.</p> -<p id="average_function"><a class="p_ident" id="p-/CzAZCG1Ww" href="#p-/CzAZCG1Ww" tabindex="-1" role="presentation"></a>Las abstracciones proporcionadas por estas funciones brillan realmente cuando necesitas <em>componer</em> 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:</p> +<p id="average_function"><a class="p_ident" id="p-/CzAZCG1Ww" href="#p-/CzAZCG1Ww" tabindex="-1" role="presentation"></a>Las abstracciones proporcionadas por estas funciones brillan realmente cuando necesitas <em>componer</em> 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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-rumPqbzokK" href="#c-rumPqbzokK" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">average</span>(<span class="tok-definition">array</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-DlxRx+dU7R" href="#c-DlxRx+dU7R" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">promedio</span>(<span class="tok-definition">array</span>) { <span class="tok-keyword">return</span> array.reduce((<span class="tok-definition">a</span>, <span class="tok-definition">b</span>) => a + b) / array.length; } -console.log(Math.round(average( +console.log(Math.round(promedio( SCRIPTS.filter(<span class="tok-definition">s</span> => s.living).map(<span class="tok-definition">s</span> => s.year)))); <span class="tok-comment">// → 1165</span> -console.log(Math.round(average( +console.log(Math.round(promedio( SCRIPTS.filter(<span class="tok-definition">s</span> => !s.living).map(<span class="tok-definition">s</span> => s.year)))); <span class="tok-comment">// → 204</span></pre> -<p><a class="p_ident" id="p-HYrvIekBW0" href="#p-HYrvIekBW0" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-HYrvIekBW0" href="#p-HYrvIekBW0" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-Z4jAr6ljSn" href="#p-Z4jAr6ljSn" tabindex="-1" role="presentation"></a>Definitivamente también podrías escribir este cálculo como un único loop grande:</p> +<p><a class="p_ident" id="p-KGXXlw/EvR" href="#p-KGXXlw/EvR" tabindex="-1" role="presentation"></a>Definitivamente también podrías escribir este cálculo como un único bucle grande:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-noyYOD0Kiy" href="#c-noyYOD0Kiy" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">total</span> = <span class="tok-number">0</span>, <span class="tok-definition">count</span> = <span class="tok-number">0</span>; -<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">script</span> <span class="tok-keyword">of</span> SCRIPTS) { - <span class="tok-keyword">if</span> (script.living) { - total += script.year; - count += <span class="tok-number">1</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-rI3MU9TnSK" href="#c-rI3MU9TnSK" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">total</span> = <span class="tok-number">0</span>, <span class="tok-definition">contador</span> = <span class="tok-number">0</span>; +<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">sistema</span> <span class="tok-keyword">of</span> SCRIPTS) { + <span class="tok-keyword">if</span> (sistema.living) { + total += sistema.year; + contador += <span class="tok-number">1</span>; } } -console.log(Math.round(total / count)); +console.log(Math.round(total / contador)); <span class="tok-comment">// → 1165</span></pre> -<p><a class="p_ident" id="p-z1zZ1R3oEN" href="#p-z1zZ1R3oEN" tabindex="-1" role="presentation"></a>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 <code>average</code> en una función separada.</p> +<p><a class="p_ident" id="p-z1zZ1R3oEN" href="#p-z1zZ1R3oEN" tabindex="-1" role="presentation"></a>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 <code>promedio</code> en una función separada.</p> -<p><a class="p_ident" id="p-JaB+c9Bjg2" href="#p-JaB+c9Bjg2" tabindex="-1" role="presentation"></a>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 <code>filter</code> y <code>map</code>, 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.</p> +<p><a class="p_ident" id="p-PY6JQfUdBq" href="#p-PY6JQfUdBq" tabindex="-1" role="presentation"></a>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 <code>filter</code> y <code>map</code>, 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.</p> <h2><a class="h_ident" id="h-XPdB1c6gdD" href="#h-XPdB1c6gdD" tabindex="-1" role="presentation"></a>Cadenas y códigos de caracteres</h2> -<p><a class="p_ident" id="p-MXjFCWbW9t" href="#p-MXjFCWbW9t" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-MXjFCWbW9t" href="#p-MXjFCWbW9t" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-GatVMstQav" href="#p-GatVMstQav" tabindex="-1" role="presentation"></a>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):</p> +<p><a class="p_ident" id="p-GatVMstQav" href="#p-GatVMstQav" tabindex="-1" role="presentation"></a>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):</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Q8918ecfHn" href="#c-Q8918ecfHn" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">characterScript</span>(<span class="tok-definition">code</span>) { - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">script</span> <span class="tok-keyword">of</span> SCRIPTS) { - <span class="tok-keyword">if</span> (script.ranges.some(([<span class="tok-definition">from</span>, <span class="tok-definition">to</span>]) => { - <span class="tok-keyword">return</span> code >= from && code < to; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-R9pXaLnTza" href="#c-R9pXaLnTza" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">sistemaCaracteres</span>(<span class="tok-definition">código</span>) { + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">sistema</span> <span class="tok-keyword">of</span> SCRIPTS) { + <span class="tok-keyword">if</span> (sistema.ranges.some(([<span class="tok-definition">desde</span>, <span class="tok-definition">hasta</span>]) => { + <span class="tok-keyword">return</span> código >= desde && código < hasta; })) { - <span class="tok-keyword">return</span> script; + <span class="tok-keyword">return</span> sistema; } } <span class="tok-keyword">return</span> <span class="tok-keyword">null</span>; } -console.log(characterScript(<span class="tok-number">121</span>)); +console.log(sistemaCaracteres(<span class="tok-number">121</span>)); <span class="tok-comment">// → {name: "Latin", …}</span></pre> -<p><a class="p_ident" id="p-hJDL1Q3G7R" href="#p-hJDL1Q3G7R" tabindex="-1" role="presentation"></a>El método <code>some</code> 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.</p> +<p><a class="p_ident" id="p-hJDL1Q3G7R" href="#p-hJDL1Q3G7R" tabindex="-1" role="presentation"></a>El método <code>some</code> 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.</p> <p id="code_units"><a class="p_ident" id="p-xwqTjYNDg/" href="#p-xwqTjYNDg/" tabindex="-1" role="presentation"></a>Pero, ¿cómo obtenemos los códigos de caracteres en una cadena?</p> -<p><a class="p_ident" id="p-d3EBXodI+B" href="#p-d3EBXodI+B" tabindex="-1" role="presentation"></a>En <a href="01_values.html">Chapter 1</a> mencioné que las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estos se llaman <em>unidades de código</em>. 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.</p> +<p><a class="p_ident" id="p-QaWyxrrWcl" href="#p-QaWyxrrWcl" tabindex="-1" role="presentation"></a>En el <a href="01_values.html">Capítulo 1</a> mencioné que las cadenas de JavaScript están codificadas como una secuencia de números de 16 bits. Estos se llaman <em>unidades de código</em>. 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.</p> -<p><a class="p_ident" id="p-MP7gPIVpo8" href="#p-MP7gPIVpo8" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-giEN4jUd0U" href="#p-giEN4jUd0U" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-cIimdGgzlf" href="#p-cIimdGgzlf" tabindex="-1" role="presentation"></a>Lamentablemente, las operaciones obvias en las cadenas de JavaScript, como obtener su longitud a través de la propiedad <code>length</code> y acceder a su contenido usando corchetes cuadrados, tratan solo con unidades de código.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-HNlpl48Xvg" href="#c-HNlpl48Xvg" tabindex="-1" role="presentation"></a><span class="tok-comment">// Dos caracteres emoji, caballo y zapato</span> -<span class="tok-keyword">let</span> <span class="tok-definition">horseShoe</span> = <span class="tok-string">"🐴👟"</span>; -console.log(horseShoe.length); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-y07dTF1oUr" href="#c-y07dTF1oUr" tabindex="-1" role="presentation"></a><span class="tok-comment">// Dos caracteres emoji, caballo y zapato</span> +<span class="tok-keyword">let</span> <span class="tok-definition">caballoZapato</span> = <span class="tok-string">"🐴👟"</span>; +console.log(caballoZapato.length); <span class="tok-comment">// → 4</span> -console.log(horseShoe[<span class="tok-number">0</span>]); +console.log(caballoZapato[<span class="tok-number">0</span>]); <span class="tok-comment">// → (Mitad de carácter inválida)</span> -console.log(horseShoe.charCodeAt(<span class="tok-number">0</span>)); -<span class="tok-comment">// → 55357 (Código de la mitad de carácter)</span> -console.log(horseShoe.codePointAt(<span class="tok-number">0</span>)); +console.log(caballoZapato.charCodeAt(<span class="tok-number">0</span>)); +<span class="tok-comment">// → 55357 (Código de la mitad de caracter)</span> +console.log(caballoZapato.codePointAt(<span class="tok-number">0</span>)); <span class="tok-comment">// → 128052 (Código real para el emoji de caballo)</span></pre> <p><a class="p_ident" id="p-BS9MJRj/qW" href="#p-BS9MJRj/qW" tabindex="-1" role="presentation"></a>El método <code>charCodeAt</code> de JavaScript te da una unidad de código, no un código de carácter completo. El método <code>codePointAt</code>, 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 <code>codePointAt</code> 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.</p> <p><a class="p_ident" id="p-+k9Io/S3re" href="#p-+k9Io/S3re" tabindex="-1" role="presentation"></a>En el <a href="datos#bucle_for_of">capítulo anterior</a>, mencioné que un bucle <code>for</code>/<code>of</code> también se puede usar en cadenas. Al igual que <code>codePointAt</code>, 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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9QIfA1qjtG" href="#c-9QIfA1qjtG" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">roseDragon</span> = <span class="tok-string">"🌹🐉"</span>; -<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">char</span> <span class="tok-keyword">of</span> roseDragon) { - console.log(char); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5IPW3Z/swv" href="#c-5IPW3Z/swv" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">rosaDragón</span> = <span class="tok-string">"🌹🐉"</span>; +<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">carácter</span> <span class="tok-keyword">of</span> rosaDragón) { + console.log(caracter); } <span class="tok-comment">// → 🌹</span> <span class="tok-comment">// → 🐉</span></pre> @@ -359,57 +372,57 @@ <h2><a class="h_ident" id="h-XPdB1c6gdD" href="#h-XPdB1c6gdD" tabindex="-1" role <h2><a class="h_ident" id="h-6v14c/pbJh" href="#h-6v14c/pbJh" tabindex="-1" role="presentation"></a>Reconociendo texto</h2> -<p><a class="p_ident" id="p-US1jun7br4" href="#p-US1jun7br4" tabindex="-1" role="presentation"></a>Tenemos una función <code>characterScript</code> 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:</p> +<p><a class="p_ident" id="p-US1jun7br4" href="#p-US1jun7br4" tabindex="-1" role="presentation"></a>Tenemos una función <code>sistemaCaracteres</code> 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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-b8ZEeu/LH5" href="#c-b8ZEeu/LH5" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">countBy</span>(<span class="tok-definition">items</span>, <span class="tok-definition">groupName</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">counts</span> = []; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-TEPk63dZNo" href="#c-TEPk63dZNo" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">contarPor</span>(<span class="tok-definition">items</span>, <span class="tok-definition">nombreGrupo</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">recuentos</span> = []; <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">item</span> <span class="tok-keyword">of</span> items) { - <span class="tok-keyword">let</span> <span class="tok-definition">name</span> = groupName(item); - <span class="tok-keyword">let</span> <span class="tok-definition">known</span> = counts.find(<span class="tok-definition">c</span> => c.name == name); - <span class="tok-keyword">if</span> (!known) { - counts.push({<span class="tok-definition">name</span>, <span class="tok-definition">count</span>: <span class="tok-number">1</span>}); + <span class="tok-keyword">let</span> <span class="tok-definition">nombre</span> = nombreGrupo(item); + <span class="tok-keyword">let</span> <span class="tok-definition">conocido</span> = recuentos.find(<span class="tok-definition">c</span> => c.nombre == nombre); + <span class="tok-keyword">if</span> (!conocido) { + recuentos.push({<span class="tok-definition">nombre</span>, <span class="tok-definition">recuento</span>: <span class="tok-number">1</span>}); } <span class="tok-keyword">else</span> { - known.count++; + conocido.recuento++; } } - <span class="tok-keyword">return</span> counts; + <span class="tok-keyword">return</span> recuentos; } -console.log(countBy([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">4</span>, <span class="tok-number">5</span>], <span class="tok-definition">n</span> => n > <span class="tok-number">2</span>)); -<span class="tok-comment">// → [{name: false, count: 2}, {name: true, count: 3}]</span></pre> +console.log(contarPor([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>, <span class="tok-number">4</span>, <span class="tok-number">5</span>], <span class="tok-definition">n</span> => n > <span class="tok-number">2</span>)); +<span class="tok-comment">// → [{nombre: false, recuento: 2}, {nombre: true, recuento: 3}]</span></pre> -<p><a class="p_ident" id="p-BkGEda/6+p" href="#p-BkGEda/6+p" tabindex="-1" role="presentation"></a>La función <code>countBy</code> espera una colección (cualquier cosa por la que podamos iterar con <code>for</code>/<code>of</code>) 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.</p> +<p><a class="p_ident" id="p-BkGEda/6+p" href="#p-BkGEda/6+p" tabindex="-1" role="presentation"></a>La función <code>contarPor</code> espera una colección (cualquier cosa por la que podamos iterar con <code>for</code>/<code>of</code>) 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.</p> <p><a class="p_ident" id="p-3D6sEDfZSV" href="#p-3D6sEDfZSV" tabindex="-1" role="presentation"></a>Utiliza otro método de array, <code>find</code>, que recorre los elementos en el array y devuelve el primero para el cual una función devuelve true. Devuelve <code>undefined</code> cuando no se encuentra dicho elemento.</p> -<p><a class="p_ident" id="p-cOB6KMMgRg" href="#p-cOB6KMMgRg" tabindex="-1" role="presentation"></a>Usando <code>countBy</code>, podemos escribir la función que nos dice qué scripts se utilizan en un fragmento de texto:</p> +<p><a class="p_ident" id="p-cOB6KMMgRg" href="#p-cOB6KMMgRg" tabindex="-1" role="presentation"></a>Usando <code>contarPor</code>, podemos escribir la función que nos dice qué sistemas de escritura se utilizan en un fragmento de texto:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-i0Qwg5s1EB" href="#c-i0Qwg5s1EB" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">textScripts</span>(<span class="tok-definition">text</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">scripts</span> = countBy(text, <span class="tok-definition">char</span> => { - <span class="tok-keyword">let</span> <span class="tok-definition">script</span> = characterScript(char.codePointAt(<span class="tok-number">0</span>)); - <span class="tok-keyword">return</span> script ? script.name : <span class="tok-string">"ninguno"</span>; - }).filter(({name}) => name != <span class="tok-string">"ninguno"</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-IBybQjGQ9Z" href="#c-IBybQjGQ9Z" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">sistemasTexto</span>(<span class="tok-definition">texto</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">sistemas</span> = contarPor(texto, <span class="tok-definition">carácter</span> => { + <span class="tok-keyword">let</span> <span class="tok-definition">sistema</span> = sistemaCaracteres(carácter.codePointAt(<span class="tok-number">0</span>)); + <span class="tok-keyword">return</span> sistema ? sistema.name : <span class="tok-string">"ninguno"</span>; + }).filter(({nombre}) => nombre != <span class="tok-string">"ninguno"</span>); - <span class="tok-keyword">let</span> <span class="tok-definition">total</span> = scripts.reduce((<span class="tok-definition">n</span>, {count}) => n + count, <span class="tok-number">0</span>); - <span class="tok-keyword">if</span> (total == <span class="tok-number">0</span>) <span class="tok-keyword">return</span> <span class="tok-string">"No se encontraron scripts"</span>; + <span class="tok-keyword">let</span> <span class="tok-definition">total</span> = sistemas.reduce((<span class="tok-definition">n</span>, {recuento}) => n + recuento, <span class="tok-number">0</span>); + <span class="tok-keyword">if</span> (total == <span class="tok-number">0</span>) <span class="tok-keyword">return</span> <span class="tok-string">"No se encontraron sistemas"</span>; - <span class="tok-keyword">return</span> scripts.map(({name, count}) => { - <span class="tok-keyword">return</span> <span class="tok-string2">`</span>${Math.round(count * <span class="tok-number">100</span> / total)}<span class="tok-string2">% </span>${name}<span class="tok-string2">`</span>; + <span class="tok-keyword">return</span> sistemas.map(({nombre, recuento}) => { + <span class="tok-keyword">return</span> <span class="tok-string2">`</span>${Math.round(recuento * <span class="tok-number">100</span> / total)}<span class="tok-string2">% </span>${nombre}<span class="tok-string2">`</span>; }).join(<span class="tok-string">", "</span>); } -console.log(textScripts(<span class="tok-string">'英国的狗说"woof", 俄罗斯的狗说"тяв"'</span>)); +console.log(sistemasTexto(<span class="tok-string">'英国的狗说"woof", 俄罗斯的狗说"тяв"'</span>)); <span class="tok-comment">// → 61% Han, 22% Latin, 17% Cyrillic</span></pre> -<p><a class="p_ident" id="p-6ZNN+7KNVP" href="#p-6ZNN+7KNVP" tabindex="-1" role="presentation"></a>La función primero cuenta los caracteres por nombre, usando <code>characterScript</code> para asignarles un nombre y retrocediendo a la cadena <code>"ninguno"</code> para los caracteres que no forman parte de ningún script. La llamada a <code>filter</code> elimina la entrada de <code>"ninguno"</code> del array resultante, ya que no nos interesan esos caracteres.</p> +<p><a class="p_ident" id="p-6ZNN+7KNVP" href="#p-6ZNN+7KNVP" tabindex="-1" role="presentation"></a>La función primero recoge los nombres de los sistemas de escritura de los caracteres en el texto usando <code>sistemaCaracteres</code> para asignarles un nombre y recurriendo a la cadena <code>"ninguno"</code> para los caracteres que no forman parte de ningún sistema. La llamada a <code>filter</code> elimina la entrada correspondiente a <code>"ninguno"</code> del array resultante, ya que no nos interesan esos caracteres.</p> -<p><a class="p_ident" id="p-z0FN1vM2Io" href="#p-z0FN1vM2Io" tabindex="-1" role="presentation"></a>Para poder calcular porcentajes, primero necesitamos el número total de caracteres que pertenecen a un script, lo cual podemos calcular con <code>reduce</code>. 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 <code>map</code> y luego las combina con <code>join</code>.</p> +<p><a class="p_ident" id="p-z0FN1vM2Io" href="#p-z0FN1vM2Io" tabindex="-1" role="presentation"></a>Para poder calcular porcentajes, primero necesitamos el número total de caracteres que pertenecen a un sistema dado, lo cual podemos calcular con <code>reduce</code>. 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 <code>map</code> y luego las combina con <code>join</code>.</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-ua0YBHiMTm" href="#p-ua0YBHiMTm" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-ua0YBHiMTm" href="#p-ua0YBHiMTm" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-OvDSlYatKQ" href="#p-OvDSlYatKQ" tabindex="-1" role="presentation"></a>Los arrays proporcionan diversos métodos de orden superior útiles. Puedes usar <code>forEach</code> para recorrer los elementos de un array. El método <code>filter</code> 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 <code>map</code>. Puedes usar <code>reduce</code> para combinar todos los elementos de un array en un único valor. El método <code>some</code> comprueba si algún elemento coincide con una función de predicado dada, mientras que <code>find</code> encuentra el primer elemento que coincide con un predicado.</p> +<p><a class="p_ident" id="p-AxW84COxvD" href="#p-AxW84COxvD" tabindex="-1" role="presentation"></a>Los arrays proporcionan diversos métodos de orden superior muy útiles. Puedes usar <code>forEach</code> para recorrer los elementos de un array. El método <code>filter</code> 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 <code>map</code>. Puedes usar <code>reduce</code> para combinar todos los elementos de un array en un único valor. El método <code>some</code> comprueba si algún elemento satisface una función de predicado dada, mientras que <code>find</code> encuentra el primer elemento que satisface un predicado.</p> <h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2> @@ -423,9 +436,9 @@ <h3><a class="i_ident" id="i-NYYN2FxNVG" href="#i-NYYN2FxNVG" tabindex="-1" role <h3><a class="i_ident" id="i-cBnCXoHx6D" href="#i-cBnCXoHx6D" tabindex="-1" role="presentation"></a>Tu propio bucle</h3> -<p><a class="p_ident" id="p-eTd6UCJrnn" href="#p-eTd6UCJrnn" tabindex="-1" role="presentation"></a>Escribe una función de orden superior <code>loop</code> que proporcione algo similar a una declaración <code>for</code> 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.</p> +<p><a class="p_ident" id="p-eTd6UCJrnn" href="#p-eTd6UCJrnn" tabindex="-1" role="presentation"></a>Escribe una función de orden superior <code>loop</code> que proporcione algo similar a una declaración de bucle <code>for</code>. 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.</p> -<p><a class="p_ident" id="p-ZjRsh6UWeD" href="#p-ZjRsh6UWeD" tabindex="-1" role="presentation"></a>Al definir la función, puedes usar un bucle regular para hacer el bucle real.</p> +<p><a class="p_ident" id="p-ZjRsh6UWeD" href="#p-ZjRsh6UWeD" tabindex="-1" role="presentation"></a>Al definir la función, puedes usar un bucle normal para hacer el bucle real.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Fv1rc97GEM" href="#c-Fv1rc97GEM" tabindex="-1" role="presentation"></a><span class="tok-comment">// Your code here.</span> @@ -436,7 +449,7 @@ <h3><a class="i_ident" id="i-cBnCXoHx6D" href="#i-cBnCXoHx6D" tabindex="-1" role <h3><a class="i_ident" id="i-SmbRSAd5GA" href="#i-SmbRSAd5GA" tabindex="-1" role="presentation"></a>Everything</h3> -<p><a class="p_ident" id="p-4Pg9vLwvKg" href="#p-4Pg9vLwvKg" tabindex="-1" role="presentation"></a>Los arrays también tienen un método <code>every</code> análogo al método <code>some</code>. Este método devuelve <code>true</code> cuando la función dada devuelve <code>true</code> para <em>cada</em> elemento en el array. En cierto modo, <code>some</code> es una versión del operador <code>||</code> que actúa en arrays, y <code>every</code> es como el operador <code>&&</code>.</p> +<p><a class="p_ident" id="p-4Pg9vLwvKg" href="#p-4Pg9vLwvKg" tabindex="-1" role="presentation"></a>Los arrays también tienen un método <code>every</code> análogo al método <code>some</code>. Este método devuelve <code>true</code> cuando la función dada devuelve <code>true</code> para <em>todo</em> elemento en el array. En cierto modo, <code>some</code> es una versión del operador <code>||</code> que actúa en arrays, y <code>every</code> es como el operador <code>&&</code>.</p> <p><a class="p_ident" id="p-pg6rAnmtg7" href="#p-pg6rAnmtg7" tabindex="-1" role="presentation"></a>Implementa <code>every</code> 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 <code>some</code>.</p> @@ -453,15 +466,15 @@ <h3><a class="i_ident" id="i-SmbRSAd5GA" href="#i-SmbRSAd5GA" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-3C1WcJRJ3U" href="#p-3C1WcJRJ3U" tabindex="-1" role="presentation"></a>Como el operador <code>&&</code>, el método <code>every</code> 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 <code>break</code> 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.</p> +<p><a class="p_ident" id="p-2enp9r8UmW" href="#p-2enp9r8UmW" tabindex="-1" role="presentation"></a>Al igual que el operador <code>&&</code>, el método <code>every</code> 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 <code>break</code> o <code>return</code>— 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.</p> -<p><a class="p_ident" id="p-Coa0zWQNO0" href="#p-Coa0zWQNO0" tabindex="-1" role="presentation"></a>Para construir <code>every</code> sobre <code>some</code>, podemos aplicar <em>leyes de De Morgan</em>, que establecen que <code>a && b</code> es igual a <code>!(!a || !b)</code>. 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.</p> +<p><a class="p_ident" id="p-Coa0zWQNO0" href="#p-Coa0zWQNO0" tabindex="-1" role="presentation"></a>Para construir <code>every</code> sobre <code>some</code>, podemos aplicar <em>leyes de De Morgan</em>, que establecen que <code>a && b</code> tiene el mismo valor que <code>!(!a || !b)</code>. 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.</p> </div></details> <h3><a class="i_ident" id="i-kUUDTvgJ6V" href="#i-kUUDTvgJ6V" tabindex="-1" role="presentation"></a>Dirección de escritura dominante</h3> -<p><a class="p_ident" id="p-wNkAaSiwZs" href="#p-wNkAaSiwZs" tabindex="-1" role="presentation"></a>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 <code>direction</code> que puede ser <code>"ltr"</code> (de izquierda a derecha), <code>"rtl"</code> (de derecha a izquierda) o <code>"ttb"</code> (de arriba a abajo).</p> +<p><a class="p_ident" id="p-wNkAaSiwZs" href="#p-wNkAaSiwZs" tabindex="-1" role="presentation"></a>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 <code>direction</code> que puede ser <code>"ltr"</code> (de izquierda a derecha), <code>"rtl"</code> (de derecha a izquierda) o <code>"ttb"</code> (de arriba a abajo).</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-CNawUvyti3" href="#c-CNawUvyti3" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">dominantDirection</span>(<span class="tok-definition">text</span>) { <span class="tok-comment">// Your code here.</span> @@ -474,9 +487,9 @@ <h3><a class="i_ident" id="i-kUUDTvgJ6V" href="#i-kUUDTvgJ6V" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-1vPjppobMm" href="#p-1vPjppobMm" tabindex="-1" role="presentation"></a>Tu solución podría parecerse mucho a la primera mitad del ejemplo de <code>textScripts</code>. De nuevo, debes contar caracteres según un criterio basado en <code>characterScript</code> y luego filtrar la parte del resultado que se refiere a caracteres no interesantes (sin script).</p> +<p><a class="p_ident" id="p-lyuHx3ewV/" href="#p-lyuHx3ewV/" tabindex="-1" role="presentation"></a>Tu solución podría parecerse mucho a la primera mitad del ejemplo de <code>sistemasTexto</code>. De nuevo, debes contar caracteres según un criterio basado en <code>sistemaCaracteres</code> y luego filtrar la parte del resultado que se refiere a caracteres no interesantes (sin sistema asociado).</p> -<p><a class="p_ident" id="p-FrVu7Vg3MC" href="#p-FrVu7Vg3MC" tabindex="-1" role="presentation"></a>Encontrar la dirección con el recuento de caracteres más alto se puede hacer con <code>reduce</code>. Si no está claro cómo hacerlo, consulta el ejemplo anterior en el capítulo, donde se usó <code>reduce</code> para encontrar el script con más caracteres.</p> +<p><a class="p_ident" id="p-FrVu7Vg3MC" href="#p-FrVu7Vg3MC" tabindex="-1" role="presentation"></a>Encontrar la dirección con el recuento de caracteres más alto es algo que se puede hacer con <code>reduce</code>. Si no está claro cómo hacerlo, consulta el ejemplo que vimos antes en el capítulo, donde se usó <code>reduce</code> para encontrar el script con más caracteres.</p> </div></details><nav><a href="04_data.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="06_object.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button> </nav> diff --git a/html/06_object.html b/html/06_object.html index ff0512ea..681360f8 100644 --- a/html/06_object.html +++ b/html/06_object.html @@ -14,86 +14,84 @@ <h1>La Vida Secreta de los Objetos</h1> <blockquote> -<p><a class="p_ident" id="p-qY38ImfBs8" href="#p-qY38ImfBs8" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-yRu9ln4fb5" href="#p-yRu9ln4fb5" tabindex="-1" role="presentation"></a>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..</p> -<footer>Barbara Liskov, <cite>Programando con Tipos de Datos Abstractos</cite></footer> +<footer>Barbara Liskov, <cite>Programming with Abstract Data Types</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_6.jpg" alt="Ilustración de un conejo junto a su prototipo, una representación esquemática de un conejo"></figure> -<p><a class="p_ident" id="p-MLbhb3BDla" href="#p-MLbhb3BDla" tabindex="-1" role="presentation"></a><a href="04_data.html">El Capítulo 4</a> introdujo los objetos de JavaScript, como contenedores que almacenan otros datos.</p> - -<p><a class="p_ident" id="p-Q8NffaLX3n" href="#p-Q8NffaLX3n" tabindex="-1" role="presentation"></a>En la cultura de la programación, tenemos algo llamado <em>programación orientada a objetos</em>, 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.</p> +<p><a class="p_ident" id="p-F38Bf52/hA" href="#p-F38Bf52/hA" tabindex="-1" role="presentation"></a>En <a href="04_data.html">el Capítulo 4</a> se introdujeron los objetos de JavaScript como contenedores que almacenan otros datos. En la cultura de la programación, la <em>programación orientada a objetos</em> 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.</p> <h2><a class="h_ident" id="h-75/TxpyaIX" href="#h-75/TxpyaIX" tabindex="-1" role="presentation"></a>Tipos de Datos Abstractos</h2> -<p><a class="p_ident" id="p-vuh02vVpGh" href="#p-vuh02vVpGh" tabindex="-1" role="presentation"></a>La idea principal en la programación orientada a objetos es utilizar objetos, o más bien <em>tipos</em> 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.</p> +<p><a class="p_ident" id="p-u1Mm/0ia2L" href="#p-u1Mm/0ia2L" tabindex="-1" role="presentation"></a>La idea principal en la programación orientada a objetos es utilizar objetos (más bien <em>tipos</em> 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.</p> -<p><a class="p_ident" id="p-o3x0hNvhgx" href="#p-o3x0hNvhgx" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-/zZ5ohVGfC" href="#p-/zZ5ohVGfC" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-GSUsxfI4s5" href="#p-GSUsxfI4s5" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-GSUsxfI4s5" href="#p-GSUsxfI4s5" tabindex="-1" role="presentation"></a>De manera similar, un <em>tipo de dato abstracto</em>, o <em>clase de objeto</em>, 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.</p> -<p><a class="p_ident" id="p-i4yiJ1AR9Q" href="#p-i4yiJ1AR9Q" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-WBRa/TjRFd" href="#p-WBRa/TjRFd" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-vwqJ9SVv4F" href="#p-vwqJ9SVv4F" tabindex="-1" role="presentation"></a>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.</p> +<p id="interfaz"><a class="p_ident" id="p-ba2yqEhhem" href="#p-ba2yqEhhem" tabindex="-1" role="presentation"></a>Cada tipo de dato abstracto tiene una <em>interfaz</em>: la colección de operaciones que el código externo puede realizar en él. Cualquier detalle más allá de dicha interfaz queda <em>encapsulado</em> al tratarse como interno al tipo y de no incumbencia para el resto del programa.</p> -<p id="interfaz"><a class="p_ident" id="p-S2D2FTl9dT" href="#p-S2D2FTl9dT" tabindex="-1" role="presentation"></a>Cada tipo de dato abstracto tiene una <em>interfaz</em>, 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 <em>individuales</em> 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.</p> +<p><a class="p_ident" id="p-L/aWSmnhpG" href="#p-L/aWSmnhpG" tabindex="-1" role="presentation"></a>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 <em>objetos</em> individuales como unidad fundamental de organización, cuando en realidad muchas funcionalidades útiles surgen de la cooperación entre varias clases de objetos.</p> <h2 id="obj_methods"><a class="h_ident" id="h-OYddlNFqr1" href="#h-OYddlNFqr1" tabindex="-1" role="presentation"></a>Métodos</h2> -<p><a class="p_ident" id="p-1j3GckeW7z" href="#p-1j3GckeW7z" tabindex="-1" role="presentation"></a>En JavaScript, los métodos no son más que propiedades que contienen valores de función. Este es un método simple:</p> +<p><a class="p_ident" id="p-1j3GckeW7z" href="#p-1j3GckeW7z" tabindex="-1" role="presentation"></a>En JavaScript, los métodos no son más que propiedades que contienen valores de función. Aquí hay un método simple:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ozl3eO/Ubk" href="#c-ozl3eO/Ubk" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">speak</span>(<span class="tok-definition">line</span>) { - console.log(<span class="tok-string2">`El conejo </span>${<span class="tok-keyword">this</span>.type}<span class="tok-string2"> dice '</span>${line}<span class="tok-string2">'`</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-4d61pl5Kpw" href="#c-4d61pl5Kpw" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">hablar</span>(<span class="tok-definition">frase</span>) { + console.log(<span class="tok-string2">`El conejo </span>${<span class="tok-keyword">this</span>.tipo}<span class="tok-string2"> dice '</span>${frase}<span class="tok-string2">'`</span>); } -<span class="tok-keyword">let</span> <span class="tok-definition">conejoBlanco</span> = {<span class="tok-definition">type</span>: <span class="tok-string">"blanco"</span>, <span class="tok-definition">speak</span>}; -<span class="tok-keyword">let</span> <span class="tok-definition">conejoHambriento</span> = {<span class="tok-definition">type</span>: <span class="tok-string">"hambriento"</span>, <span class="tok-definition">speak</span>}; +<span class="tok-keyword">let</span> <span class="tok-definition">conejoBlanco</span> = {<span class="tok-definition">tipo</span>: <span class="tok-string">"blanco"</span>, <span class="tok-definition">hablar</span>}; +<span class="tok-keyword">let</span> <span class="tok-definition">conejoHambriento</span> = {<span class="tok-definition">tipo</span>: <span class="tok-string">"hambriento"</span>, <span class="tok-definition">hablar</span>}; -conejoBlanco.speak(<span class="tok-string">"Oh, mi pelaje y mis bigotes"</span>); +conejoBlanco.hablar(<span class="tok-string">"Oh, mi pelaje y mis bigotes"</span>); <span class="tok-comment">// → El conejo blanco dice 'Oh, mi pelaje y mis bigotes'</span> -conejoHambriento.speak(<span class="tok-string">"¿Tienes zanahorias?"</span>); +conejoHambriento.hablar(<span class="tok-string">"¿Tienes zanahorias?"</span>); <span class="tok-comment">// → El conejo hambriento dice '¿Tienes zanahorias?'</span></pre> -<p><a class="p_ident" id="p-J8/M7byK1x" href="#p-J8/M7byK1x" tabindex="-1" role="presentation"></a>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 <code>objeto.método()</code>—la vinculación llamada <code>this</code> en su cuerpo apunta automáticamente al objeto en el que fue llamada.</p> +<p><a class="p_ident" id="p-eYc4BV58G2" href="#p-eYc4BV58G2" tabindex="-1" role="presentation"></a>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 <code>objeto.método()</code>— la asociación llamada <code>this</code> en el cuerpo de la misma apunta automáticamente al objeto sobre el que se hizo la llamada.</p> -<p id="call_method"><a class="p_ident" id="p-YUYNbzHjN5" href="#p-YUYNbzHjN5" tabindex="-1" role="presentation"></a>Puedes pensar en <code>this</code> 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 <code>call</code> de una función, el cual toma el valor de <code>this</code> como su primer argumento y trata los siguientes argumentos como parámetros normales.</p> +<p id="call_method"><a class="p_ident" id="p-YUYNbzHjN5" href="#p-YUYNbzHjN5" tabindex="-1" role="presentation"></a>Puedes pensar en <code>this</code> 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 <code>call</code> de la función, que toma el valor de <code>this</code> como primer argumento y trata los siguientes argumentos como parámetros normales.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-lhLyFZ1Ecp" href="#c-lhLyFZ1Ecp" tabindex="-1" role="presentation"></a>speak.call(conejoBlanco, <span class="tok-string">"Rápido"</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Siw9MbNHZU" href="#c-Siw9MbNHZU" tabindex="-1" role="presentation"></a>hablar.call(conejoBlanco, <span class="tok-string">"Rápido"</span>); <span class="tok-comment">// → El conejo blanco dice 'Rápido'</span></pre> -<p><a class="p_ident" id="p-If9Duq/u+K" href="#p-If9Duq/u+K" tabindex="-1" role="presentation"></a>Dado que cada función tiene su propia vinculación <code>this</code>, cuyo valor depende de la forma en que es llamada, no puedes hacer referencia al <code>this</code> del ámbito envolvente en una función regular definida con la palabra clave <code>function</code>.</p> +<p><a class="p_ident" id="p-qq127AGsPS" href="#p-qq127AGsPS" tabindex="-1" role="presentation"></a>Dado que cada función tiene su propia asociación <code>this</code>, cuyo valor depende de la forma en que es llamada, dentro una función normal definida con la palabra clave <code>function</code> no puedes hacer referencia al <code>this</code> del ámbito en el que esta se encuentra envuelta.</p> -<p><a class="p_ident" id="p-iqgQxRUpn1" href="#p-iqgQxRUpn1" tabindex="-1" role="presentation"></a>Las funciones flecha son diferentes—no vinculan su propio <code>this</code> pero pueden ver la vinculación <code>this</code> del ámbito que las rodea. Por lo tanto, puedes hacer algo como el siguiente código, el cual hace referencia a <code>this</code> desde dentro de una función local:</p> +<p><a class="p_ident" id="p-iqgQxRUpn1" href="#p-iqgQxRUpn1" tabindex="-1" role="presentation"></a>Las funciones flecha son diferentes —no enlazan su propio <code>this</code> sino que pueden acceder a la asociación <code>this</code> del ámbito que las rodea. Por lo tanto, puedes hacer algo como el siguiente código, que hace referencia a <code>this</code> desde dentro de una función local:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-DqfAE+Qk4U" href="#c-DqfAE+Qk4U" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">buscador</span> = { - <span class="tok-definition">find</span>(<span class="tok-definition">array</span>) { - <span class="tok-keyword">return</span> array.some(<span class="tok-definition">v</span> => v == <span class="tok-keyword">this</span>.value); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-C5fSfhtEfB" href="#c-C5fSfhtEfB" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">buscador</span> = { + <span class="tok-definition">buscar</span>(<span class="tok-definition">array</span>) { + <span class="tok-keyword">return</span> array.some(<span class="tok-definition">v</span> => v == <span class="tok-keyword">this</span>.valor); }, - <span class="tok-definition">value</span>: <span class="tok-number">5</span> + <span class="tok-definition">valor</span>: <span class="tok-number">5</span> }; -console.log(buscador.find([<span class="tok-number">4</span>, <span class="tok-number">5</span>])); +console.log(buscador.buscar([<span class="tok-number">4</span>, <span class="tok-number">5</span>])); <span class="tok-comment">// → true</span></pre> -<p><a class="p_ident" id="p-GbEqZ6yrdl" href="#p-GbEqZ6yrdl" tabindex="-1" role="presentation"></a>Una propiedad como <code>find(array)</code> en una expresión de objeto es una forma abreviada de definir un método. Crea una propiedad llamada <code>find</code> y le asigna una función como su valor.</p> +<p><a class="p_ident" id="p-60kXWtQelX" href="#p-60kXWtQelX" tabindex="-1" role="presentation"></a>Una propiedad como <code>buscar(array)</code> en una expresión de objeto es una forma abreviada de definir un método. Crea una propiedad llamada <code>buscar</code> y le asigna una función como valor de la misma.</p> -<p><a class="p_ident" id="p-Nmpv2shDAC" href="#p-Nmpv2shDAC" tabindex="-1" role="presentation"></a>Si hubiera escrito el argumento de <code>some</code> usando la palabra clave <code>function</code>, este código no funcionaría.</p> +<p><a class="p_ident" id="p-caQeTijmYv" href="#p-caQeTijmYv" tabindex="-1" role="presentation"></a>Si hubiera escrito el argumento de <code>some</code> usando la palabra clave <code>function</code>, este código no funcionaría, por lo mencionado más arriba.</p> <h2 id="prototypes"><a class="h_ident" id="h-8D3hGqC4Vb" href="#h-8D3hGqC4Vb" tabindex="-1" role="presentation"></a>Prototipos</h2> -<p><a class="p_ident" id="p-zucU9w1t5a" href="#p-zucU9w1t5a" tabindex="-1" role="presentation"></a>Entonces, una forma de crear un tipo de conejo abstracto con un método <code>speak</code> 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 <code>type</code> y nuestra función <code>speak</code> en su propiedad <code>speak</code>.</p> +<p><a class="p_ident" id="p-e69Rxp2GFi" href="#p-e69Rxp2GFi" tabindex="-1" role="presentation"></a>Una manera de crear un de objeto de tipo conejo con un método <code>hablar</code> 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 <code>tipo</code> y nuestra función de hablar en su propiedad <code>hablar</code>.</p> -<p><a class="p_ident" id="p-7KfiGiT//W" href="#p-7KfiGiT//W" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-7KfiGiT//W" href="#p-7KfiGiT//W" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-j6CLviaCYU" href="#p-j6CLviaCYU" tabindex="-1" role="presentation"></a>En JavaScript, los <em>prototipos</em> 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 <code>{}</code> están enlazados a un objeto llamado <code>Object.prototype</code>.</p> +<p><a class="p_ident" id="p-gQLEMh5WGO" href="#p-gQLEMh5WGO" tabindex="-1" role="presentation"></a>En JavaScript, la manera de hacer eso son los <em>prototipos</em>. 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 <code>{}</code> están enlazados a un objeto llamado <code>Object.prototype</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-P0GVdA5c8J" href="#c-P0GVdA5c8J" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">empty</span> = {}; -console.log(empty.toString); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-qTmgvh8p1s" href="#c-qTmgvh8p1s" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">vacío</span> = {}; +console.log(vacío.toString); <span class="tok-comment">// → function toString(){…}</span> -console.log(empty.toString()); +console.log(vacío.toString()); <span class="tok-comment">// → [object Object]</span></pre> -<p><a class="p_ident" id="p-VugV5h0c5y" href="#p-VugV5h0c5y" tabindex="-1" role="presentation"></a>Parece que acabamos de extraer una propiedad de un objeto vacío. Pero de hecho, <code>toString</code> es un método almacenado en <code>Object.prototype</code>, lo que significa que está disponible en la mayoría de los objetos.</p> +<p><a class="p_ident" id="p-VugV5h0c5y" href="#p-VugV5h0c5y" tabindex="-1" role="presentation"></a>Parece que acabamos de extraer una propiedad de un objeto vacío. Pero resulta que <code>toString</code> es un método almacenado en <code>Object.prototype</code>, lo que significa que está disponible en la mayoría de los objetos.</p> -<p><a class="p_ident" id="p-PCh9X/oMQG" href="#p-PCh9X/oMQG" tabindex="-1" role="presentation"></a>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 <em>su</em> prototipo, y así sucesivamente hasta llegar a un objeto que no tiene prototipo (<code>Object.prototype</code> es un objeto de este tipo).</p> +<p><a class="p_ident" id="p-HejXJ3N84Z" href="#p-HejXJ3N84Z" tabindex="-1" role="presentation"></a>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 <em>su</em> prototipo, y así sucesivamente hasta llegar a un objeto que no tiene prototipo (<code>Object.prototype</code> es uno de estos objetos).</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-OQZG/UHNHD" href="#c-OQZG/UHNHD" tabindex="-1" role="presentation"></a>console.log(Object.getPrototypeOf({}) == Object.prototype); <span class="tok-comment">// → true</span> @@ -102,7 +100,7 @@ <h2 id="prototypes"><a class="h_ident" id="h-8D3hGqC4Vb" href="#h-8D3hGqC4Vb" ta <p><a class="p_ident" id="p-nprGUKePiW" href="#p-nprGUKePiW" tabindex="-1" role="presentation"></a>Como podrás imaginar, <code>Object.<wbr>getPrototypeOf</code> devuelve el prototipo de un objeto.</p> -<p><a class="p_ident" id="p-3tMgS779JM" href="#p-3tMgS779JM" tabindex="-1" role="presentation"></a>Muchos objetos no tienen directamente <code>Object.prototype</code> como su prototipo, sino que tienen otro objeto que proporciona un conjunto diferente de propiedades predeterminadas. Las funciones se derivan de <code>Function.<wbr>prototype</code>, y los arreglos se derivan de <code>Array.prototype</code>.</p> +<p><a class="p_ident" id="p-3tMgS779JM" href="#p-3tMgS779JM" tabindex="-1" role="presentation"></a>Muchos objetos no tienen directamente <code>Object.prototype</code> 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 <code>Function.<wbr>prototype</code>, y los arrays se derivan de <code>Array.prototype</code>.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-u0Ich5CNwz" href="#c-u0Ich5CNwz" tabindex="-1" role="presentation"></a>console.log(Object.getPrototypeOf(Math.max) == Function.prototype); @@ -110,143 +108,148 @@ <h2 id="prototypes"><a class="h_ident" id="h-8D3hGqC4Vb" href="#h-8D3hGqC4Vb" ta console.log(Object.getPrototypeOf([]) == Array.prototype); <span class="tok-comment">// → true</span></pre> -<p><a class="p_ident" id="p-vhhBvSxfXx" href="#p-vhhBvSxfXx" tabindex="-1" role="presentation"></a>Un objeto prototipo de este tipo tendrá a su vez un prototipo, a menudo <code>Object.prototype</code>, de modo que aún proporciona de forma indirecta métodos como <code>toString</code>.</p> +<p><a class="p_ident" id="p-vhhBvSxfXx" href="#p-vhhBvSxfXx" tabindex="-1" role="presentation"></a>Un objeto prototipo de este tipo tendrá a su vez un prototipo, a menudo <code>Object.prototype</code>, de modo que este aún proporciona de forma indirecta métodos como <code>toString</code>.</p> <p><a class="p_ident" id="p-z0YymTAque" href="#p-z0YymTAque" tabindex="-1" role="presentation"></a>Puedes utilizar <code>Object.create</code> para crear un objeto con un prototipo específico.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NaPtsYBkJN" href="#c-NaPtsYBkJN" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">protoRabbit</span> = { - <span class="tok-definition">speak</span>(<span class="tok-definition">line</span>) { - console.log(<span class="tok-string2">`El conejo </span>${<span class="tok-keyword">this</span>.type}<span class="tok-string2"> dice '</span>${line}<span class="tok-string2">'`</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-+nHEf+mC1K" href="#c-+nHEf+mC1K" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">protoConejo</span> = { + <span class="tok-definition">hablar</span>(<span class="tok-definition">frase</span>) { + console.log(<span class="tok-string2">`El conejo </span>${<span class="tok-keyword">this</span>.tipo}<span class="tok-string2"> dice '</span>${frase}<span class="tok-string2">'`</span>); } }; -<span class="tok-keyword">let</span> <span class="tok-definition">blackRabbit</span> = Object.create(protoRabbit); -blackRabbit.type = <span class="tok-string">"negro"</span>; -blackRabbit.speak(<span class="tok-string">"Soy el miedo y la oscuridad"</span>); -<span class="tok-comment">// → El conejo negro dice 'Soy el miedo y la oscuridad'</span></pre> +<span class="tok-keyword">let</span> <span class="tok-definition">conejoNegro</span> = Object.create(protoConejo); +conejoNegro.tipo = <span class="tok-string">"negro"</span>; +conejoNegro.hablar(<span class="tok-string">"Soy miedo y oscuridad"</span>); +<span class="tok-comment">// → El conejo negro dice 'Soy miedo y oscuridad'</span></pre> -<p><a class="p_ident" id="p-uB941a5WJb" href="#p-uB941a5WJb" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-ErFs0w+ABB" href="#p-ErFs0w+ABB" tabindex="-1" role="presentation"></a>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.</p> <h2 id="clases"><a class="h_ident" id="h-KLZW7aPxR5" href="#h-KLZW7aPxR5" tabindex="-1" role="presentation"></a>Clases</h2> -<p><a class="p_ident" id="p-KApLmuwuZI" href="#p-KApLmuwuZI" tabindex="-1" role="presentation"></a>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 <em>instancia</em> de la clase.</p> +<p><a class="p_ident" id="p-KApLmuwuZI" href="#p-KApLmuwuZI" tabindex="-1" role="presentation"></a>El sistema de prototipos de JavaScript puede interpretarse como una versión algo libre de los tipos de datos abstractos o clases. Una <em>clase</em> define la forma de un tipo de objeto —los métodos y propiedades que tiene. A dicho objeto se le llama una <em>instancia</em> de la clase.</p> -<p><a class="p_ident" id="p-zz4nhYtJgu" href="#p-zz4nhYtJgu" tabindex="-1" role="presentation"></a>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 <code>type</code> de nuestros conejos, deben ser almacenadas directamente en los objetos mismos.</p> +<p><a class="p_ident" id="p-zz4nhYtJgu" href="#p-zz4nhYtJgu" tabindex="-1" role="presentation"></a>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 <code>tipo</code> de los conejos, deben ser almacenadas directamente en los objetos mismos.</p> -<p id="constructores"><a class="p_ident" id="p-+3ctSy6D/c" href="#p-+3ctSy6D/c" tabindex="-1" role="presentation"></a>Así que para crear una instancia de una clase, debes hacer un objeto que se derive del prototipo adecuado, pero <em>también</em> 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 <em>constructor</em>.</p> +<p id="constructores"><a class="p_ident" id="p-uEKTBkWLsH" href="#p-uEKTBkWLsH" tabindex="-1" role="presentation"></a>Para crear una instancia de una clase dada, debes hacer un objeto que herede del prototipo adecuado, pero <em>también</em> 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 <em>constructor</em>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-oOKUeIzSVa" href="#c-oOKUeIzSVa" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">makeRabbit</span>(<span class="tok-definition">type</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">rabbit</span> = Object.create(protoRabbit); - rabbit.type = type; - <span class="tok-keyword">return</span> rabbit; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-OQtat/xeLL" href="#c-OQtat/xeLL" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">hacerConejo</span>(<span class="tok-definition">tipo</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">conejo</span> = Object.create(protoConejo); + conejo.tipo = tipo; + <span class="tok-keyword">return</span> conejo; }</pre> -<p><a class="p_ident" id="p-ke62mHUrBS" href="#p-ke62mHUrBS" tabindex="-1" role="presentation"></a>La notación de class de JavaScript facilita la definición de este tipo de función, junto con un objeto prototype.</p> +<p><a class="p_ident" id="p-ke62mHUrBS" href="#p-ke62mHUrBS" tabindex="-1" role="presentation"></a>La notación de class de JavaScript facilita la definición de este tipo de funciones, junto con un objeto prototype.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-cJ0DPIGyzG" href="#c-cJ0DPIGyzG" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> Rabbit { - <span class="tok-definition">constructor</span>(<span class="tok-definition">type</span>) { - <span class="tok-keyword">this</span>.type = type; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-3DSXslc5qX" href="#c-3DSXslc5qX" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> Conejo { + <span class="tok-definition">constructor</span>(<span class="tok-definition">tipo</span>) { + <span class="tok-keyword">this</span>.tipo = tipo; } - <span class="tok-definition">speak</span>(<span class="tok-definition">line</span>) { - console.log(<span class="tok-string2">`El conejo </span>${<span class="tok-keyword">this</span>.type}<span class="tok-string2"> dice '</span>${line}<span class="tok-string2">'`</span>); + <span class="tok-definition">hablar</span>(<span class="tok-definition">frase</span>) { + console.log(<span class="tok-string2">`El conejo </span>${<span class="tok-keyword">this</span>.tipo}<span class="tok-string2"> dice '</span>${frase}<span class="tok-string2">'`</span>); } }</pre> -<p><a class="p_ident" id="p-57dMNMBzNN" href="#p-57dMNMBzNN" tabindex="-1" role="presentation"></a>La palabra clave <code>class</code> 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 <code>Rabbit</code>, que contiene una función que ejecuta el código en <code>constructor</code>, y tiene una propiedad <code>prototype</code> que contiene el método <code>speak</code>.</p> +<p><a class="p_ident" id="p-57dMNMBzNN" href="#p-57dMNMBzNN" tabindex="-1" role="presentation"></a>La palabra clave <code>class</code> 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 <code>Conejo</code>, que contiene una función que ejecuta el código en <code>constructor</code>, y tiene una propiedad <code>prototype</code> que contiene el método <code>hablar</code>.</p> -<p><a class="p_ident" id="p-O8DTPcH5xn" href="#p-O8DTPcH5xn" tabindex="-1" role="presentation"></a>Esta función no puede ser llamada normalmente. Los constructores, en JavaScript, se llaman colocando la palabra clave <code>new</code> delante de ellos. Al hacerlo, se crea un objeto nuevo con el objeto contenido en la propiedad <code>prototype</code> de la función como prototipo, luego se ejecuta la función con <code>this</code> vinculado al nuevo objeto, y finalmente se devuelve el objeto.</p> +<p><a class="p_ident" id="p-O8DTPcH5xn" href="#p-O8DTPcH5xn" tabindex="-1" role="presentation"></a>Esta función no se puede llamar como una función normal. Los constructores, en JavaScript, se llaman colocando la palabra clave <code>new</code> delante de ellos. Al hacerlo, se crea una nueva instancia de objeto cuyo prototipo es el objeto de la propiedad <code>prototype</code> de la función, luego se ejecuta la función con <code>this</code> enlazado al nuevo objeto, y finalmente se devuelve el objeto.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-beJBbbcraU" href="#c-beJBbbcraU" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">killerRabbit</span> = <span class="tok-keyword">new</span> Rabbit(<span class="tok-string">"asesino"</span>);</pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9KsdtMxnwN" href="#c-9KsdtMxnwN" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">conejoAsesino</span> = <span class="tok-keyword">new</span> Rabbit(<span class="tok-string">"asesino"</span>);</pre> -<p><a class="p_ident" id="p-WBkBpKVfZe" href="#p-WBkBpKVfZe" tabindex="-1" role="presentation"></a>De hecho, la palabra clave <code>class</code> 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 <code>prototype</code>.</p> +<p><a class="p_ident" id="p-WBkBpKVfZe" href="#p-WBkBpKVfZe" tabindex="-1" role="presentation"></a>De hecho, la palabra clave <code>class</code> 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 <code>prototype</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-G0wj90Jmh4" href="#c-G0wj90Jmh4" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">ConejoArcaico</span>(<span class="tok-definition">type</span>) { - <span class="tok-keyword">this</span>.type = type; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-CH5JJMQkAW" href="#c-CH5JJMQkAW" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">ConejoArcaico</span>(<span class="tok-definition">tipo</span>) { + <span class="tok-keyword">this</span>.tipo = tipo; } -ConejoArcaico.prototype.speak = <span class="tok-keyword">function</span>(<span class="tok-definition">line</span>) { - console.log(<span class="tok-string2">`El conejo </span>${<span class="tok-keyword">this</span>.type}<span class="tok-string2"> dice '</span>${line}<span class="tok-string2">'`</span>); +ConejoArcaico.prototype.hablar = <span class="tok-keyword">function</span>(<span class="tok-definition">frase</span>) { + console.log(<span class="tok-string2">`El conejo </span>${<span class="tok-keyword">this</span>.tipo}<span class="tok-string2"> dice '</span>${frase}<span class="tok-string2">'`</span>); }; -<span class="tok-keyword">let</span> <span class="tok-definition">conejoEstiloAntiguo</span> = <span class="tok-keyword">new</span> ConejoArcaico(<span class="tok-string">"estilo antiguo"</span>);</pre> +<span class="tok-keyword">let</span> <span class="tok-definition">conejoViejaEscuela</span> = <span class="tok-keyword">new</span> ConejoArcaico(<span class="tok-string">"de la vieja escuela"</span>);</pre> -<p><a class="p_ident" id="p-x6GN3ycVan" href="#p-x6GN3ycVan" tabindex="-1" role="presentation"></a>Por esta razón, todas las funciones que no sean de flecha comienzan con una propiedad <code>prototype</code> que contiene un objeto vacío.</p> +<p><a class="p_ident" id="p-x6GN3ycVan" href="#p-x6GN3ycVan" tabindex="-1" role="presentation"></a>Por esta razón, todas las funciones que no sean funciones flecha comienzan teniendo una propiedad <code>prototype</code> que contiene un objeto vacío.</p> <p><a class="p_ident" id="p-TKZNbhUTqz" href="#p-TKZNbhUTqz" tabindex="-1" role="presentation"></a>Por convención, los nombres de constructores se escriben con mayúscula inicial para que puedan distinguirse fácilmente de otras funciones.</p> -<p><a class="p_ident" id="p-jxChLMhUHf" href="#p-jxChLMhUHf" tabindex="-1" role="presentation"></a>Es importante entender la distinción entre la forma en que un prototipo está asociado con un constructor (a través de su <em>propiedad</em> <code>prototype</code>) y la forma en que los objetos <em>tienen</em> un prototipo (que se puede encontrar con <code>Object.<wbr>getPrototypeOf</code>). El prototipo real de un constructor es <code>Function.<wbr>prototype</code> ya que los constructores son funciones. Su <em>propiedad</em> <code>prototype</code> contiene el prototipo utilizado para las instancias creadas a través de él.</p> +<p><a class="p_ident" id="p-jxChLMhUHf" href="#p-jxChLMhUHf" tabindex="-1" role="presentation"></a>Es importante entender la distinción entre la forma en que un prototipo está asociado a un constructor (a través de su <em>propiedad</em> <code>prototype</code>) y la forma en que los objetos <em>tienen</em> un prototipo (que se puede encontrar con <code>Object.<wbr>getPrototypeOf</code>). El prototipo real de un constructor es <code>Function.<wbr>prototype</code> ya que los constructores son funciones. Su <em>propiedad</em> <code>prototype</code> contiene el prototipo utilizado para las instancias creadas a través de él.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Q/QbNN+9Zh" href="#c-Q/QbNN+9Zh" tabindex="-1" role="presentation"></a>console.log(Object.getPrototypeOf(Rabbit) == +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Qla8qGIgi1" href="#c-Qla8qGIgi1" tabindex="-1" role="presentation"></a>console.log(Object.getPrototypeOf(Conejo) == Function.prototype); <span class="tok-comment">// → true</span> -console.log(Object.getPrototypeOf(killerRabbit) == - Rabbit.prototype); +console.log(Object.getPrototypeOf(conejoAsesino) == + Conejo.prototype); <span class="tok-comment">// → true</span></pre> -<p><a class="p_ident" id="p-yNn0iV+BaD" href="#p-yNn0iV+BaD" tabindex="-1" role="presentation"></a>Por lo general, los constructores agregarán algunas propiedades específicas de instancia a <code>this</code>. 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.</p> +<p><a class="p_ident" id="p-yNn0iV+BaD" href="#p-yNn0iV+BaD" tabindex="-1" role="presentation"></a>Por lo general, los constructores añadirán algunas propiedades específicas por instancia a <code>this</code>. 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-KBHDrbFbxX" href="#c-KBHDrbFbxX" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> Particle { - <span class="tok-definition">speed</span> = <span class="tok-number">0</span>; - <span class="tok-definition">constructor</span>(<span class="tok-definition">position</span>) { - <span class="tok-keyword">this</span>.position = position; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-rHRyWRtykc" href="#c-rHRyWRtykc" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> Partícula { + <span class="tok-definition">rapidez</span> = <span class="tok-number">0</span>; + <span class="tok-definition">constructor</span>(<span class="tok-definition">posición</span>) { + <span class="tok-keyword">this</span>.posición = posición; } }</pre> -<p><a class="p_ident" id="p-IsMykN2X0p" href="#p-IsMykN2X0p" tabindex="-1" role="presentation"></a>Al igual que <code>function</code>, <code>class</code> 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.</p> +<p><a class="p_ident" id="p-IsMykN2X0p" href="#p-IsMykN2X0p" tabindex="-1" role="presentation"></a>Al igual que <code>function</code>, <code>class</code> 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-79re+GWcTJ" href="#c-79re+GWcTJ" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">object</span> = <span class="tok-keyword">new</span> <span class="tok-keyword">class</span> { <span class="tok-definition">getWord</span>() { <span class="tok-keyword">return</span> <span class="tok-string">"hello"</span>; } }; -console.log(object.getWord()); -<span class="tok-comment">// → hello</span></pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9g1toPBzZT" href="#c-9g1toPBzZT" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">objeto</span> = <span class="tok-keyword">new</span> <span class="tok-keyword">class</span> { <span class="tok-definition">obtenerPalabra</span>() { <span class="tok-keyword">return</span> <span class="tok-string">"hola"</span>; } }; +console.log(objeto.obtenerPalabra()); +<span class="tok-comment">// → hola</span></pre> <h2><a class="h_ident" id="h-Mcv8cYMW5o" href="#h-Mcv8cYMW5o" tabindex="-1" role="presentation"></a>Propiedades privadas</h2> -<p><a class="p_ident" id="p-PUWpyjF3Zr" href="#p-PUWpyjF3Zr" tabindex="-1" role="presentation"></a>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 <em>privadas</em>, en contraposición a las públicas, que son parte de la interfaz externa del objeto.</p> +<p><a class="p_ident" id="p-PUWpyjF3Zr" href="#p-PUWpyjF3Zr" tabindex="-1" role="presentation"></a>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 <em>privadas</em>, en contraposición a las <em>públicas</em>, que son parte de la interfaz externa del objeto.</p> <p><a class="p_ident" id="p-2PxBmSSVSA" href="#p-2PxBmSSVSA" tabindex="-1" role="presentation"></a>Para declarar un método privado, coloca un signo <code>#</code> delante de su nombre. Estos métodos solo pueden ser llamados desde dentro de la declaración de la <code>class</code> que los define.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-P9tY27VmAy" href="#c-P9tY27VmAy" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> SecretiveObject { - <span class="tok-definition">#getSecret</span>() { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-mm33m+dQkH" href="#c-mm33m+dQkH" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> ObjectoConfidencial { + <span class="tok-definition">#obtenerSecreto</span>() { <span class="tok-keyword">return</span> <span class="tok-string">"Me comí todas las ciruelas"</span>; } - <span class="tok-definition">interrogate</span>() { - <span class="tok-keyword">let</span> <span class="tok-definition">deboDecirlo</span> = <span class="tok-keyword">this</span>.#getSecret(); + <span class="tok-definition">interrogar</span>() { + <span class="tok-keyword">let</span> <span class="tok-definition">voyADecirlo</span> = <span class="tok-keyword">this</span>.#obtenerSecreto(); <span class="tok-keyword">return</span> <span class="tok-string">"nunca"</span>; } }</pre> -<p><a class="p_ident" id="p-gfxNQhu2Og" href="#p-gfxNQhu2Og" tabindex="-1" role="presentation"></a>Si intentas llamar a <code>#getSecret</code> desde fuera de la clase, obtendrás un error. Su existencia está completamente oculta dentro de la declaración de la clase.</p> +<p><a class="p_ident" id="p-Y5GmdIXcFW" href="#p-Y5GmdIXcFW" tabindex="-1" role="presentation"></a>Cuando una clase no declara un constructor, automáticamente obtiene un constructor vacío.</p> + +<p><a class="p_ident" id="p-gfxNQhu2Og" href="#p-gfxNQhu2Og" tabindex="-1" role="presentation"></a>Si intentas llamar a <code>#obtenerSecreto</code> desde fuera de la clase, obtendrás un error. Su existencia está completamente oculta dentro de la declaración de la clase.</p> -<p><a class="p_ident" id="p-heEPUsCBMK" href="#p-heEPUsCBMK" tabindex="-1" role="presentation"></a>Para usar propiedades de instancia privadas, debes declararlas. Las propiedades regulares se pueden crear simplemente asignándoles un valor, pero las propiedades privadas <em>deben</em> declararse en la declaración de la clase para estar disponibles en absoluto.</p> +<p><a class="p_ident" id="p-5pIvdAQZzs" href="#p-5pIvdAQZzs" tabindex="-1" role="presentation"></a>Para usar propiedades de instancia privadas, debes declararlas. Las propiedades normales se pueden crear simplemente asignándoles un valor, pero las propiedades privadas <em>deben</em> declararse en la declaración de la clase para estar disponibles.</p> -<p><a class="p_ident" id="p-9tES/9bHWe" href="#p-9tES/9bHWe" tabindex="-1" role="presentation"></a>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: <code>getNumber</code>.</p> +<p><a class="p_ident" id="p-9tES/9bHWe" href="#p-9tES/9bHWe" tabindex="-1" role="presentation"></a>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: <code>obtenerNúmero</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-2bWeku8FvK" href="#c-2bWeku8FvK" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> RandomSource { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-qAsh63/RQM" href="#c-qAsh63/RQM" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> FuenteDeAzar { <span class="tok-definition">#max</span>; <span class="tok-definition">constructor</span>(<span class="tok-definition">max</span>) { <span class="tok-keyword">this</span>.#max = max; } - <span class="tok-definition">getNumber</span>() { + <span class="tok-definition">obtenerNúmero</span>() { <span class="tok-keyword">return</span> Math.floor(Math.random() * <span class="tok-keyword">this</span>.#max); } }</pre> -<h2><a class="h_ident" id="h-mcYWuHdtlm" href="#h-mcYWuHdtlm" tabindex="-1" role="presentation"></a>Sobrescribiendo propiedades derivadas</h2> +<h2><a class="h_ident" id="h-DW+BM/WIgT" href="#h-DW+BM/WIgT" tabindex="-1" role="presentation"></a>Sobrescribiendo propiedades heredadas</h2> -<p><a class="p_ident" id="p-HxBM44Z1jx" href="#p-HxBM44Z1jx" tabindex="-1" role="presentation"></a>Cuando agregas una propiedad a un objeto, ya sea que esté presente en el prototipo o no, la propiedad se agrega al objeto <em>mismo</em>. 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.</p> +<p><a class="p_ident" id="p-mp0T561YVd" href="#p-mp0T561YVd" tabindex="-1" role="presentation"></a>Cuando agregas una propiedad a un objeto, esté presente en el prototipo o no, la propiedad se agrega al <em>propio</em> 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-iiINk44qG3" href="#c-iiINk44qG3" tabindex="-1" role="presentation"></a>Rabbit.prototype.teeth = <span class="tok-string">"pequeñas"</span>; -console.log(killerRabbit.teeth); -<span class="tok-comment">// → pequeñas</span> -killerRabbit.teeth = <span class="tok-string">"largos, afilados y sangrientos"</span>; -console.log(killerRabbit.teeth); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-VI+/eeBHap" href="#c-VI+/eeBHap" tabindex="-1" role="presentation"></a>Conejo.prototype.dientes = <span class="tok-string">"pequeños"</span>; +console.log(conejoAsesino.dientes); +<span class="tok-comment">// → pequeños</span> +conejoAsesino.dientes = <span class="tok-string">"largos, afilados y sangrientos"</span>; +console.log(conejoAsesino.dientes); <span class="tok-comment">// → largos, afilados y sangrientos</span> -console.log((<span class="tok-keyword">new</span> Rabbit(<span class="tok-string">"básico"</span>)).teeth); -<span class="tok-comment">// → pequeñas</span> -console.log(Rabbit.prototype.teeth); -<span class="tok-comment">// → pequeñas</span></pre> +console.log((<span class="tok-keyword">new</span> Conejo(<span class="tok-string">"básico"</span>)).dientes); +<span class="tok-comment">// → pequeños</span> +console.log(Conejo.prototype.dientes); +<span class="tok-comment">// → pequeños</span></pre> + +<p><a class="p_ident" id="p-C8YEGixoPb" href="#p-C8YEGixoPb" tabindex="-1" role="presentation"></a>El siguiente diagrama esquematiza la situación después de ejecutar este código. Los prototipos <code>Conejo</code> y <code>Object</code> están detrás de <code>conejoAsesino</code> como una especie telón de fondo, donde se pueden buscar propiedades que no se encuentran en el objeto mismo.</p><figure><img src="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."></figure> -<p><a class="p_ident" id="p-C8YEGixoPb" href="#p-C8YEGixoPb" tabindex="-1" role="presentation"></a>El siguiente diagrama esquematiza la situación después de que se ha ejecutado este código. Los prototipos <code>Rabbit</code> y <code>Object</code> están detrás de <code>killerRabbit</code> como un telón de fondo, donde se pueden buscar propiedades que no se encuentran en el objeto mismo.</p><figure><img src="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."></figure> +<div class="translator-note"><p><strong>N. del T.:</strong> 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, <code>killerRabbit</code> es <code>conejoAsesino</code>, <code>teeth</code> es <code>dientes</code>, <code>type</code> es <code>tipo</code>, <code>speak</code> es <code>hablar</code> y <code>Rabbit</code> es <code>Conejo</code>.</p> +</div> -<p><a class="p_ident" id="p-unrgQyrFPO" href="#p-unrgQyrFPO" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-unrgQyrFPO" href="#p-unrgQyrFPO" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-ZgWk3RVPU9" href="#p-ZgWk3RVPU9" tabindex="-1" role="presentation"></a>También se utiliza la sobrescritura para dar a los prototipos estándar de funciones y arrays un método <code>toString</code> diferente al del prototipo básico de objeto.</p> @@ -256,16 +259,16 @@ <h2><a class="h_ident" id="h-mcYWuHdtlm" href="#h-mcYWuHdtlm" tabindex="-1" role console.log([<span class="tok-number">1</span>, <span class="tok-number">2</span>].toString()); <span class="tok-comment">// → 1,2</span></pre> -<p><a class="p_ident" id="p-QW4LQ+6i3T" href="#p-QW4LQ+6i3T" tabindex="-1" role="presentation"></a>Llamar a <code>toString</code> en un array produce un resultado similar a llamar a <code>.<wbr>join(",")</code> en él—coloca comas entre los valores en el array. Llamar directamente a <code>Object.<wbr>prototype.<wbr>toString</code> con un array produce una cadena diferente. Esa función no conoce acerca de los arrays, por lo que simplemente coloca la palabra <em>object</em> y el nombre del tipo entre corchetes.</p> +<p><a class="p_ident" id="p-QW4LQ+6i3T" href="#p-QW4LQ+6i3T" tabindex="-1" role="presentation"></a>Llamar a <code>toString</code> en un array produce un resultado similar a llamar a <code>.<wbr>join(",")</code> en él —coloca comas entre los valores en el array. Llamar directamente a <code>Object.<wbr>prototype.<wbr>toString</code> con un array produce una cadena diferente. Esa función no conoce acerca de los arrays, por lo que simplemente coloca la palabra <em>object</em> y el nombre del tipo entre corchetes.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-XpqFUrDFJE" href="#c-XpqFUrDFJE" tabindex="-1" role="presentation"></a>console.log(Object.prototype.toString.call([<span class="tok-number">1</span>, <span class="tok-number">2</span>])); <span class="tok-comment">// → [object Array]</span></pre> <h2><a class="h_ident" id="h-UekXwIlliC" href="#h-UekXwIlliC" tabindex="-1" role="presentation"></a>Mapas</h2> -<p><a class="p_ident" id="p-9PMCR3bigz" href="#p-9PMCR3bigz" tabindex="-1" role="presentation"></a>Vimos la palabra <em>map</em> utilizada en el <a href="05_higher_order.html#map">capítulo anterior</a> 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.</p> +<p><a class="p_ident" id="p-9PMCR3bigz" href="#p-9PMCR3bigz" tabindex="-1" role="presentation"></a>Vimos la palabra <em>map</em> utilizada en el <a href="05_higher_order.html#map">capítulo anterior</a> 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.</p> -<p><a class="p_ident" id="p-r8/g2LZ4R4" href="#p-r8/g2LZ4R4" tabindex="-1" role="presentation"></a>Un <em>mapa</em> (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.</p> +<p><a class="p_ident" id="p-+DmKsVO58M" href="#p-+DmKsVO58M" tabindex="-1" role="presentation"></a>Un <em>mapa</em> (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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-zjT1IcK2YP" href="#c-zjT1IcK2YP" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">edades</span> = { <span class="tok-definition">Boris</span>: <span class="tok-number">39</span>, @@ -280,32 +283,32 @@ <h2><a class="h_ident" id="h-UekXwIlliC" href="#h-UekXwIlliC" tabindex="-1" role console.log(<span class="tok-string">"¿Se conoce la edad de toString?"</span>, <span class="tok-string">"toString"</span> <span class="tok-keyword">in</span> edades); <span class="tok-comment">// → ¿Se conoce la edad de toString? true</span></pre> -<p><a class="p_ident" id="p-8/vnDucH1Z" href="#p-8/vnDucH1Z" tabindex="-1" role="presentation"></a>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 <code>Object.prototype</code>, parece que la propiedad está allí.</p> +<p><a class="p_ident" id="p-djifpfPugK" href="#p-djifpfPugK" tabindex="-1" role="presentation"></a>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 <code>Object.prototype</code>, parece que la propiedad sí que está presente ahí.</p> -<p><a class="p_ident" id="p-Ip63XgEwbB" href="#p-Ip63XgEwbB" tabindex="-1" role="presentation"></a>Por lo tanto, usar objetos simples como mapas es peligroso. Hay varias formas posibles de evitar este problema. Primero, es posible crear objetos sin <em>ningún</em> prototipo. Si pasas <code>null</code> a <code>Object.create</code>, el objeto resultante no derivará de <code>Object.prototype</code> y se puede usar de forma segura como un mapa.</p> +<p><a class="p_ident" id="p-Ip63XgEwbB" href="#p-Ip63XgEwbB" tabindex="-1" role="presentation"></a>Por lo tanto, usar objetos simples como mapas es peligroso. Hay varias formas posibles de evitar este problema. Primero, es posible crear objetos <em>sin</em> prototipo. Si pasas <code>null</code> a <code>Object.create</code>, el objeto resultante no derivará de <code>Object.prototype</code> y se puede usar de forma segura como un mapa.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-AkRQLQc4AG" href="#c-AkRQLQc4AG" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"toString"</span> <span class="tok-keyword">in</span> Object.create(<span class="tok-keyword">null</span>)); <span class="tok-comment">// → false</span></pre> -<p><a class="p_ident" id="p-pzAIVt130A" href="#p-pzAIVt130A" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-pzAIVt130A" href="#p-pzAIVt130A" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-HaYZV/H0va" href="#p-HaYZV/H0va" tabindex="-1" role="presentation"></a>Afortunadamente, JavaScript viene con una clase llamada <code>Map</code> que está escrita para este propósito exacto. Almacena un mapeo y permite cualquier tipo de claves.</p> +<p><a class="p_ident" id="p-Mk/tSGtLhv" href="#p-Mk/tSGtLhv" tabindex="-1" role="presentation"></a>Por suerte, JavaScript viene con una clase llamada <code>Map</code> que está escrita justo para esto. Almacena un mapeo y permite cualquier tipo de claves.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Rx9LUaRTVZ" href="#c-Rx9LUaRTVZ" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">ages</span> = <span class="tok-keyword">new</span> Map(); -ages.set(<span class="tok-string">"Boris"</span>, <span class="tok-number">39</span>); -ages.set(<span class="tok-string">"Liang"</span>, <span class="tok-number">22</span>); -ages.set(<span class="tok-string">"Júlia"</span>, <span class="tok-number">62</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-fSIde07MEg" href="#c-fSIde07MEg" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">edades</span> = <span class="tok-keyword">new</span> Map(); +edades.set(<span class="tok-string">"Boris"</span>, <span class="tok-number">39</span>); +edades.set(<span class="tok-string">"Liang"</span>, <span class="tok-number">22</span>); +edades.set(<span class="tok-string">"Júlia"</span>, <span class="tok-number">62</span>); -console.log(<span class="tok-string2">`Júlia tiene </span>${ages.get(<span class="tok-string">"Júlia"</span>)}<span class="tok-string2">`</span>); +console.log(<span class="tok-string2">`Júlia tiene </span>${edades.get(<span class="tok-string">"Júlia"</span>)}<span class="tok-string2">`</span>); <span class="tok-comment">// → Júlia tiene 62</span> -console.log(<span class="tok-string">"¿Se conoce la edad de Jack?"</span>, ages.has(<span class="tok-string">"Jack"</span>)); +console.log(<span class="tok-string">"¿Se conoce la edad de Jack?"</span>, edades.has(<span class="tok-string">"Jack"</span>)); <span class="tok-comment">// → ¿Se conoce la edad de Jack? false</span> -console.log(ages.has(<span class="tok-string">"toString"</span>)); +console.log(edades.has(<span class="tok-string">"toString"</span>)); <span class="tok-comment">// → false</span></pre> -<p><a class="p_ident" id="p-Nt985eLvDi" href="#p-Nt985eLvDi" tabindex="-1" role="presentation"></a>Los métodos <code>set</code>, <code>get</code> y <code>has</code> forman parte de la interfaz del objeto <code>Map</code>. 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.</p> +<p><a class="p_ident" id="p-bUX8wICy97" href="#p-bUX8wICy97" tabindex="-1" role="presentation"></a>Los métodos <code>set</code>, <code>get</code> y <code>has</code> forman parte de la interfaz del objeto <code>Map</code>. 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.</p> -<p><a class="p_ident" id="p-ImkM3PaOoc" href="#p-ImkM3PaOoc" tabindex="-1" role="presentation"></a>Si tienes un objeto simple que necesitas tratar como un mapa por alguna razón, es útil saber que <code>Object.keys</code> devuelve solo las claves <em>propias</em> de un objeto, no las del prototipo. Como alternativa al operador <code>in</code>, puedes utilizar la función <code>Object.hasOwn</code>, que ignora el prototipo del objeto.</p> +<p><a class="p_ident" id="p-ImkM3PaOoc" href="#p-ImkM3PaOoc" tabindex="-1" role="presentation"></a>Si tienes un objeto simple que necesitas tratar como un mapa por algún motivo, es útil saber que <code>Object.keys</code> devuelve solo las claves <em>propias</em> de un objeto, no las del prototipo. Como alternativa al operador <code>in</code>, puedes utilizar la función <code>Object.hasOwn</code>, que ignora el prototipo del objeto.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-KF6ERT9sZf" href="#c-KF6ERT9sZf" tabindex="-1" role="presentation"></a>console.log(Object.hasOwn({<span class="tok-definition">x</span>: <span class="tok-number">1</span>}, <span class="tok-string">"x"</span>)); <span class="tok-comment">// → true</span> @@ -314,107 +317,107 @@ <h2><a class="h_ident" id="h-UekXwIlliC" href="#h-UekXwIlliC" tabindex="-1" role <h2><a class="h_ident" id="h-D9SQL+5hu2" href="#h-D9SQL+5hu2" tabindex="-1" role="presentation"></a>Polimorfismo</h2> -<p><a class="p_ident" id="p-Ak3g9CFweG" href="#p-Ak3g9CFweG" tabindex="-1" role="presentation"></a>Cuando llamas a la función <code>String</code> (que convierte un valor a una cadena) en un objeto, llamará al método <code>toString</code> 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 <code>toString</code> para poder crear una cadena que contenga información más útil que <code>"[object Object]"</code>. También puedes hacerlo tú mismo.</p> +<p><a class="p_ident" id="p-Ak3g9CFweG" href="#p-Ak3g9CFweG" tabindex="-1" role="presentation"></a>Cuando llamas a la función <code>String</code> (que convierte un valor a una cadena) en un objeto, llamará al método <code>toString</code> 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 <code>toString</code> para poder crear una cadena que contenga información más útil que <code>"[object Object]"</code>. También puedes hacerlo tú mismo.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-z6Pvu4wS7m" href="#c-z6Pvu4wS7m" tabindex="-1" role="presentation"></a>Rabbit.prototype.toString = <span class="tok-keyword">function</span>() { - <span class="tok-keyword">return</span> <span class="tok-string2">`un conejo </span>${<span class="tok-keyword">this</span>.type}<span class="tok-string2">`</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ep4yyMjOk7" href="#c-ep4yyMjOk7" tabindex="-1" role="presentation"></a>Conejo.prototype.toString = <span class="tok-keyword">function</span>() { + <span class="tok-keyword">return</span> <span class="tok-string2">`un conejo </span>${<span class="tok-keyword">this</span>.tipo}<span class="tok-string2">`</span>; }; -console.log(String(killerRabbit)); +console.log(String(conejoAsesino)); <span class="tok-comment">// → un conejo asesino</span></pre> -<p><a class="p_ident" id="p-jc1Gz6pFUo" href="#p-jc1Gz6pFUo" tabindex="-1" role="presentation"></a>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 <code>toString</code>, cualquier tipo de objeto que accidentalmente admita esta interfaz puede ser enchufado en el código, y este podrá funcionar con él.</p> +<p><a class="p_ident" id="p-zaCry+yNDB" href="#p-zaCry+yNDB" tabindex="-1" role="presentation"></a>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 <code>toString</code>), cualquier tipo de objeto que cumpla con esta interfaz puede integrarse en el código y funcionará correctamente.</p> -<p><a class="p_ident" id="p-V0Q+YFK3xd" href="#p-V0Q+YFK3xd" tabindex="-1" role="presentation"></a>Esta técnica se llama <em>polimorfismo</em>. El código polimórfico puede trabajar con valores de diferentes formas, siempre y cuando admitan la interfaz que espera.</p> +<p><a class="p_ident" id="p-BwYn4EqktU" href="#p-BwYn4EqktU" tabindex="-1" role="presentation"></a>Esta técnica se llama <em>polimorfismo</em>. El código polimórfico puede trabajar con valores de diferentes formas, siempre y cuando admitan la interfaz que este espera.</p> -<p><a class="p_ident" id="p-J1KQDNzg8h" href="#p-J1KQDNzg8h" tabindex="-1" role="presentation"></a>Un ejemplo de una interfaz ampliamente utilizada es la de los objeto similar a un array que tiene una propiedad <code>length</code> 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 <code>forEach</code> en el <a href="05_higher_order.html">Capítulo 5</a> funciona en cualquier cosa que proporcione esta interfaz. De hecho, también lo hace <code>Array.<wbr>prototype.<wbr>forEach</code>.</p> +<p><a class="p_ident" id="p-J1KQDNzg8h" href="#p-J1KQDNzg8h" tabindex="-1" role="presentation"></a>Un ejemplo de una interfaz ampliamente utilizada es la de los objetos similares a un array, que tienen una propiedad <code>length</code> 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 <code>forEach</code> en el <a href="05_higher_order.html">Capítulo 5</a> funciona en cualquier cosa que proporcione esta interfaz. De hecho, también lo hace <code>Array.<wbr>prototype.<wbr>forEach</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-8an+KU+XJV" href="#c-8an+KU+XJV" tabindex="-1" role="presentation"></a>Array.prototype.forEach.call({ +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Pwg1e9G1W5" href="#c-Pwg1e9G1W5" tabindex="-1" role="presentation"></a>Array.prototype.forEach.call({ <span class="tok-definition">length</span>: <span class="tok-number">2</span>, <span class="tok-number">0</span>: <span class="tok-string">"A"</span>, <span class="tok-number">1</span>: <span class="tok-string">"B"</span> -}, <span class="tok-definition">elt</span> => console.log(elt)); +}, <span class="tok-definition">elemento</span> => console.log(elemento)); <span class="tok-comment">// → A</span> <span class="tok-comment">// → B</span></pre> <h2><a class="h_ident" id="h-m7QR5dYMZ9" href="#h-m7QR5dYMZ9" tabindex="-1" role="presentation"></a>Getters, setters y estáticos</h2> -<p><a class="p_ident" id="p-o1yLQEv1u3" href="#p-o1yLQEv1u3" tabindex="-1" role="presentation"></a>Las interfaces a menudo contienen propiedades simples, no solo métodos. Por ejemplo, los objetos <code>Map</code> tienen una propiedad <code>size</code> que te dice cuántas claves están almacenadas en ellos.</p> +<p><a class="p_ident" id="p-B44tD2wMDG" href="#p-B44tD2wMDG" tabindex="-1" role="presentation"></a>Las interfaces a menudo contienen propiedades simples, no solo métodos. Por ejemplo, los objetos <code>Map</code> tienen una propiedad <code>size</code> que te dice cuántas claves almacenan.</p> <p><a class="p_ident" id="p-IM3KoNxElD" href="#p-IM3KoNxElD" tabindex="-1" role="presentation"></a>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 <em>getter</em> y se definen escribiendo <code>get</code> delante del nombre del método en una expresión de objeto o declaración de clase.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Np05mJ4GVO" href="#c-Np05mJ4GVO" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">varyingSize</span> = { - <span class="tok-keyword">get</span> <span class="tok-definition">size</span>() { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-qjLRDSqyQI" href="#c-qjLRDSqyQI" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">tamañoCambiante</span> = { + <span class="tok-keyword">get</span> <span class="tok-definition">tamaño</span>() { <span class="tok-keyword">return</span> Math.floor(Math.random() * <span class="tok-number">100</span>); } }; -console.log(varyingSize.size); +console.log(tamañoCambiante.tamaño); <span class="tok-comment">// → 73</span> -console.log(varyingSize.size); +console.log(tamañoCambiante.tamaño); <span class="tok-comment">// → 49</span></pre> -<p><a class="p_ident" id="p-RVEsAqIoVf" href="#p-RVEsAqIoVf" tabindex="-1" role="presentation"></a>Cada vez que alguien lee la propiedad <code>size</code> de este objeto, se llama al método asociado. Puedes hacer algo similar cuando se escribe en una propiedad, utilizando un <em>setter</em>.</p> +<p><a class="p_ident" id="p-RVEsAqIoVf" href="#p-RVEsAqIoVf" tabindex="-1" role="presentation"></a>Cada vez que alguien lee la propiedad <code>tamaño</code> de este objeto, se llama al método asociado. Puedes hacer algo similar cuando se escribe en una propiedad, utilizando un <em>setter</em>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-7LQG88c1BA" href="#c-7LQG88c1BA" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> Temperature { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-xYSVMNQkdL" href="#c-xYSVMNQkdL" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> Temperatura { <span class="tok-definition">constructor</span>(<span class="tok-definition">celsius</span>) { <span class="tok-keyword">this</span>.celsius = celsius; } <span class="tok-keyword">get</span> <span class="tok-definition">fahrenheit</span>() { <span class="tok-keyword">return</span> <span class="tok-keyword">this</span>.celsius * <span class="tok-number">1.8</span> + <span class="tok-number">32</span>; } - <span class="tok-keyword">set</span> <span class="tok-definition">fahrenheit</span>(<span class="tok-definition">value</span>) { - <span class="tok-keyword">this</span>.celsius = (value - <span class="tok-number">32</span>) / <span class="tok-number">1.8</span>; + <span class="tok-keyword">set</span> <span class="tok-definition">fahrenheit</span>(<span class="tok-definition">valor</span>) { + <span class="tok-keyword">this</span>.celsius = (valor - <span class="tok-number">32</span>) / <span class="tok-number">1.8</span>; } - <span class="tok-keyword">static</span> <span class="tok-definition">fromFahrenheit</span>(<span class="tok-definition">value</span>) { - <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Temperature((value - <span class="tok-number">32</span>) / <span class="tok-number">1.8</span>); + <span class="tok-keyword">static</span> <span class="tok-definition">fromFahrenheit</span>(<span class="tok-definition">valor</span>) { + <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Temperatura((valor - <span class="tok-number">32</span>) / <span class="tok-number">1.8</span>); } } -<span class="tok-keyword">let</span> <span class="tok-definition">temp</span> = <span class="tok-keyword">new</span> Temperature(<span class="tok-number">22</span>); +<span class="tok-keyword">let</span> <span class="tok-definition">temp</span> = <span class="tok-keyword">new</span> Temperatura(<span class="tok-number">22</span>); console.log(temp.fahrenheit); <span class="tok-comment">// → 71.6</span> temp.fahrenheit = <span class="tok-number">86</span>; console.log(temp.celsius); <span class="tok-comment">// → 30</span></pre> -<p><a class="p_ident" id="p-B0AenGTIjn" href="#p-B0AenGTIjn" tabindex="-1" role="presentation"></a>La clase <code>Temperature</code> 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 <em>getter</em> y <em>setter</em> de <code>fahrenheit</code>.</p> +<p><a class="p_ident" id="p-B0AenGTIjn" href="#p-B0AenGTIjn" tabindex="-1" role="presentation"></a>La clase <code>Temperatura</code> 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 <em>getter</em> y <em>setter</em> de <code>fahrenheit</code>.</p> <p><a class="p_ident" id="p-nEBcTEEWRj" href="#p-nEBcTEEWRj" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-TqEGsaffU8" href="#p-TqEGsaffU8" tabindex="-1" role="presentation"></a>Dentro de una declaración de clase, los métodos o propiedades que tienen <code>static</code> escrito antes de su nombre se almacenan en el constructor. Por lo tanto, la clase <code>Temperature</code> te permite escribir <code>Temperature.<wbr>fromFahrenheit(100)</code> para crear una temperatura usando grados Fahrenheit.</p> +<p><a class="p_ident" id="p-TqEGsaffU8" href="#p-TqEGsaffU8" tabindex="-1" role="presentation"></a>Dentro de una declaración de clase, los métodos o propiedades que tienen <code>static</code> escrito antes de su nombre se almacenan en el constructor. Por lo tanto, la clase <code>Temperatura</code> te permite escribir <code>Temperatura.<wbr>fromFahrenheit(100)</code> para crear una temperatura usando grados Fahrenheit.</p> <h2><a class="h_ident" id="h-aB1RPFkM8u" href="#h-aB1RPFkM8u" tabindex="-1" role="presentation"></a>Símbolos</h2> -<p><a class="p_ident" id="p-d/50yosRZx" href="#p-d/50yosRZx" tabindex="-1" role="presentation"></a>Mencioné en el <a href="04_data.html#for_of_loop">Capítulo 4</a> que un bucle <code>for</code>/<code>of</code> 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.</p> +<p><a class="p_ident" id="p-7BGLQyz0DM" href="#p-7BGLQyz0DM" tabindex="-1" role="presentation"></a>Mencioné en el <a href="04_data.html#for_of_loop">Capítulo 4</a> que un bucle <code>for</code>/<code>of</code> 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.</p> <p><a class="p_ident" id="p-jntaX3QeYC" href="#p-jntaX3QeYC" tabindex="-1" role="presentation"></a>Es posible que múltiples interfaces utilicen el mismo nombre de propiedad para diferentes cosas. Por ejemplo, en objetos similares a arrays, <code>length</code> 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 <code>length</code> para proporcionar la longitud de la ruta en metros. No sería posible que un objeto cumpla con ambas interfaces.</p> -<p><a class="p_ident" id="p-Kj42DYTowS" href="#p-Kj42DYTowS" tabindex="-1" role="presentation"></a>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 <em>realmente</em> no entrara en conflicto con ninguna otra. Por lo tanto, en 2015, se agregaron los <em>símbolos</em> al lenguaje.</p> +<p><a class="p_ident" id="p-Kj42DYTowS" href="#p-Kj42DYTowS" tabindex="-1" role="presentation"></a>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 <em>realmente</em> no entrara en conflicto con ninguna otra. Por lo tanto, en 2015, se agregaron los <em>símbolos</em> al lenguaje.</p> -<p><a class="p_ident" id="p-0m5vk1/JjF" href="#p-0m5vk1/JjF" tabindex="-1" role="presentation"></a>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 <code>Symbol</code>. A diferencia de las cadenas, los símbolos recién creados son únicos: no puedes crear el mismo símbolo dos veces.</p> +<p><a class="p_ident" id="p-0m5vk1/JjF" href="#p-0m5vk1/JjF" tabindex="-1" role="presentation"></a>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 <code>Symbol</code>. A diferencia de las cadenas, un símbolo recién creado es único: no puedes crear el mismo símbolo dos veces.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-dlb4VMgEOy" href="#c-dlb4VMgEOy" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">sym</span> = Symbol(<span class="tok-string">"nombre"</span>); -console.log(sym == Symbol(<span class="tok-string">"nombre"</span>)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-IpNGrKi6ks" href="#c-IpNGrKi6ks" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">símbolo</span> = Symbol(<span class="tok-string">"nombre"</span>); +console.log(símbolo == Symbol(<span class="tok-string">"nombre"</span>)); <span class="tok-comment">// → false</span> -Rabbit.prototype[sym] = <span class="tok-number">55</span>; -console.log(killerRabbit[sym]); +Conejo.prototype[símbolo] = <span class="tok-number">55</span>; +console.log(conejoAsesino[símbolo]); <span class="tok-comment">// → 55</span></pre> -<p><a class="p_ident" id="p-8lum8GnTmL" href="#p-8lum8GnTmL" tabindex="-1" role="presentation"></a>La cadena que pasas a <code>Symbol</code> 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.</p> +<p><a class="p_ident" id="p-8lum8GnTmL" href="#p-8lum8GnTmL" tabindex="-1" role="presentation"></a>La cadena que pasas a <code>Symbol</code> 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.</p> <p><a class="p_ident" id="p-wc+g/+bClz" href="#p-wc+g/+bClz" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5zAKW7mGvi" href="#c-5zAKW7mGvi" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">longitud</span> = Symbol(<span class="tok-string">"longitud"</span>); -Array.prototype[longitud] = <span class="tok-number">0</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-647+NgW7T5" href="#c-647+NgW7T5" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">length</span> = Symbol(<span class="tok-string">"length"</span>); +Array.prototype[length] = <span class="tok-number">0</span>; console.log([<span class="tok-number">1</span>, <span class="tok-number">2</span>].length); <span class="tok-comment">// → 2</span> -console.log([<span class="tok-number">1</span>, <span class="tok-number">2</span>][longitud]); +console.log([<span class="tok-number">1</span>, <span class="tok-number">2</span>][length]); <span class="tok-comment">// → 0</span></pre> -<p><a class="p_ident" id="p-JoH+KKVaoS" href="#p-JoH+KKVaoS" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-Xcha9YlNLC" href="#p-Xcha9YlNLC" tabindex="-1" role="presentation"></a>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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-aw+eXaoqpG" href="#c-aw+eXaoqpG" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">miViaje</span> = { <span class="tok-definition">longitud</span>: <span class="tok-number">2</span>, @@ -425,75 +428,75 @@ <h2><a class="h_ident" id="h-aB1RPFkM8u" href="#h-aB1RPFkM8u" tabindex="-1" role console.log(miViaje[longitud], miViaje.longitud); <span class="tok-comment">// → 21500 2</span></pre> -<h2><a class="h_ident" id="h-lKslrJAdAo" href="#h-lKslrJAdAo" tabindex="-1" role="presentation"></a>La interfaz del iterador</h2> +<h2><a class="h_ident" id="h-UuEOM0agDA" href="#h-UuEOM0agDA" tabindex="-1" role="presentation"></a>La interfaz iterador</h2> <p><a class="p_ident" id="p-y5CYRUDQhn" href="#p-y5CYRUDQhn" tabindex="-1" role="presentation"></a>Se espera que el objeto proporcionado a un bucle <code>for</code>/<code>of</code> sea <em>iterable</em>. Esto significa que tiene un método nombrado con el símbolo <code>Symbol.iterator</code> (un valor de símbolo definido por el lenguaje, almacenado como una propiedad de la función <code>Symbol</code>).</p> -<p><a class="p_ident" id="p-CoqiNFo1//" href="#p-CoqiNFo1//" tabindex="-1" role="presentation"></a>Cuando se llama, ese método debería devolver un objeto que proporcione una segunda interfaz, <em>iterador</em>. Este es lo que realmente itera. Tiende un método <code>next</code> que devuelve el próximo resultado. Ese resultado debería ser un objeto con una propiedad <code>value</code> que proporciona el siguiente valor, si lo hay, y una propiedad <code>done</code>, que debería ser <code>true</code> cuando no hay más resultados y <code>false</code> en caso contrario.</p> +<p><a class="p_ident" id="p-CoqiNFo1//" href="#p-CoqiNFo1//" tabindex="-1" role="presentation"></a>Cuando se llama, ese método debería devolver un objeto que proporcione una segunda interfaz, <em>iterador</em>. Esto es lo que realmente se itera. Tiene un método <code>next</code> que devuelve el siguiente resultado. Ese resultado debería ser un objeto con una propiedad <code>value</code> que proporciona el siguiente valor, si lo hay, y una propiedad <code>done</code>, que debería ser <code>true</code> cuando no hay más resultados y <code>false</code> en caso contrario.</p> -<p><a class="p_ident" id="p-UNu4gEH21W" href="#p-UNu4gEH21W" tabindex="-1" role="presentation"></a>Ten en cuenta que los nombres de propiedad <code>next</code>, <code>value</code> y <code>done</code> son simples cadenas, no símbolos. Solo <code>Symbol.iterator</code>, que probablemente se agregará a <em>muchos</em> objetos diferentes, es un símbolo real.</p> +<p><a class="p_ident" id="p-CGkpohg3dm" href="#p-CGkpohg3dm" tabindex="-1" role="presentation"></a>Ten en cuenta que los nombres de propiedad <code>next</code>, <code>value</code> y <code>done</code> son simples cadenas, no símbolos. Solo <code>Symbol.iterator</code>, que probablemente se agregará a <em>muchos</em> objetos diferentes, es realmente un símbolo.</p> <p><a class="p_ident" id="p-YKPX2XUawZ" href="#p-YKPX2XUawZ" tabindex="-1" role="presentation"></a>Podemos usar esta interfaz directamente nosotros mismos.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-/tRId5K0nh" href="#c-/tRId5K0nh" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">okIterador</span> = <span class="tok-string">"OK"</span>[Symbol.iterator](); -console.log(okIterador.next()); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-h9hI8DAzQx" href="#c-h9hI8DAzQx" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">iteradorOk</span> = <span class="tok-string">"OK"</span>[Symbol.iterator](); +console.log(iteradorOk.next()); <span class="tok-comment">// → {value: "O", done: false}</span> -console.log(okIterador.next()); +console.log(iteradorOk.next()); <span class="tok-comment">// → {value: "K", done: false}</span> -console.log(okIterador.next()); +console.log(iteradorOk.next()); <span class="tok-comment">// → {value: undefined, done: true}</span></pre> <p><a class="p_ident" id="p-pWWhlMCrry" href="#p-pWWhlMCrry" tabindex="-1" role="presentation"></a>Implementemos una estructura de datos iterable similar a la lista enlazada del ejercicio en el <a href="04_data.html">Capítulo 4</a>. Esta vez escribiremos la lista como una clase.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-gbtYx+2BOB" href="#c-gbtYx+2BOB" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> List { - <span class="tok-definition">constructor</span>(<span class="tok-definition">value</span>, <span class="tok-definition">rest</span>) { - <span class="tok-keyword">this</span>.value = value; - <span class="tok-keyword">this</span>.rest = rest; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-k3YBPkgorU" href="#c-k3YBPkgorU" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> Lista { + <span class="tok-definition">constructor</span>(<span class="tok-definition">valor</span>, <span class="tok-definition">resto</span>) { + <span class="tok-keyword">this</span>.valor = valor; + <span class="tok-keyword">this</span>.resto = resto; } - <span class="tok-keyword">get</span> <span class="tok-definition">length</span>() { - <span class="tok-keyword">return</span> <span class="tok-number">1</span> + (<span class="tok-keyword">this</span>.rest ? <span class="tok-keyword">this</span>.rest.length : <span class="tok-number">0</span>); + <span class="tok-keyword">get</span> <span class="tok-definition">longitud</span>() { + <span class="tok-keyword">return</span> <span class="tok-number">1</span> + (<span class="tok-keyword">this</span>.resto ? <span class="tok-keyword">this</span>.resto.longitud : <span class="tok-number">0</span>); } - <span class="tok-keyword">static</span> <span class="tok-definition">fromArray</span>(<span class="tok-definition">array</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">result</span> = <span class="tok-keyword">null</span>; + <span class="tok-keyword">static</span> <span class="tok-definition">desdeArray</span>(<span class="tok-definition">array</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">resultado</span> = <span class="tok-keyword">null</span>; <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = array.length - <span class="tok-number">1</span>; i >= <span class="tok-number">0</span>; i--) { - result = <span class="tok-keyword">new</span> <span class="tok-keyword">this</span>(array[i], result); + resultado = <span class="tok-keyword">new</span> <span class="tok-keyword">this</span>(array[i], resultado); } <span class="tok-keyword">return</span> result; } }</pre> -<p><a class="p_ident" id="p-c2tvPOgW+J" href="#p-c2tvPOgW+J" tabindex="-1" role="presentation"></a>Toma en cuenta que <code>this</code>, 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.</p> +<p><a class="p_ident" id="p-JseqPIhhI0" href="#p-JseqPIhhI0" tabindex="-1" role="presentation"></a>Ten en cuenta que <code>this</code>, 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.</p> -<p><a class="p_ident" id="p-MDb4MF0B0v" href="#p-MDb4MF0B0v" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-MDb4MF0B0v" href="#p-MDb4MF0B0v" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-xNhGMf/Lhe" href="#c-xNhGMf/Lhe" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> ListIterator { - <span class="tok-definition">constructor</span>(<span class="tok-definition">list</span>) { - <span class="tok-keyword">this</span>.list = list; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-OziqKwgx9X" href="#c-OziqKwgx9X" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> iteradorDeLista { + <span class="tok-definition">constructor</span>(<span class="tok-definition">lista</span>) { + <span class="tok-keyword">this</span>.lista = lista; } <span class="tok-definition">next</span>() { - <span class="tok-keyword">if</span> (<span class="tok-keyword">this</span>.list == <span class="tok-keyword">null</span>) { + <span class="tok-keyword">if</span> (<span class="tok-keyword">this</span>.lista == <span class="tok-keyword">null</span>) { <span class="tok-keyword">return</span> { <span class="tok-definition">done</span>: true }; } - <span class="tok-keyword">let</span> <span class="tok-definition">value</span> = <span class="tok-keyword">this</span>.list.value; - <span class="tok-keyword">this</span>.list = <span class="tok-keyword">this</span>.list.rest; + <span class="tok-keyword">let</span> <span class="tok-definition">value</span> = <span class="tok-keyword">this</span>.lista.valor; + <span class="tok-keyword">this</span>.lista = <span class="tok-keyword">this</span>.lista.resto; <span class="tok-keyword">return</span> { <span class="tok-definition">value</span>, <span class="tok-definition">done</span>: false }; } }</pre> -<p><a class="p_ident" id="p-xdlBL0DX57" href="#p-xdlBL0DX57" tabindex="-1" role="presentation"></a>La clase realiza un seguimiento del progreso de la iteración a través de la lista actualizando su propiedad <code>list</code> 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).</p> +<p><a class="p_ident" id="p-xdlBL0DX57" href="#p-xdlBL0DX57" tabindex="-1" role="presentation"></a>La clase realiza un seguimiento del progreso de la iteración a través de la lista actualizando su propiedad <code>lista</code> 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).</p> -<p><a class="p_ident" id="p-YuxvpCh8Ac" href="#p-YuxvpCh8Ac" tabindex="-1" role="presentation"></a>Ahora configuraremos la clase <code>List</code> 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.</p> +<p><a class="p_ident" id="p-1GeBL6rlOU" href="#p-1GeBL6rlOU" tabindex="-1" role="presentation"></a>Ahora configuraremos la clase <code>Lista</code> 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-o1rzfIRpoY" href="#c-o1rzfIRpoY" tabindex="-1" role="presentation"></a>List.prototype[Symbol.iterator] = <span class="tok-keyword">function</span>() { - <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> ListIterator(<span class="tok-keyword">this</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-QCLl5yqdSM" href="#c-QCLl5yqdSM" tabindex="-1" role="presentation"></a>Lista.prototype[Symbol.iterator] = <span class="tok-keyword">function</span>() { + <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> iteradorDeLista(<span class="tok-keyword">this</span>); };</pre> <p><a class="p_ident" id="p-wr1mzaCiLZ" href="#p-wr1mzaCiLZ" tabindex="-1" role="presentation"></a>Ahora podemos iterar sobre una lista con <code>for</code>/<code>of</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-RNwFlMxUfH" href="#c-RNwFlMxUfH" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">lista</span> = List.fromArray([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>]); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-8dPSOLXS3o" href="#c-8dPSOLXS3o" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">lista</span> = Lista.desdeArray([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>]); <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">elemento</span> <span class="tok-keyword">of</span> lista) { console.log(elemento); } @@ -501,84 +504,84 @@ <h2><a class="h_ident" id="h-lKslrJAdAo" href="#h-lKslrJAdAo" tabindex="-1" role <span class="tok-comment">// → 2</span> <span class="tok-comment">// → 3</span></pre> -<p><a class="p_ident" id="p-mcMmI6e+om" href="#p-mcMmI6e+om" tabindex="-1" role="presentation"></a>La sintaxis <code>...</code> en notación de arrays y en llamadas a funciones funciona de forma similar con cualquier objeto iterable. Por ejemplo, puedes usar <code>[...valor]</code> para crear un array que contenga los elementos de un objeto iterable arbitrario.</p> +<p><a class="p_ident" id="p-mcMmI6e+om" href="#p-mcMmI6e+om" tabindex="-1" role="presentation"></a>La sintaxis <code>...</code> en notación de arrays y en llamadas a funciones funciona de menaera similar con cualquier objeto iterable. Por ejemplo, puedes usar <code>[...valor]</code> para crear un array que contenga los elementos de un objeto iterable arbitrario.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-GMZyzfVSc7" href="#c-GMZyzfVSc7" tabindex="-1" role="presentation"></a>console.log([... <span class="tok-string">"PCI"</span>]); <span class="tok-comment">// → ["P", "C", "I"]</span></pre> <h2><a class="h_ident" id="h-uawVYpHl7k" href="#h-uawVYpHl7k" tabindex="-1" role="presentation"></a>Herencia</h2> -<p><a class="p_ident" id="p-jWxiKM17aJ" href="#p-jWxiKM17aJ" tabindex="-1" role="presentation"></a>Imaginemos que necesitamos un tipo de lista, bastante parecido a la clase <code>List</code> que vimos anteriormente, pero como siempre estaremos preguntando por su longitud, no queremos tener que recorrer su <code>rest</code> cada vez, en su lugar, queremos almacenar la longitud en cada instancia para un acceso eficiente.</p> +<p><a class="p_ident" id="p-jWxiKM17aJ" href="#p-jWxiKM17aJ" tabindex="-1" role="presentation"></a>Imaginemos que necesitamos un tipo de lista, bastante parecido a la clase <code>Lista</code> que vimos anteriormente, pero como siempre estaremos preguntando por su longitud, no queremos tener que recorrer su <code>resto</code> cada vez, en su lugar, queremos almacenar la longitud en cada instancia para un acceso eficiente.</p> -<p><a class="p_ident" id="p-Q/aOCukYIj" href="#p-Q/aOCukYIj" tabindex="-1" role="presentation"></a>El sistema de prototipos de JavaScript permite crear una <em>nueva</em> 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 <code>getter</code> de <code>length</code>.</p> +<p><a class="p_ident" id="p-Q/aOCukYIj" href="#p-Q/aOCukYIj" tabindex="-1" role="presentation"></a>El sistema de prototipos de JavaScript permite crear una <em>nueva</em> 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 <code>getter</code> de <code>longitud</code>.</p> <p><a class="p_ident" id="p-9CQDFcjJfI" href="#p-9CQDFcjJfI" tabindex="-1" role="presentation"></a>En términos de programación orientada a objetos, esto se llama <em>herencia</em>. La nueva clase hereda propiedades y comportamientos de la clase antigua.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-joKzyMTV/2" href="#c-joKzyMTV/2" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> LengthList <span class="tok-keyword">extends</span> List { - <span class="tok-definition">#length</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5FbQ9kTJFe" href="#c-5FbQ9kTJFe" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> ListaLongitud <span class="tok-keyword">extends</span> Lista { + <span class="tok-definition">#longitud</span>; - <span class="tok-definition">constructor</span>(<span class="tok-definition">valor</span>, <span class="tok-definition">rest</span>) { - <span class="tok-atom">super</span>(valor, rest); - <span class="tok-keyword">this</span>.#length = <span class="tok-atom">super</span>.length; + <span class="tok-definition">constructor</span>(<span class="tok-definition">valor</span>, <span class="tok-definition">resto</span>) { + <span class="tok-atom">super</span>(valor, resto); + <span class="tok-keyword">this</span>.#longitud = <span class="tok-atom">super</span>.longitud; } - <span class="tok-keyword">get</span> <span class="tok-definition">length</span>() { - <span class="tok-keyword">return</span> <span class="tok-keyword">this</span>.#length; + <span class="tok-keyword">get</span> <span class="tok-definition">longitud</span>() { + <span class="tok-keyword">return</span> <span class="tok-keyword">this</span>.#longitud; } } -console.log(LengthList.fromArray([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>]).length); +console.log(ListaLongitud.fromArray([<span class="tok-number">1</span>, <span class="tok-number">2</span>, <span class="tok-number">3</span>]).length); <span class="tok-comment">// → 3</span></pre> -<p><a class="p_ident" id="p-SUbVcV5osC" href="#p-SUbVcV5osC" tabindex="-1" role="presentation"></a>El uso de la palabra <code>extends</code> indica que esta clase no debería basarse directamente en el prototipo predeterminado de <code>Object</code>, sino en alguna otra clase. Esta se llama la <em>superclase</em>. La clase derivada es la <em>subclase</em>.</p> +<p><a class="p_ident" id="p-SUbVcV5osC" href="#p-SUbVcV5osC" tabindex="-1" role="presentation"></a>El uso de la palabra <code>extends</code> indica que esta clase no debería basarse directamente en el prototipo predeterminado de <code>Object</code>, sino en alguna otra clase. A esta se le llama la <em>superclase</em>. La clase derivada es la <em>subclase</em>.</p> -<p><a class="p_ident" id="p-yxUGRgw6LO" href="#p-yxUGRgw6LO" tabindex="-1" role="presentation"></a>Para inicializar una instancia de <code>LengthList</code>, el constructor llama al constructor de su superclase a través de la palabra clave <code>super</code>. Esto es necesario porque si este nuevo objeto se va a comportar (aproximadamente) como una <code>List</code>, va a necesitar las propiedades de instancia que tienen las listas.</p> +<p><a class="p_ident" id="p-yxUGRgw6LO" href="#p-yxUGRgw6LO" tabindex="-1" role="presentation"></a>Para inicializar una instancia de <code>ListaLongitud</code>, el constructor llama al constructor de su superclase a través de la palabra clave <code>super</code>. Esto es necesario porque si este nuevo objeto se va a comportar (aproximadamente) como una <code>Lista</code>, va a necesitar las propiedades de instancia que tienen las listas.</p> -<p><a class="p_ident" id="p-nMcLF4NRiL" href="#p-nMcLF4NRiL" tabindex="-1" role="presentation"></a>Luego, el constructor almacena la longitud de la lista en una propiedad privada. Si hubiéramos escrito <code>this.longitud</code> ahí, se habría llamado al getter de la propia clase, lo cual no funciona aún, ya que <code>#longitud</code> aún no ha sido completado. Podemos usar <code>super.algo</code> para llamar a métodos y getters en el prototipo de la superclase, lo cual a menudo es útil.</p> +<p><a class="p_ident" id="p-nMcLF4NRiL" href="#p-nMcLF4NRiL" tabindex="-1" role="presentation"></a>Luego, el constructor almacena la longitud de la lista en una propiedad privada. Si hubiéramos escrito <code>this.longitud</code> ahí, se habría llamado al getter de la propia clase, lo cual no funciona aún, ya que <code>#longitud</code> aún no se ha rellenado. Podemos usar <code>super.algo</code> para llamar a métodos y getters en el prototipo de la superclase, lo cual a menudo es útil.</p> -<p><a class="p_ident" id="p-hSr/UaPC9M" href="#p-hSr/UaPC9M" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-hSr/UaPC9M" href="#p-hSr/UaPC9M" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-vq1pcEddDz" href="#p-vq1pcEddDz" tabindex="-1" role="presentation"></a>Mientras que encapsulación y polimorfismo se pueden utilizar para <em>separar</em> las piezas de código unas de otras, reduciendo el enredo del programa en general, herencia fundamentalmente ata clases juntas, creando <em>más</em> 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).</p> +<p><a class="p_ident" id="p-vq1pcEddDz" href="#p-vq1pcEddDz" tabindex="-1" role="presentation"></a>Mientras que encapsulación y polimorfismo se pueden utilizar para <em>separar</em> las piezas de código unas de otras, reduciendo el enredo del programa en general, la herencia fundamentalmente ata las clases, creando <em>más</em> 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).</p> <h2><a class="h_ident" id="h-bPUPRLPX+i" href="#h-bPUPRLPX+i" tabindex="-1" role="presentation"></a>El operador instanceof</h2> -<p><a class="p_ident" id="p-xHBw7+XALS" href="#p-xHBw7+XALS" tabindex="-1" role="presentation"></a>A veces es útil saber si un objeto se derivó de una clase específica. Para esto, JavaScript proporciona un operador binario llamado <code>instanceof</code>.</p> +<p><a class="p_ident" id="p-xHBw7+XALS" href="#p-xHBw7+XALS" tabindex="-1" role="presentation"></a>A veces es útil saber si un objeto hereda de una clase específica. Para esto, JavaScript proporciona un operador binario llamado <code>instanceof</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-wtZ2QK2G5a" href="#c-wtZ2QK2G5a" tabindex="-1" role="presentation"></a>console.log( - <span class="tok-keyword">new</span> LengthList(<span class="tok-number">1</span>, <span class="tok-keyword">null</span>) <span class="tok-keyword">instanceof</span> LengthList); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-CLrKEM6k+W" href="#c-CLrKEM6k+W" tabindex="-1" role="presentation"></a>console.log( + <span class="tok-keyword">new</span> ListaLongitud(<span class="tok-number">1</span>, <span class="tok-keyword">null</span>) <span class="tok-keyword">instanceof</span> ListaLongitud); <span class="tok-comment">// → true</span> -console.log(<span class="tok-keyword">new</span> LengthList(<span class="tok-number">2</span>, <span class="tok-keyword">null</span>) <span class="tok-keyword">instanceof</span> List); +console.log(<span class="tok-keyword">new</span> ListaLongitud(<span class="tok-number">2</span>, <span class="tok-keyword">null</span>) <span class="tok-keyword">instanceof</span> Lista); <span class="tok-comment">// → true</span> -console.log(<span class="tok-keyword">new</span> List(<span class="tok-number">3</span>, <span class="tok-keyword">null</span>) <span class="tok-keyword">instanceof</span> LengthList); +console.log(<span class="tok-keyword">new</span> Lista(<span class="tok-number">3</span>, <span class="tok-keyword">null</span>) <span class="tok-keyword">instanceof</span> ListaLongitud); <span class="tok-comment">// → false</span> console.log([<span class="tok-number">1</span>] <span class="tok-keyword">instanceof</span> Array); <span class="tok-comment">// → true</span></pre> -<p><a class="p_ident" id="p-FZsdmcK/El" href="#p-FZsdmcK/El" tabindex="-1" role="presentation"></a>El operador podrá ver a través de tipos heredados, por lo que un <code>LengthList</code> es una instancia de <code>List</code>. El operador también se puede aplicar a constructores estándar como <code>Array</code>. Casi todo objeto es una instancia de <code>Object</code>.</p> +<p><a class="p_ident" id="p-FZsdmcK/El" href="#p-FZsdmcK/El" tabindex="-1" role="presentation"></a>El operador podrá ver a través de tipos heredados, por lo que un <code>ListaLongitud</code> es una instancia de <code>Lista</code>. El operador también se puede aplicar a constructores estándar como <code>Array</code>. Casi todo objeto es una instancia de <code>Object</code>.</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-0qRY8zu5VE" href="#p-0qRY8zu5VE" tabindex="-1" role="presentation"></a>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 <code>Object.prototype</code> como su prototipo.</p> +<p><a class="p_ident" id="p-0WnxZM+B+h" href="#p-0WnxZM+B+h" tabindex="-1" role="presentation"></a>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 <code>Object.prototype</code>.</p> -<p><a class="p_ident" id="p-4mpQle7DxG" href="#p-4mpQle7DxG" tabindex="-1" role="presentation"></a>Los constructores, que son funciones cuyos nombres generalmente comienzan con una letra mayúscula, se pueden usar con el operador <code>new</code> para crear nuevos objetos. El prototipo del nuevo objeto será el objeto encontrado en la propiedad <code>prototype</code> 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 <code>class</code> que proporciona una forma clara de definir un constructor y su prototipo.</p> +<p><a class="p_ident" id="p-4mpQle7DxG" href="#p-4mpQle7DxG" tabindex="-1" role="presentation"></a>Los constructores, que son funciones cuyos nombres generalmente comienzan con una letra mayúscula, se pueden usar con el operador <code>new</code> para crear nuevos objetos. El prototipo del nuevo objeto será el objeto encontrado en la propiedad <code>prototype</code> 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 <code>class</code> que proporciona una forma clara de definir un constructor y su prototipo.</p> -<p><a class="p_ident" id="p-ufZoyPJWEE" href="#p-ufZoyPJWEE" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-ufZoyPJWEE" href="#p-ufZoyPJWEE" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-V4iOZ7bWJx" href="#p-V4iOZ7bWJx" tabindex="-1" role="presentation"></a>El operador <code>instanceof</code> puede, dado un objeto y un constructor, decirte si ese objeto es una instancia de ese constructor.</p> -<p><a class="p_ident" id="p-5cNIF5voif" href="#p-5cNIF5voif" tabindex="-1" role="presentation"></a>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 <em>encapsulados</em>, escondidos detrás de la interfaz. Puedes usar propiedades privadas para ocultar una parte de tu objeto del mundo exterior.</p> +<p><a class="p_ident" id="p-iqieXpU7pW" href="#p-iqieXpU7pW" tabindex="-1" role="presentation"></a>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 <em>encapsulados</em>, escondidos detrás de la interfaz. Puedes usar propiedades privadas para ocultar una parte de tu objeto al mundo exterior.</p> -<p><a class="p_ident" id="p-7n4enOfcU6" href="#p-7n4enOfcU6" tabindex="-1" role="presentation"></a>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 <em>polimorfismo</em>.</p> +<p><a class="p_ident" id="p-8vs0kW8IjA" href="#p-8vs0kW8IjA" tabindex="-1" role="presentation"></a>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 <em>polimorfismo</em>.</p> <p><a class="p_ident" id="p-kqIe6aGWXO" href="#p-kqIe6aGWXO" tabindex="-1" role="presentation"></a>Cuando se implementan múltiples clases que difieren solo en algunos detalles, puede ser útil escribir las nuevas clases como <em>subclases</em> de una clase existente, <em>heredando</em> parte de su comportamiento.</p> <h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2> -<h3 id="exercise_vector"><a class="i_ident" id="i-k7TRkMJ5GZ" href="#i-k7TRkMJ5GZ" tabindex="-1" role="presentation"></a>Un tipo de vector</h3> +<h3 id="exercise_vector"><a class="i_ident" id="i-RXP1a4qVB4" href="#i-RXP1a4qVB4" tabindex="-1" role="presentation"></a>Un tipo vector</h3> <p><a class="p_ident" id="p-F9C1LVy+Xt" href="#p-F9C1LVy+Xt" tabindex="-1" role="presentation"></a>Escribe una clase <code>Vec</code> que represente un vector en el espacio bidimensional. Toma los parámetros <code>x</code> e <code>y</code> (números), que debería guardar en propiedades del mismo nombre.</p> -<p><a class="p_ident" id="p-O2b2V/Nzyc" href="#p-O2b2V/Nzyc" tabindex="-1" role="presentation"></a>Dale a la clase <code>Vec</code> dos métodos en su prototipo, <code>plus</code> y <code>minus</code>, que tomen otro vector como parámetro y devuelvan un nuevo vector que tenga la suma o la diferencia de los valores <em>x</em> e <em>y</em> de los dos vectores (<code>this</code> y el parámetro).</p> +<p><a class="p_ident" id="p-4KF0oH4nly" href="#p-4KF0oH4nly" tabindex="-1" role="presentation"></a>Dale al prototipo de <code>Vec</code> dos métodos, <code>plus</code> y <code>minus</code>, que tomen otro vector como parámetro y devuelvan un nuevo vector que tenga la suma o la diferencia de los valores <em>x</em> e <em>y</em> de los dos vectores (<code>this</code> y el parámetro).</p> <p><a class="p_ident" id="p-NRjy/7e22k" href="#p-NRjy/7e22k" tabindex="-1" role="presentation"></a>Agrega una propiedad getter <code>length</code> al prototipo que calcule la longitud del vector, es decir, la distancia del punto (<em>x</em>, <em>y</em>) desde el origen (0, 0).</p> @@ -593,17 +596,17 @@ <h3 id="exercise_vector"><a class="i_ident" id="i-k7TRkMJ5GZ" href="#i-k7TRkMJ5G <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-yPiKiyo2sU" href="#p-yPiKiyo2sU" tabindex="-1" role="presentation"></a>Mira de nuevo el ejemplo de la clase <code>Rabbit</code> si no estás seguro de cómo se ven las declaraciones de <code>class</code>.</p> +<p><a class="p_ident" id="p-yPiKiyo2sU" href="#p-yPiKiyo2sU" tabindex="-1" role="presentation"></a>Mira de nuevo el ejemplo de la clase <code>Conejo</code> si no estás seguro de cómo se ven las declaraciones de <code>class</code>.</p> -<p><a class="p_ident" id="p-t2AaqFHh1u" href="#p-t2AaqFHh1u" tabindex="-1" role="presentation"></a>Agregar una propiedad getter al constructor se puede hacer poniendo la palabra <code>get</code> 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<sup>2</sup> + y<sup>2</sup>) es el número que buscas. <code>Math.sqrt</code> es la forma de calcular una raíz cuadrada en JavaScript y <code>x ** 2</code> se puede usar para elevar al cuadrado un número.</p> +<p><a class="p_ident" id="p-t2AaqFHh1u" href="#p-t2AaqFHh1u" tabindex="-1" role="presentation"></a>Agregar una propiedad getter al constructor se puede hacer poniendo la palabra <code>get</code> antes del nombre del método. Para calcular la distancia desde (0, 0) hasta (<em>x</em>, <em>y</em>), 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 <em>x</em> más el cuadrado de la coordenada <em>y</em>. Por lo tanto, √(<em>x</em><sup>2</sup> + <em>y</em><sup>2</sup>) es el número que buscas. <code>Math.sqrt</code> es la forma de calcular una raíz cuadrada en JavaScript y <code>x ** 2</code> se puede usar para elevar al cuadrado un número.</p> </div></details> <h3><a class="i_ident" id="i-g/PrvGqpxG" href="#i-g/PrvGqpxG" tabindex="-1" role="presentation"></a>Grupos</h3> -<p id="groups"><a class="p_ident" id="p-0LFs4AAQ5b" href="#p-0LFs4AAQ5b" tabindex="-1" role="presentation"></a>El entorno estándar de JavaScript proporciona otra estructura de datos llamada <code>Set</code>. Al igual que una instancia de <code>Map</code>, un conjunto contiene una colección de valores. A diferencia de <code>Map</code>, 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.</p> +<p id="groups"><a class="p_ident" id="p-0LFs4AAQ5b" href="#p-0LFs4AAQ5b" tabindex="-1" role="presentation"></a>El entorno estándar de JavaScript proporciona otra estructura de datos llamada <code>Set</code>. Al igual que una instancia de <code>Map</code>, un conjunto contiene una colección de valores. A diferencia de <code>Map</code>, 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.</p> -<p><a class="p_ident" id="p-o2kNQq27/9" href="#p-o2kNQq27/9" tabindex="-1" role="presentation"></a>Escribe una clase llamada <code>Group</code> (ya que <code>Set</code> está siendo utilizado). Al igual que <code>Set</code>, tiene los métodos <code>add</code>, <code>delete</code> y <code>has</code>. Su constructor crea un grupo vacío, <code>add</code> agrega un valor al grupo (pero solo si aún no es miembro), <code>delete</code> elimina su argumento del grupo (si era miembro), y <code>has</code> devuelve un valor booleano que indica si su argumento es miembro del grupo.</p> +<p><a class="p_ident" id="p-o2kNQq27/9" href="#p-o2kNQq27/9" tabindex="-1" role="presentation"></a>Escribe una clase llamada <code>Group</code> (ya que <code>Set</code> está siendo utilizado). Como <code>Set</code>, tiene que tener los métodos <code>add</code>, <code>delete</code> y <code>has</code>. Su constructor crea un grupo vacío, <code>add</code> agrega un valor al grupo (pero solo si aún no es miembro), <code>delete</code> elimina su argumento del grupo (si era miembro), y <code>has</code> devuelve un valor booleano que indica si su argumento es miembro del grupo.</p> <p><a class="p_ident" id="p-YdE9RBfWsV" href="#p-YdE9RBfWsV" tabindex="-1" role="presentation"></a>Usa el operador <code>===</code>, o algo equivalente como <code>indexOf</code>, para determinar si dos valores son iguales.</p> @@ -627,7 +630,7 @@ <h3><a class="i_ident" id="i-g/PrvGqpxG" href="#i-g/PrvGqpxG" tabindex="-1" role <p><a class="p_ident" id="p-7oedrRBDw1" href="#p-7oedrRBDw1" tabindex="-1" role="presentation"></a>La forma más sencilla de hacer esto es almacenar un array de miembros del grupo en una propiedad de instancia. Los métodos <code>includes</code> o <code>indexOf</code> se pueden usar para verificar si un valor dado está en el array.</p> -<p><a class="p_ident" id="p-HEN3NaL8rR" href="#p-HEN3NaL8rR" tabindex="-1" role="presentation"></a>El constructor de tu clase puede establecer la colección de miembros en un array vacío. Cuando se llama a <code>add</code>, debe verificar si el valor dado está en el array o agregarlo, por ejemplo con <code>push</code>, de lo contrario.</p> +<p><a class="p_ident" id="p-HEN3NaL8rR" href="#p-HEN3NaL8rR" tabindex="-1" role="presentation"></a>El constructor de tu clase puede establecer la colección de miembros en un array vacío. Cuando se llama a <code>add</code>, debe verificar si el valor dado está en el array y agregarlo, por ejemplo con <code>push</code>, de lo contrario.</p> <p><a class="p_ident" id="p-+nb0kbrnJv" href="#p-+nb0kbrnJv" tabindex="-1" role="presentation"></a>Eliminar un elemento de un array, en <code>delete</code>, es menos directo, pero puedes usar <code>filter</code> 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.</p> @@ -637,11 +640,11 @@ <h3><a class="i_ident" id="i-g/PrvGqpxG" href="#i-g/PrvGqpxG" tabindex="-1" role <h3><a class="i_ident" id="i-HCxu3+WnnE" href="#i-HCxu3+WnnE" tabindex="-1" role="presentation"></a>Grupos iterables</h3> -<p id="group_iterator"><a class="p_ident" id="p-J+eIbxDczY" href="#p-J+eIbxDczY" tabindex="-1" role="presentation"></a>Haz que la clase <code>Group</code> 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.</p> +<p id="group_iterator"><a class="p_ident" id="p-J+eIbxDczY" href="#p-J+eIbxDczY" tabindex="-1" role="presentation"></a>Haz que la clase <code>Group</code> 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.</p> -<p><a class="p_ident" id="p-4GK8DWwORP" href="#p-4GK8DWwORP" tabindex="-1" role="presentation"></a>Si utilizaste un array para representar los miembros del grupo, no devuelvas simplemente el iterador creado al llamar al método <code>Symbol.iterator</code> en el array. Eso funcionaría, pero va en contra del propósito de este ejercicio.</p> +<p><a class="p_ident" id="p-UVQ3SuwlK7" href="#p-UVQ3SuwlK7" tabindex="-1" role="presentation"></a>Si usaste un array para representar los miembros del grupo, no devuelvas simplemente el iterador creado al llamar al método <code>Symbol.iterator</code> en el array. Eso funcionaría, pero va en contra del propósito de este ejercicio.</p> -<p><a class="p_ident" id="p-8RHrsgBfOU" href="#p-8RHrsgBfOU" tabindex="-1" role="presentation"></a>Está bien si tu iterador se comporta de manera extraña cuando el grupo se modifica durante la iteración.</p> +<p><a class="p_ident" id="p-VT1bqMWagS" href="#p-VT1bqMWagS" tabindex="-1" role="presentation"></a>No pasa nada si tu iterador se comporta de manera extraña cuando el grupo se modifica durante la iteración.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NyqSAtbB/S" href="#c-NyqSAtbB/S" tabindex="-1" role="presentation"></a><span class="tok-comment">// Tu código aquí (y el código del ejercicio anterior)</span> @@ -656,7 +659,7 @@ <h3><a class="i_ident" id="i-HCxu3+WnnE" href="#i-HCxu3+WnnE" tabindex="-1" role <p><a class="p_ident" id="p-jIMj/I0gM7" href="#p-jIMj/I0gM7" tabindex="-1" role="presentation"></a>Probablemente valga la pena definir una nueva clase <code>GroupIterator</code>. Las instancias del iterador deberían tener una propiedad que rastree la posición actual en el grupo. Cada vez que se llama a <code>next</code>, verifica si ha terminado y, si no, avanza más allá del valor actual y lo devuelve.</p> -<p><a class="p_ident" id="p-EVpsWuCbsg" href="#p-EVpsWuCbsg" tabindex="-1" role="presentation"></a>La clase <code>Group</code> en sí misma obtiene un método nombrado <code>Symbol.iterator</code> que, al ser llamado, devuelve una nueva instancia de la clase iteradora para ese grupo.</p> +<p><a class="p_ident" id="p-EVpsWuCbsg" href="#p-EVpsWuCbsg" tabindex="-1" role="presentation"></a>La clase <code>Group</code> en sí misma obtiene un método llamado <code>Symbol.iterator</code> que, al ser llamado, devuelve una nueva instancia de la clase iteradora para ese grupo.</p> </div></details><nav><a href="05_higher_order.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="07_robot.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button> </nav> 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 @@ <h1>Proyecto: Un Robot</h1> <blockquote> -<p><a class="p_ident" id="p-/Oc+pQbX3S" href="#p-/Oc+pQbX3S" tabindex="-1" role="presentation"></a>[...] la pregunta de si las Máquinas Pueden Pensar [...] es tan relevante como la pregunta de si los Submarinos Pueden Nadar.</p> +<p><a class="p_ident" id="p-DpY4TSZBO0" href="#p-DpY4TSZBO0" tabindex="-1" role="presentation"></a>La cuestión de si las Máquinas Pueden Pensar [...] es tan relevante como la cuestión de si los Submarinos Pueden Nadar.</p> -<footer>Edsger Dijkstra, <cite>Las amenazas a la ciencia informática</cite></footer> +<footer>Edsger Dijkstra, <cite>The Threats to Computing Science</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_7.jpg" alt="Ilustración de un robot sosteniendo una pila de paquetes"></figure> -<p><a class="p_ident" id="p-0PBWKq6GYO" href="#p-0PBWKq6GYO" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-0PBWKq6GYO" href="#p-0PBWKq6GYO" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-1ftX4jVXFE" href="#p-1ftX4jVXFE" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-UmFK5fYed8" href="#h-UmFK5fYed8" tabindex="-1" role="presentation"></a>Meadowfield</h2> -<p><a class="p_ident" id="p-CoFPYYp5Q2" href="#p-CoFPYYp5Q2" tabindex="-1" role="presentation"></a>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:</p> +<p><a class="p_ident" id="p-6SFWJuvltH" href="#p-6SFWJuvltH" tabindex="-1" role="presentation"></a>El pueblo de Meadowfield no es muy grande. Consiste en 11 lugares conectados por 14 caminos. Se puede describir con este array de caminos:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NplCaNzuvn" href="#c-NplCaNzuvn" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">roads</span> = [ <span class="tok-string">"Casa de Alice-Casa de Bob"</span>,<span class="tok-string">"Casa de Alice-Cabaña"</span>, @@ -36,11 +36,11 @@ <h2><a class="h_ident" id="h-UmFK5fYed8" href="#h-UmFK5fYed8" tabindex="-1" role <span class="tok-string">"Casa de Grete-Tienda"</span>,<span class="tok-string">"Plaza del Mercado-Granja"</span>, <span class="tok-string">"Plaza del Mercado-Oficina de Correos"</span>,<span class="tok-string">"Plaza del Mercado-Tienda"</span>, <span class="tok-string">"Plaza del Mercado-Ayuntamiento"</span>,<span class="tok-string">"Tienda-Ayuntamiento"</span> -];</pre><figure><img src="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> +];</pre><figure><img src="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"></figure> -<p><a class="p_ident" id="p-z97coTX23j" href="#p-z97coTX23j" tabindex="-1" role="presentation"></a>La red de carreteras en el pueblo forma un <em>gráfico</em>. 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.</p> +<p><a class="p_ident" id="p-z97coTX23j" href="#p-z97coTX23j" tabindex="-1" role="presentation"></a>La red de carreteras en el pueblo forma un <em>grafo</em>. 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.</p> -<p><a class="p_ident" id="p-Hw3WY9bviH" href="#p-Hw3WY9bviH" tabindex="-1" role="presentation"></a>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í.</p> +<p><a class="p_ident" id="p-sR36P4HEt1" href="#p-sR36P4HEt1" tabindex="-1" role="presentation"></a>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í.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-k8seJpRr5W" href="#c-k8seJpRr5W" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">buildGraph</span>(<span class="tok-definition">edges</span>) { <span class="tok-keyword">let</span> <span class="tok-definition">graph</span> = Object.create(<span class="tok-keyword">null</span>); @@ -62,7 +62,7 @@ <h2><a class="h_ident" id="h-UmFK5fYed8" href="#h-UmFK5fYed8" tabindex="-1" role <p><a class="p_ident" id="p-jL6/RvPMt+" href="#p-jL6/RvPMt+" tabindex="-1" role="presentation"></a>Dado un array de aristas, <code>buildGraph</code> crea un objeto de mapa que, para cada nodo, almacena un array de nodos conectados.</p> -<p><a class="p_ident" id="p-ibN0Rbh4w2" href="#p-ibN0Rbh4w2" tabindex="-1" role="presentation"></a>Utiliza el método <code>split</code> para pasar de las cadenas de carreteras, que tienen la forma <code>"Inicio-Fin"</code>, a arrays de dos elementos que contienen el inicio y el fin como cadenas separadas.</p> +<p><a class="p_ident" id="p-ibN0Rbh4w2" href="#p-ibN0Rbh4w2" tabindex="-1" role="presentation"></a>Utiliza el método <code>split</code> para pasar de las cadenas representando caminos, que tienen la forma <code>"Inicio-Fin"</code>, a arrays de dos elementos que contienen el inicio y el fin como cadenas separadas.</p> <h2><a class="h_ident" id="h-ZcNPR9BKaF" href="#h-ZcNPR9BKaF" tabindex="-1" role="presentation"></a>La tarea</h2> @@ -74,11 +74,11 @@ <h2><a class="h_ident" id="h-ZcNPR9BKaF" href="#h-ZcNPR9BKaF" tabindex="-1" role <p><a class="p_ident" id="p-ImUqv6bngP" href="#p-ImUqv6bngP" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-C/Z3QwwroQ" href="#p-C/Z3QwwroQ" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-8g3eWGEJEl" href="#p-8g3eWGEJEl" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-5099+VupJV" href="#p-5099+VupJV" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-P5OPIi9FfW" href="#p-P5OPIi9FfW" tabindex="-1" role="presentation"></a>Y mientras lo hacemos, hagamos que no <em>cambiemos</em> este estado cuando el robot se mueve, sino que calculemos un <em>nuevo</em> estado para la situación después del movimiento.</p> +<p><a class="p_ident" id="p-ndhWCdxz4M" href="#p-ndhWCdxz4M" tabindex="-1" role="presentation"></a>Ya que estamos, hagamos que este estado no <em>cambie</em> cuando el robot se mueve, sino que en su lugar se calcule un <em>nuevo</em> estado para la situación después del movimiento.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-VcDNIi1lcV" href="#c-VcDNIi1lcV" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> VillageState { <span class="tok-definition">constructor</span>(<span class="tok-definition">place</span>, <span class="tok-definition">parcels</span>) { @@ -99,11 +99,11 @@ <h2><a class="h_ident" id="h-ZcNPR9BKaF" href="#h-ZcNPR9BKaF" tabindex="-1" role } }</pre> -<p><a class="p_ident" id="p-dEEiTFRt0X" href="#p-dEEiTFRt0X" tabindex="-1" role="presentation"></a>El método <code>move</code> 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.</p> +<p><a class="p_ident" id="p-dEEiTFRt0X" href="#p-dEEiTFRt0X" tabindex="-1" role="presentation"></a>El método <code>move</code> 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.</p> -<p><a class="p_ident" id="p-YeKbjqCqtC" href="#p-YeKbjqCqtC" tabindex="-1" role="presentation"></a>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 <code>map</code> se encarga del traslado y la llamada a <code>filter</code> de la entrega.</p> +<p><a class="p_ident" id="p-t6cEL0QbmG" href="#p-t6cEL0QbmG" tabindex="-1" role="presentation"></a>Si sí, crea un nuevo estado con el destino que se pasa como parámetro a <code>move</code> 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 <code>map</code> se encarga del traslado y la llamada a <code>filter</code> de la entrega.</p> -<p><a class="p_ident" id="p-3lMuPCNKYP" href="#p-3lMuPCNKYP" tabindex="-1" role="presentation"></a>Los objetos de parcela no se modifican cuando se mueven, sino que se vuelven a crear. El método <code>move</code> nos proporciona un nuevo estado de aldea pero deja intacto por completo el anterior.</p> +<p><a class="p_ident" id="p-PkzzJ4X0Zs" href="#p-PkzzJ4X0Zs" tabindex="-1" role="presentation"></a>Los objetos que representan los paquetes (<code>parcels</code>) no se modifican cuando se mueven, sino que se vuelven a crear. El método <code>move</code> nos proporciona un nuevo estado del pueblo pero deja intacto por completo el anterior.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-rRgQCtG7hr" href="#c-rRgQCtG7hr" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">first</span> = <span class="tok-keyword">new</span> VillageState( <span class="tok-string">"Oficina de Correos"</span>, @@ -118,30 +118,28 @@ <h2><a class="h_ident" id="h-ZcNPR9BKaF" href="#h-ZcNPR9BKaF" tabindex="-1" role console.log(first.place); <span class="tok-comment">// → Oficina de Correos</span></pre> -<p><a class="p_ident" id="p-Myxbdg6oBC" href="#p-Myxbdg6oBC" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-M8MMrzFIaV" href="#p-M8MMrzFIaV" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-TZqUeD/4Kg" href="#h-TZqUeD/4Kg" tabindex="-1" role="presentation"></a>Datos persistentes</h2> <p><a class="p_ident" id="p-FhP/ZMbXxd" href="#p-FhP/ZMbXxd" tabindex="-1" role="presentation"></a>Las estructuras de datos que no cambian se llaman <em>inmutables</em> o <em>persistentes</em>. 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.</p> -<p><a class="p_ident" id="p-jM2h5YhXld" href="#p-jM2h5YhXld" tabindex="-1" role="presentation"></a>En JavaScript, casi todo <em>puede</em> cambiarse, por lo que trabajar con valores que se supone que son persistentes requiere cierta moderación. Existe una función llamada <code>Object.freeze</code> 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.</p> +<p><a class="p_ident" id="p-jM2h5YhXld" href="#p-jM2h5YhXld" tabindex="-1" role="presentation"></a>En JavaScript, casi todo <em>puede</em> modificarse, por lo que trabajar con valores que deberían ser persistentes requiere cierta disciplina. Existe una función llamada <code>Object.freeze</code> 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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-tzmey+74SE" href="#c-tzmey+74SE" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">object</span> = Object.freeze({<span class="tok-definition">value</span>: <span class="tok-number">5</span>}); object.value = <span class="tok-number">10</span>; console.log(object.value); <span class="tok-comment">// → 5</span></pre> -<p><a class="p_ident" id="p-3ZcXSzHegg" href="#p-3ZcXSzHegg" tabindex="-1" role="presentation"></a>¿Por qué me estoy esforzando tanto en no cambiar los objetos cuando el lenguaje obviamente espera que lo haga?</p> +<p><a class="p_ident" id="p-YBylhtk2TD" href="#p-YBylhtk2TD" tabindex="-1" role="presentation"></a>¿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.</p> -<p><a class="p_ident" id="p-1ihsgYZ82c" href="#p-1ihsgYZ82c" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-FW5kl43Zwa" href="#p-FW5kl43Zwa" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-FW5kl43Zwa" href="#p-FW5kl43Zwa" tabindex="-1" role="presentation"></a>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.</p> - -<p><a class="p_ident" id="p-T1HGdD5GjP" href="#p-T1HGdD5GjP" tabindex="-1" role="presentation"></a>Desafortunadamente, aunque entender un sistema construido sobre estructuras de datos persistentes es más fácil, <em>diseñar</em> 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.</p> +<p><a class="p_ident" id="p-T+JjyzLpmb" href="#p-T+JjyzLpmb" tabindex="-1" role="presentation"></a>Por desgracia, aunque entender un sistema construido sobre estructuras de datos persistentes es más fácil, <em>diseñar</em> 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.</p> <h2><a class="h_ident" id="h-NngWObDYaj" href="#h-NngWObDYaj" tabindex="-1" role="presentation"></a>Simulación</h2> -<p><a class="p_ident" id="p-Lt1p8txGDL" href="#p-Lt1p8txGDL" tabindex="-1" role="presentation"></a>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 <code>VillageState</code> y devuelve el nombre de un lugar cercano.</p> +<p><a class="p_ident" id="p-Lt1p8txGDL" href="#p-Lt1p8txGDL" tabindex="-1" role="presentation"></a>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 <code>VillageState</code> y devuelve el nombre de un lugar cercano.</p> <p><a class="p_ident" id="p-pbn2tWD1Al" href="#p-pbn2tWD1Al" tabindex="-1" role="presentation"></a>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.</p> @@ -162,7 +160,7 @@ <h2><a class="h_ident" id="h-NngWObDYaj" href="#h-NngWObDYaj" tabindex="-1" role <p><a class="p_ident" id="p-5bsdqun/8W" href="#p-5bsdqun/8W" tabindex="-1" role="presentation"></a>¿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.</p> -<p><a class="p_ident" id="p-SlxnfWOsG9" href="#p-SlxnfWOsG9" tabindex="-1" role="presentation"></a>Esto es cómo podría lucir eso:</p> +<p><a class="p_ident" id="p-jKsOJPz4Lp" href="#p-jKsOJPz4Lp" tabindex="-1" role="presentation"></a>Esta es la pinta que podría tener algo así:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-eldzpwzhOB" href="#c-eldzpwzhOB" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">randomPick</span>(<span class="tok-definition">array</span>) { <span class="tok-keyword">let</span> <span class="tok-definition">choice</span> = Math.floor(Math.random() * array.length); @@ -173,7 +171,7 @@ <h2><a class="h_ident" id="h-NngWObDYaj" href="#h-NngWObDYaj" tabindex="-1" role <span class="tok-keyword">return</span> {<span class="tok-definition">direction</span>: randomPick(roadGraph[state.place])}; }</pre> -<p><a class="p_ident" id="p-iGtbW23NAf" href="#p-iGtbW23NAf" tabindex="-1" role="presentation"></a>Recuerda que <code>Math.random()</code> 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 <code>Math.floor</code> nos da un índice aleatorio para el array.</p> +<p><a class="p_ident" id="p-iGtbW23NAf" href="#p-iGtbW23NAf" tabindex="-1" role="presentation"></a>Recuerda que <code>Math.random()</code> 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 <code>Math.floor</code>, obtenemos un índice aleatorio para el array.</p> <p><a class="p_ident" id="p-/n/qSIuoNM" href="#p-/n/qSIuoNM" tabindex="-1" role="presentation"></a>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 <code>memory</code> en su objeto devuelto.</p> @@ -221,7 +219,7 @@ <h2><a class="h_ident" id="h-Gn9gM3o6mE" href="#h-Gn9gM3o6mE" tabindex="-1" role <span class="tok-string">"Plaza del Mercado"</span>, <span class="tok-string">"Oficina de Correos"</span> ];</pre> -<p><a class="p_ident" id="p-NuQQdtHsp+" href="#p-NuQQdtHsp+" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-NuQQdtHsp+" href="#p-NuQQdtHsp+" tabindex="-1" role="presentation"></a>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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-FlV5rBgCYM" href="#c-FlV5rBgCYM" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">routeRobot</span>(<span class="tok-definition">state</span>, <span class="tok-definition">memory</span>) { <span class="tok-keyword">if</span> (memory.length == <span class="tok-number">0</span>) { @@ -236,15 +234,17 @@ <h2><a class="h_ident" id="h-Gn9gM3o6mE" href="#h-Gn9gM3o6mE" tabindex="-1" role <h2><a class="h_ident" id="h-Qr/SzElzir" href="#h-Qr/SzElzir" tabindex="-1" role="presentation"></a>Búsqueda de caminos</h2> -<p><a class="p_ident" id="p-+h0jpYRuOH" href="#p-+h0jpYRuOH" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-+h0jpYRuOH" href="#p-+h0jpYRuOH" tabindex="-1" role="presentation"></a>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.</p> + +<p><a class="p_ident" id="p-Qq4JUaMcE0" href="#p-Qq4JUaMcE0" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-Qq4JUaMcE0" href="#p-Qq4JUaMcE0" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-PxJJwn/RDt" href="#p-PxJJwn/RDt" tabindex="-1" role="presentation"></a>El problema de encontrar una ruta a través de un grafo es un <em>problema de búsqueda</em> 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.</p> -<p><a class="p_ident" id="p-PxJJwn/RDt" href="#p-PxJJwn/RDt" tabindex="-1" role="presentation"></a>El problema de encontrar una ruta a través de un grafo es un <em>problema de búsqueda</em> 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.</p> +<p><a class="p_ident" id="p-HqXJNn2t7A" href="#p-HqXJNn2t7A" tabindex="-1" role="presentation"></a>El número de rutas posibles a través de un grafo es enorme. Pero al buscar una ruta de <em>A</em> a <em>B</em>, solo estamos interesados en aquellas que comienzan en <em>A</em>. 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.</p> -<p><a class="p_ident" id="p-shDyhu90T5" href="#p-shDyhu90T5" tabindex="-1" role="presentation"></a>El número de rutas posibles a través de un grafo es infinito. Pero al buscar una ruta de <em>A</em> a <em>B</em>, solo estamos interesados en aquellas que comienzan en <em>A</em>. 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 <em>más corta</em>. 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).</p> +<p><a class="p_ident" id="p-KfzXn7zEZy" href="#p-KfzXn7zEZy" tabindex="-1" role="presentation"></a>De hecho, estamos sobre todo interesados en la ruta <em>más corta</em>. 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).</p> -<p id="findRoute"><a class="p_ident" id="p-HzB02WYPFY" href="#p-HzB02WYPFY" tabindex="-1" role="presentation"></a>Aquí hay una función que hace esto:</p> +<p id="findRoute"><a class="p_ident" id="p-xfOXrXlBkY" href="#p-xfOXrXlBkY" tabindex="-1" role="presentation"></a>Aquí, una función que hace esto:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-qT/+IETEgM" href="#c-qT/+IETEgM" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">findRoute</span>(<span class="tok-definition">graph</span>, <span class="tok-definition">from</span>, <span class="tok-definition">to</span>) { <span class="tok-keyword">let</span> <span class="tok-definition">work</span> = [{<span class="tok-definition">at</span>: from, <span class="tok-definition">route</span>: []}]; @@ -261,13 +261,13 @@ <h2><a class="h_ident" id="h-Qr/SzElzir" href="#h-Qr/SzElzir" tabindex="-1" role <p><a class="p_ident" id="p-xVUWEP9k6b" href="#p-xVUWEP9k6b" tabindex="-1" role="presentation"></a>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 <em>desde allí</em> 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.</p> -<p><a class="p_ident" id="p-9iy0XgVHaG" href="#p-9iy0XgVHaG" tabindex="-1" role="presentation"></a>Por lo tanto, la función mantiene una <em>lista de trabajo</em>. 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.</p> +<p><a class="p_ident" id="p-9iy0XgVHaG" href="#p-9iy0XgVHaG" tabindex="-1" role="presentation"></a>Por lo tanto, la función mantiene una <em>lista de trabajo</em>: 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.</p> <p><a class="p_ident" id="p-vY5At+m4ct" href="#p-vY5At+m4ct" tabindex="-1" role="presentation"></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.</p> -<p><a class="p_ident" id="p-nSmvyFJjX5" href="#p-nSmvyFJjX5" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-nSmvyFJjX5" href="#p-nSmvyFJjX5" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-8xtuFs30lw" href="#p-8xtuFs30lw" tabindex="-1" role="presentation"></a>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á <em>conectado</em>, 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.</p> +<p><a class="p_ident" id="p-8xtuFs30lw" href="#p-8xtuFs30lw" tabindex="-1" role="presentation"></a>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á <em>conectado</em>, 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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-RJgvG9cgxq" href="#c-RJgvG9cgxq" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">goalOrientedRobot</span>({place, parcels}, <span class="tok-definition">route</span>) { <span class="tok-keyword">if</span> (route.length == <span class="tok-number">0</span>) { @@ -281,14 +281,14 @@ <h2><a class="h_ident" id="h-Qr/SzElzir" href="#h-Qr/SzElzir" tabindex="-1" role <span class="tok-keyword">return</span> {<span class="tok-definition">direction</span>: route[<span class="tok-number">0</span>], <span class="tok-definition">memory</span>: route.slice(<span class="tok-number">1</span>)}; }</pre> -<p><a class="p_ident" id="p-SgSFCewRHC" href="#p-SgSFCewRHC" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-SgSFCewRHC" href="#p-SgSFCewRHC" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-HSmURF+mCI" href="#p-HSmURF+mCI" tabindex="-1" role="presentation"></a>Veamos cómo lo hace.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-yYuNlgXLX9" href="#c-yYuNlgXLX9" tabindex="-1" role="presentation"></a>runRobotAnimation(VillageState.random(), goalOrientedRobot, []);</pre> -<p><a class="p_ident" id="p-+YpJ1wVO73" href="#p-+YpJ1wVO73" tabindex="-1" role="presentation"></a>Este robot suele terminar la tarea de entregar 5 paquetes en aproximadamente 16 turnos. Eso es ligeramente mejor que <code>routeRobot</code> pero definitivamente no es óptimo.</p> +<p><a class="p_ident" id="p-+YpJ1wVO73" href="#p-+YpJ1wVO73" tabindex="-1" role="presentation"></a>Este robot suele terminar la tarea de entregar 5 paquetes en aproximadamente 16 turnos. Eso es ligeramente mejor que <code>routeRobot</code> pero está claro que no es óptimo.</p> <h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2> @@ -298,7 +298,7 @@ <h3><a class="i_ident" id="i-nSYnmkjC8T" href="#i-nSYnmkjC8T" tabindex="-1" role <p><a class="p_ident" id="p-vvltSTdEem" href="#p-vvltSTdEem" tabindex="-1" role="presentation"></a>Escribe una función <code>compareRobots</code> 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.</p> -<p><a class="p_ident" id="p-Y+3Np8UuLO" href="#p-Y+3Np8UuLO" tabindex="-1" role="presentation"></a>Por el bien de la equidad, asegúrate de darle a cada tarea a ambos robots, en lugar de generar tareas diferentes por robot.</p> +<p><a class="p_ident" id="p-SJoPoN5vJL" href="#p-SJoPoN5vJL" tabindex="-1" role="presentation"></a>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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-kWSW2DNAYZ" href="#c-kWSW2DNAYZ" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">compareRobots</span>(<span class="tok-definition">robot1</span>, <span class="tok-definition">memory1</span>, <span class="tok-definition">robot2</span>, <span class="tok-definition">memory2</span>) { <span class="tok-comment">// Tu código aquí</span> @@ -310,13 +310,13 @@ <h3><a class="i_ident" id="i-nSYnmkjC8T" href="#i-nSYnmkjC8T" tabindex="-1" role <p><a class="p_ident" id="p-kcCXvCKFts" href="#p-kcCXvCKFts" tabindex="-1" role="presentation"></a>Tendrás que escribir una variante de la función <code>runRobot</code> que, en lugar de registrar los eventos en la consola, devuelva el número de pasos que el robot tomó para completar la tarea.</p> -<p><a class="p_ident" id="p-Xr248zBbAN" href="#p-Xr248zBbAN" tabindex="-1" role="presentation"></a>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 <code>console.log</code> para mostrar el promedio de cada robot, que es el número total de pasos tomados dividido por el número de mediciones.</p> +<p><a class="p_ident" id="p-Xr248zBbAN" href="#p-Xr248zBbAN" tabindex="-1" role="presentation"></a>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 <code>console.log</code> para mostrar el promedio de cada robot, que es el número total de pasos dados dividido por el número de mediciones.</p> </div></details> <h3><a class="i_ident" id="i-XtZPo4zIj3" href="#i-XtZPo4zIj3" tabindex="-1" role="presentation"></a>Eficiencia del robot</h3> -<p><a class="p_ident" id="p-ekiPq8jgZ7" href="#p-ekiPq8jgZ7" tabindex="-1" role="presentation"></a>¿Puedes escribir un robot que termine la tarea de entrega más rápido que <code>goalOrientedRobot</code>? Si observas el comportamiento de ese robot, ¿qué cosas claramente absurdas hace? ¿Cómo podrían mejorarse?</p> +<p><a class="p_ident" id="p-ekiPq8jgZ7" href="#p-ekiPq8jgZ7" tabindex="-1" role="presentation"></a>¿Puedes escribir un robot que termine la tarea de entrega más rápido que <code>goalOrientedRobot</code>? Si observas el comportamiento de ese robot, ¿qué cosas evidentemente absurdas está haciendo? ¿Cómo podrían mejorarse?</p> <p><a class="p_ident" id="p-WHsjtdy1wY" href="#p-WHsjtdy1wY" tabindex="-1" role="presentation"></a>Si resolviste el ejercicio anterior, es posible que desees utilizar tu función <code>compareRobots</code> para verificar si mejoraste el robot.</p> @@ -326,9 +326,9 @@ <h3><a class="i_ident" id="i-XtZPo4zIj3" href="#i-XtZPo4zIj3" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-sRXTWyzdOx" href="#p-sRXTWyzdOx" tabindex="-1" role="presentation"></a>La principal limitación de <code>goalOrientedRobot</code> 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.</p> +<p><a class="p_ident" id="p-sRXTWyzdOx" href="#p-sRXTWyzdOx" tabindex="-1" role="presentation"></a>La principal limitación de <code>goalOrientedRobot</code> 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.</p> -<p><a class="p_ident" id="p-h8Mq8+DtKj" href="#p-h8Mq8+DtKj" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-wemFc7+Eow" href="#p-wemFc7+Eow" tabindex="-1" role="presentation"></a>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.</p> </div></details> @@ -340,7 +340,7 @@ <h3><a class="i_ident" id="i-4PUyGyQ+OK" href="#i-4PUyGyQ+OK" tabindex="-1" role <p><a class="p_ident" id="p-3XYr611b0/" href="#p-3XYr611b0/" tabindex="-1" role="presentation"></a>Sin embargo, su método <code>add</code> debería devolver una <em>nueva</em> instancia de <code>PGroup</code> con el miembro dado añadido y dejar la anterior sin cambios. De manera similar, <code>delete</code> crea una nueva instancia sin un miembro dado.</p> -<p><a class="p_ident" id="p-OqTz+KTGUf" href="#p-OqTz+KTGUf" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-OqTz+KTGUf" href="#p-OqTz+KTGUf" tabindex="-1" role="presentation"></a>La clase debería funcionar para valores de cualquier tipo, no solo para strings. <em>No</em> tiene que ser eficiente cuando se utiliza con grandes cantidades de valores.</p> <p><a class="p_ident" id="p-xZHWaBEuTQ" href="#p-xZHWaBEuTQ" tabindex="-1" role="presentation"></a>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, <code>PGroup.empty</code>, que se puede usar como valor inicial.</p> 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 @@ <h1>Bugs y Errores</h1> <blockquote> -<p><a class="p_ident" id="p-d+xhst8cOA" href="#p-d+xhst8cOA" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-d+xhst8cOA" href="#p-d+xhst8cOA" tabindex="-1" role="presentation"></a>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.</p> <footer>Brian Kernighan and P.J. Plauger, <cite>The Elements of Programming Style</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_8.jpg" alt="Ilustración mostrando varios insectos y un ciempiés"></figure> -<p><a class="p_ident" id="p-A/LLrFO0lg" href="#p-A/LLrFO0lg" tabindex="-1" role="presentation"></a>Las fallas en los programas de computadora generalmente se llaman <em>bugs</em>. 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í.</p> +<p><a class="p_ident" id="p-smWVAVFBAK" href="#p-smWVAVFBAK" tabindex="-1" role="presentation"></a>Los errores en los programas de computadora generalmente se llaman <em>bugs</em>. 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í.</p> -<p><a class="p_ident" id="p-DretlFLrRR" href="#p-DretlFLrRR" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-qn4XNaJR3q" href="#p-qn4XNaJR3q" tabindex="-1" role="presentation"></a>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.º</p> <h2><a class="h_ident" id="h-EWyEuXUxbG" href="#h-EWyEuXUxbG" tabindex="-1" role="presentation"></a>Lenguaje</h2> -<p><a class="p_ident" id="p-76p39k7Awv" href="#p-76p39k7Awv" tabindex="-1" role="presentation"></a>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 <code>true * "monkey"</code>.</p> +<p><a class="p_ident" id="p-76p39k7Awv" href="#p-76p39k7Awv" tabindex="-1" role="presentation"></a>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 <code>true * "monkey"</code>.</p> -<p><a class="p_ident" id="p-9FkgM0j2UZ" href="#p-9FkgM0j2UZ" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-9FkgM0j2UZ" href="#p-9FkgM0j2UZ" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-jRmfwLXxFS" href="#p-jRmfwLXxFS" tabindex="-1" role="presentation"></a>Pero a menudo, tu cálculo absurdo simplemente producirá <code>NaN</code> (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.</p> +<p><a class="p_ident" id="p-jRmfwLXxFS" href="#p-jRmfwLXxFS" tabindex="-1" role="presentation"></a>Pero a menudo, tu cálculo absurdo simplemente producirá <code>NaN</code> (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.</p> -<p><a class="p_ident" id="p-/tIrAOs/Jj" href="#p-/tIrAOs/Jj" tabindex="-1" role="presentation"></a>El proceso de encontrar errores—bugs—en los programas se llama <em>depuración</em>.</p> +<p><a class="p_ident" id="p-Rq+UCOHvqQ" href="#p-Rq+UCOHvqQ" tabindex="-1" role="presentation"></a>El proceso de encontrar errores —bugs— en los programas se llama <em>depuración</em> (en inglés, <em>debugging</em>).</p> <h2><a class="h_ident" id="h-VqnTmwySiJ" href="#h-VqnTmwySiJ" tabindex="-1" role="presentation"></a>Modo estricto</h2> -<p><a class="p_ident" id="p-iJcOGE1sqz" href="#p-iJcOGE1sqz" tabindex="-1" role="presentation"></a>JavaScript puede ser un <em>poco</em> más estricto al habilitar el <em>modo estricto</em>. Esto se hace colocando la cadena <code>"use strict"</code> en la parte superior de un archivo o en el cuerpo de una función. Aquí tienes un ejemplo:</p> +<p><a class="p_ident" id="p-R1p3SEr9lZ" href="#p-R1p3SEr9lZ" tabindex="-1" role="presentation"></a>Se puede hacer que JavaScript sea un <em>poco</em> más estricto al habilitar el <em>modo estricto</em>. Esto se hace colocando la cadena <code>"use strict"</code> en la parte superior de un archivo o en el cuerpo de una función. Aquí tienes un ejemplo:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-tKCBneE0vw" href="#c-tKCBneE0vw" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">canYouSpotTheProblem</span>() { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-dU3l5G+fAo" href="#c-dU3l5G+fAo" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">puedesEncontrarElProblema</span>() { <span class="tok-string">"use strict"</span>; - <span class="tok-keyword">for</span> (counter = <span class="tok-number">0</span>; counter < <span class="tok-number">10</span>; counter++) { + <span class="tok-keyword">for</span> (contador = <span class="tok-number">0</span>; contador < <span class="tok-number">10</span>; contador++) { console.log(<span class="tok-string">"Happy happy"</span>); } } -canYouSpotTheProblem(); -<span class="tok-comment">// → ReferenceError: counter is not defined</span></pre> +puedesEncontrarElProblema(); +<span class="tok-comment">// → ReferenceError: contador is not defined</span></pre> -<p><a class="p_ident" id="p-GbnCF9fb71" href="#p-GbnCF9fb71" tabindex="-1" role="presentation"></a>Normalmente, cuando olvidas poner <code>let</code> frente a tu enlace, como en el caso de <code>counter</code> 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.</p> +<p><a class="p_ident" id="p-gY/KoQA2un" href="#p-gY/KoQA2un" tabindex="-1" role="presentation"></a>El código de dentro de una clase o un módulo (que veremos en el <a href="10_modules.html">Capítulo 10</a>) 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.</p> -<p><a class="p_ident" id="p-CH5/UhR2LK" href="#p-CH5/UhR2LK" tabindex="-1" role="presentation"></a>Otro cambio en el modo estricto es que el enlace <code>this</code> mantiene el valor <code>undefined</code> en funciones que no son llamadas como métodos. Al hacer una llamada de este tipo fuera del modo estricto, <code>this</code> 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 <code>this</code>, en lugar de escribir felizmente en el ámbito global.</p> +<p><a class="p_ident" id="p-i1bK3MNOer" href="#p-i1bK3MNOer" tabindex="-1" role="presentation"></a>Normalmente, cuando olvidas poner <code>let</code> frente a tu asociación, como en el caso de <code>counter</code> 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.</p> -<p><a class="p_ident" id="p-lSOsyuYctI" href="#p-lSOsyuYctI" tabindex="-1" role="presentation"></a>Por ejemplo, considera el siguiente código, que llama a una función constructor sin la palabra clave <code>new</code> para que su <code>this</code> <em>no</em> se refiera a un objeto recién construido:</p> +<p><a class="p_ident" id="p-CH5/UhR2LK" href="#p-CH5/UhR2LK" tabindex="-1" role="presentation"></a>Otro cambio en el modo estricto es que el enlace <code>this</code> tiene el valor <code>undefined</code> en funciones que no son llamadas como métodos. Al hacer una llamada de este tipo fuera del modo estricto, <code>this</code> 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 <code>this</code>, en lugar de escribir en el ámbito global.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-FPKrb2F1C3" href="#c-FPKrb2F1C3" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">Person</span>(<span class="tok-definition">name</span>) { <span class="tok-keyword">this</span>.name = name; } -<span class="tok-keyword">let</span> <span class="tok-definition">ferdinand</span> = Person(<span class="tok-string">"Ferdinand"</span>); <span class="tok-comment">// oops</span> -console.log(name); +<p><a class="p_ident" id="p-KmeHELZRoB" href="#p-KmeHELZRoB" tabindex="-1" role="presentation"></a>Considera, por ejemplo, el siguiente código, que llama a una función constructor sin la palabra clave <code>new</code> para que su <code>this</code> <em>no</em> se refiera a un objeto recién construido:</p> + +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-dVGdKtXXIU" href="#c-dVGdKtXXIU" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">Persona</span>(<span class="tok-definition">nombre</span>) { <span class="tok-keyword">this</span>.nombre = nombre; } +<span class="tok-keyword">let</span> <span class="tok-definition">ferdinand</span> = Persona(<span class="tok-string">"Ferdinand"</span>); <span class="tok-comment">// oops</span> +console.log(nombre); <span class="tok-comment">// → Ferdinand</span></pre> -<p><a class="p_ident" id="p-Kvv0M23vAl" href="#p-Kvv0M23vAl" tabindex="-1" role="presentation"></a>Entonces, la llamada falsa a <code>Person</code> tuvo éxito pero devolvió un valor no definido y creó el enlace global <code>name</code>. En modo estricto, el resultado es diferente.</p> +<p><a class="p_ident" id="p-3Pg5xOQ3EB" href="#p-3Pg5xOQ3EB" tabindex="-1" role="presentation"></a>La llamada errónea a <code>Persona</code> ha tenido éxito pero ha devuelto un valor no definido y ha creado el enlace global <code>name</code>. En modo estricto, el resultado es diferente.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-d2bO4uR9UP" href="#c-d2bO4uR9UP" tabindex="-1" role="presentation"></a><span class="tok-string">"use strict"</span>; -<span class="tok-keyword">function</span> <span class="tok-definition">Person</span>(<span class="tok-definition">name</span>) { <span class="tok-keyword">this</span>.name = name; } -<span class="tok-keyword">let</span> <span class="tok-definition">ferdinand</span> = Person(<span class="tok-string">"Ferdinand"</span>); <span class="tok-comment">// olvidó el new</span> -<span class="tok-comment">// → TypeError: Cannot set property 'name' of undefined</span></pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-q+urn9iPjQ" href="#c-q+urn9iPjQ" tabindex="-1" role="presentation"></a><span class="tok-string">"use strict"</span>; +<span class="tok-keyword">function</span> <span class="tok-definition">Persona</span>(<span class="tok-definition">nombre</span>) { <span class="tok-keyword">this</span>.nombre = nombre; } +<span class="tok-keyword">let</span> <span class="tok-definition">ferdinand</span> = Persona(<span class="tok-string">"Ferdinand"</span>); <span class="tok-comment">// falta el new</span> +<span class="tok-comment">// → TypeError: Cannot set property 'nombre' of undefined</span></pre> -<p><a class="p_ident" id="p-j2ef/d1TDi" href="#p-j2ef/d1TDi" tabindex="-1" role="presentation"></a>Inmediatamente se nos informa que algo está mal. Esto es útil.</p> +<p><a class="p_ident" id="p-j2ef/d1TDi" href="#p-j2ef/d1TDi" tabindex="-1" role="presentation"></a>Inmediatamente se nos informa de que algo falla. Esto es útil.</p> -<p><a class="p_ident" id="p-QrETth0MQK" href="#p-QrETth0MQK" tabindex="-1" role="presentation"></a>Afortunadamente, los constructores creados con la notación <code>class</code> siempre mostrarán una queja si se llaman sin <code>new</code>, lo que hace que esto sea menos problemático incluso en modo no estricto.</p> +<p><a class="p_ident" id="p-IYk5xdBTya" href="#p-IYk5xdBTya" tabindex="-1" role="presentation"></a>Por suerte, los constructores creados con la notación <code>class</code> siempre se van a quejar si se llaman sin <code>new</code>, conque esto no será tanto problema incluso en modo no estricto.</p> -<p><a class="p_ident" id="p-R6rUgXg6qF" href="#p-R6rUgXg6qF" tabindex="-1" role="presentation"></a>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 <code>with</code>, que es tan incorrecta que no se discute más en este libro).</p> +<p><a class="p_ident" id="p-R6rUgXg6qF" href="#p-R6rUgXg6qF" tabindex="-1" role="presentation"></a>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 <code>with</code>, que es tan incorrecta que ni se va a discutir más en este libro).</p> -<p><a class="p_ident" id="p-b84P9pZUCj" href="#p-b84P9pZUCj" tabindex="-1" role="presentation"></a>En resumen, colocar <code>"use strict"</code> al principio de tu programa rara vez duele y podría ayudarte a identificar un problema.</p> +<p><a class="p_ident" id="p-b84P9pZUCj" href="#p-b84P9pZUCj" tabindex="-1" role="presentation"></a>En resumen, colocar <code>"use strict"</code> al principio de tu programa rara vez hace daño y podría ayudarte a identificar un problema.</p> <h2><a class="h_ident" id="h-Oj3wsjsPjg" href="#h-Oj3wsjsPjg" tabindex="-1" role="presentation"></a>Tipos</h2> -<p><a class="p_ident" id="p-ZdIS/hFvOa" href="#p-ZdIS/hFvOa" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-ZdIS/hFvOa" href="#p-ZdIS/hFvOa" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-AId/2vaEOU" href="#p-AId/2vaEOU" tabindex="-1" role="presentation"></a>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 <code>findRoute</code> del capítulo anterior para describir su tipo:</p> +<p><a class="p_ident" id="p-1gSju03/fG" href="#p-1gSju03/fG" tabindex="-1" role="presentation"></a>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 <code>findRoute</code> del capítulo anterior para describir su tipo:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-VApmuU1GdX" href="#c-VApmuU1GdX" tabindex="-1" role="presentation"></a><span class="tok-comment">// (graph: Object, from: string, to: string) => string[]</span> <span class="tok-keyword">function</span> <span class="tok-definition">findRoute</span>(<span class="tok-definition">graph</span>, <span class="tok-definition">from</span>, <span class="tok-definition">to</span>) { @@ -87,70 +89,70 @@ <h2><a class="h_ident" id="h-Oj3wsjsPjg" href="#h-Oj3wsjsPjg" tabindex="-1" role <p><a class="p_ident" id="p-NHKkQjh4qk" href="#p-NHKkQjh4qk" tabindex="-1" role="presentation"></a>Existen varias convenciones diferentes para anotar programas de JavaScript con tipos.</p> -<p><a class="p_ident" id="p-1St0Nf/rv4" href="#p-1St0Nf/rv4" tabindex="-1" role="presentation"></a>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 <code>randomPick</code> que devuelve un elemento aleatorio de un array? Necesitarías introducir una <em>variable de tipo</em>, <em>T</em>, que pueda representar cualquier tipo, para que puedas darle a <code>randomPick</code> un tipo como <code>(T[]) → T</code> (función de un array de <em>T</em> a un <em>T</em>).</p> +<p><a class="p_ident" id="p-1St0Nf/rv4" href="#p-1St0Nf/rv4" tabindex="-1" role="presentation"></a>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 <code>randomPick</code> que devuelve un elemento aleatorio de un array? Necesitarías introducir una <em>variable de tipo</em>, <em>T</em>, que pueda representar cualquier tipo, para que puedas darle a <code>randomPick</code> un tipo como <code>(T[]) → T</code> (función de un array de <em>T</em> a un <em>T</em>).</p> <p id="typing"><a class="p_ident" id="p-mWQocfyIkx" href="#p-mWQocfyIkx" tabindex="-1" role="presentation"></a>Cuando los tipos de un programa son conocidos, es posible que la computadora los <em>verifique</em> 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 <a href="https://www.typescriptlang.org/">TypeScript</a>. Si estás interesado en agregar más rigor a tus programas, te recomiendo que lo pruebes.</p> <p><a class="p_ident" id="p-dDhyAGmawD" href="#p-dDhyAGmawD" tabindex="-1" role="presentation"></a>En este libro, continuaremos utilizando código JavaScript crudo, peligroso y sin tipos.</p> -<h2><a class="h_ident" id="h-q7sv+LnNdr" href="#h-q7sv+LnNdr" tabindex="-1" role="presentation"></a>Pruebas</h2> +<h2><a class="h_ident" id="h-CCCzKyBrc1" href="#h-CCCzKyBrc1" tabindex="-1" role="presentation"></a>Testing</h2> -<p><a class="p_ident" id="p-Mh0wNpH7/y" href="#p-Mh0wNpH7/y" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-Mh0wNpH7/y" href="#p-Mh0wNpH7/y" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-oYkmD38AMe" href="#p-oYkmD38AMe" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-oYkmD38AMe" href="#p-oYkmD38AMe" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-9SYP3YuKrm" href="#p-9SYP3YuKrm" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-XsiI1vG7f9" href="#p-XsiI1vG7f9" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-4I7oSeVVi0" href="#p-4I7oSeVVi0" tabindex="-1" role="presentation"></a>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 <code>toUpperCase</code> estándar podría lucir así:</p> +<p><a class="p_ident" id="p-KU5x3L5jCZ" href="#p-KU5x3L5jCZ" tabindex="-1" role="presentation"></a>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 <code>toUpperCase</code> estándar podría tener esta pinta:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-i6XPIwq65l" href="#c-i6XPIwq65l" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">test</span>(<span class="tok-definition">label</span>, <span class="tok-definition">body</span>) { - <span class="tok-keyword">if</span> (!body()) console.log(<span class="tok-string2">`Fallo: </span>${label}<span class="tok-string2">`</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-2BUXSBLqjg" href="#c-2BUXSBLqjg" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">test</span>(<span class="tok-definition">etiqueta</span>, <span class="tok-definition">cuerpo</span>) { + <span class="tok-keyword">if</span> (!cuerpo()) console.log(<span class="tok-string2">`Fallo: </span>${etiqueta}<span class="tok-string2">`</span>); } test(<span class="tok-string">"convertir texto latino a mayúsculas"</span>, () => { - <span class="tok-keyword">return</span> <span class="tok-string">"hello"</span>.toUpperCase() == <span class="tok-string">"HELLO"</span>; + <span class="tok-keyword">return</span> <span class="tok-string">"hola"</span>.toUpperCase() == <span class="tok-string">"HOLA"</span>; }); test(<span class="tok-string">"convertir texto griego a mayúsculas"</span>, () => { <span class="tok-keyword">return</span> <span class="tok-string">"Χαίρετε"</span>.toUpperCase() == <span class="tok-string">"ΧΑΊΡΕΤΕ"</span>; }); -test(<span class="tok-string">"no convertir caracteres sin caso"</span>, () => { +test(<span class="tok-string">"no convertir caracteres sin mayúsculas"</span>, () => { <span class="tok-keyword">return</span> <span class="tok-string">"مرحبا"</span>.toUpperCase() == <span class="tok-string">"مرحبا"</span>; });</pre> -<p><a class="p_ident" id="p-WBLtdfJ+Ms" href="#p-WBLtdfJ+Ms" tabindex="-1" role="presentation"></a>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 (<em>suites de pruebas</em>) 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 <em>corredores de pruebas</em>.</p> +<p><a class="p_ident" id="p-prFEy3BfCB" href="#p-prFEy3BfCB" tabindex="-1" role="presentation"></a>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 (<em>test suites</em>) 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 <em>test runners</em>.</p> -<p><a class="p_ident" id="p-2HxEM1LrbU" href="#p-2HxEM1LrbU" tabindex="-1" role="presentation"></a>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 <a href="07_robot.html">capítulo anterior</a>, que utiliza valores persistentes autocontenidos en lugar de objetos cambiantes, tiende a ser fácil de probar.</p> +<p><a class="p_ident" id="p-gBcjbuXET/" href="#p-gBcjbuXET/" tabindex="-1" role="presentation"></a>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 <a href="07_robot.html">capítulo anterior</a>, que utiliza valores persistentes autocontenidos en lugar de objetos cambiantes, suele ser fácil de probar.</p> <h2><a class="h_ident" id="h-nCqjdGY4Fq" href="#h-nCqjdGY4Fq" tabindex="-1" role="presentation"></a>Depuración</h2> -<p><a class="p_ident" id="p-Wg8h9d/mPj" href="#p-Wg8h9d/mPj" tabindex="-1" role="presentation"></a>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 <em>cuál</em> es el problema.</p> +<p><a class="p_ident" id="p-Wg8h9d/mPj" href="#p-Wg8h9d/mPj" tabindex="-1" role="presentation"></a>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 <em>cuál</em> es el problema.</p> <p><a class="p_ident" id="p-AuzxnqBdGR" href="#p-AuzxnqBdGR" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-FQOl3siskW" href="#p-FQOl3siskW" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-YhdyY6oKg7" href="#p-YhdyY6oKg7" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-YhdyY6oKg7" href="#p-YhdyY6oKg7" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-8tOR9x4PzT" href="#c-8tOR9x4PzT" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">numberToString</span>(<span class="tok-definition">n</span>, <span class="tok-definition">base</span> = <span class="tok-number">10</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">result</span> = <span class="tok-string">""</span>, <span class="tok-definition">sign</span> = <span class="tok-string">""</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-2DuSf4OeS0" href="#c-2DuSf4OeS0" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">númeroACadena</span>(<span class="tok-definition">n</span>, <span class="tok-definition">base</span> = <span class="tok-number">10</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">resultado</span> = <span class="tok-string">""</span>, <span class="tok-definition">signo</span> = <span class="tok-string">""</span>; <span class="tok-keyword">if</span> (n < <span class="tok-number">0</span>) { - sign = <span class="tok-string">"-"</span>; + signo = <span class="tok-string">"-"</span>; n = -n; } <span class="tok-keyword">do</span> { - result = String(n % base) + result; + resultado = String(n % base) + resultado; n /= base; } <span class="tok-keyword">while</span> (n > <span class="tok-number">0</span>); - <span class="tok-keyword">return</span> sign + result; + <span class="tok-keyword">return</span> signo + resultado; } -console.log(numberToString(<span class="tok-number">13</span>, <span class="tok-number">10</span>)); +console.log(númeroACadena(<span class="tok-number">13</span>, <span class="tok-number">10</span>)); <span class="tok-comment">// → 1.5e-3231.3e-3221.3e-3211.3e-3201.3e-3191.3e-3181.3…</span></pre> <p><a class="p_ident" id="p-sU8Vx9sd2m" href="#p-sU8Vx9sd2m" tabindex="-1" role="presentation"></a>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é.</p> -<p><a class="p_ident" id="p-coTKqI9MYU" href="#p-coTKqI9MYU" tabindex="-1" role="presentation"></a>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, <em>piensa</em>. 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.</p> +<p><a class="p_ident" id="p-coTKqI9MYU" href="#p-coTKqI9MYU" tabindex="-1" role="presentation"></a>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, <em>piensa</em>. 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.</p> -<p><a class="p_ident" id="p-CJZqnlaGs3" href="#p-CJZqnlaGs3" tabindex="-1" role="presentation"></a>Colocar algunas llamadas <code>console.log</code> 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 <code>n</code> tome los valores <code>13</code>, <code>1</code> y luego <code>0</code>. Vamos a escribir su valor al inicio del ciclo.</p> +<p><a class="p_ident" id="p-SKPtzdC+ed" href="#p-SKPtzdC+ed" tabindex="-1" role="presentation"></a>Colocar algunas llamadas a <code>console.log</code> 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 <code>n</code> tome los valores <code>13</code>, <code>1</code> y luego <code>0</code>. Vamos a escribir su valor al inicio del bucle.</p> <pre class="snippet" data-language="null" ><a class="c_ident" id="c-nB/OeL8UNa" href="#c-nB/OeL8UNa" tabindex="-1" role="presentation"></a>13 1.3 @@ -159,139 +161,141 @@ <h2><a class="h_ident" id="h-nCqjdGY4Fq" href="#h-nCqjdGY4Fq" tabindex="-1" role … 1.5e-323</pre> -<p><a class="p_ident" id="p-CGETZI9u8W" href="#p-CGETZI9u8W" tabindex="-1" role="presentation"></a><em>Correcto</em>. Al dividir 13 por 10 no se produce un número entero. En lugar de <code>n /= base</code>, lo que realmente queremos es <code>n = Math.<wbr>floor(n /<wbr> base)</code> para que el número se “desplace” correctamente hacia la derecha.</p> +<p><a class="p_ident" id="p-dTuS06tEpi" href="#p-dTuS06tEpi" tabindex="-1" role="presentation"></a><em>Correcto</em>. Al dividir 13 por 10 no se produce un número entero. En lugar de <code>n /= base</code>, lo que realmente queremos es <code>n = Math.<wbr>floor(n /<wbr> base)</code> de manera que pasamos correctamente a calcular el siguiente dígito.</p> + +<p><a class="p_ident" id="p-/Nj0jLv0dQ" href="#p-/Nj0jLv0dQ" tabindex="-1" role="presentation"></a>Una alternativa a usar <code>console.log</code> para observar el comportamiento del programa es utilizar las capacidades del <em>depurador</em> de tu navegador. Los navegadores vienen con la capacidad de establecer un <em>punto de interrupción</em> 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.</p> -<p><a class="p_ident" id="p-iCF+nPuolv" href="#p-iCF+nPuolv" tabindex="-1" role="presentation"></a>Una alternativa a usar <code>console.log</code> para observar el comportamiento del programa es utilizar las capacidades del <em>depurador</em> de tu navegador. Los navegadores vienen con la capacidad de establecer un <em>punto de interrupción</em> 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 <code>debugger</code> (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.</p> +<p><a class="p_ident" id="p-IgFTQktaD/" href="#p-IgFTQktaD/" tabindex="-1" role="presentation"></a>Otra forma de establecer un punto de interrupción es incluir una instrucción <code>debugger</code> (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.</p> <h2><a class="h_ident" id="h-a0SXG/vlkA" href="#h-a0SXG/vlkA" tabindex="-1" role="presentation"></a>Propagación de errores</h2> -<p><a class="p_ident" id="p-hHvvnQa31B" href="#p-hHvvnQa31B" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-1rQZVbh6Us" href="#p-1rQZVbh6Us" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-rFotl1BZs4" href="#p-rFotl1BZs4" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-rFotl1BZs4" href="#p-rFotl1BZs4" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-lJIpzewONf" href="#p-lJIpzewONf" tabindex="-1" role="presentation"></a>Imaginemos que tienes una función <code>promptNumber</code> que solicita al usuario un número y lo retorna. ¿Qué debería retornar si el usuario ingresa “naranja”?</p> +<p><a class="p_ident" id="p-h8AcrLNLKm" href="#p-h8AcrLNLKm" tabindex="-1" role="presentation"></a>Imaginemos que tienes una función <code>solicitarNúmero</code> que solicita al usuario un número y lo devuelve. ¿Qué debería devolver si el usuario dice “naranja”?</p> -<p><a class="p_ident" id="p-SQNPd296Uq" href="#p-SQNPd296Uq" tabindex="-1" role="presentation"></a>Una opción es hacer que retorne un valor especial. Las opciones comunes para tales valores son <code>null</code>, <code>undefined</code> o -1.</p> +<p><a class="p_ident" id="p-SQNPd296Uq" href="#p-SQNPd296Uq" tabindex="-1" role="presentation"></a>Una opción es hacer que devuelva un valor especial. Algunas opciones comunes para tales valores son <code>null</code>, <code>undefined</code> o -1.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-A6EqMxOZ3z" href="#c-A6EqMxOZ3z" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">promptNumber</span>(<span class="tok-definition">pregunta</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-AJfp9iTC/8" href="#c-AJfp9iTC/8" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">solicitarNúmero</span>(<span class="tok-definition">pregunta</span>) { <span class="tok-keyword">let</span> <span class="tok-definition">resultado</span> = Number(prompt(pregunta)); <span class="tok-keyword">if</span> (Number.isNaN(resultado)) <span class="tok-keyword">return</span> <span class="tok-keyword">null</span>; <span class="tok-keyword">else</span> <span class="tok-keyword">return</span> resultado; } -console.log(promptNumber(<span class="tok-string">"¿Cuántos árboles ves?"</span>));</pre> +console.log(solicitarNúmero(<span class="tok-string">"¿Cuántos árboles ves?"</span>));</pre> -<p><a class="p_ident" id="p-iEwNnwQra+" href="#p-iEwNnwQra+" tabindex="-1" role="presentation"></a>Ahora, cualquier código que llame a <code>promptNumber</code> 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ó.</p> +<p><a class="p_ident" id="p-iEwNnwQra+" href="#p-iEwNnwQra+" tabindex="-1" role="presentation"></a>Ahora, cualquier código que llame a <code>solicitarNúmero</code> 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ó.</p> -<p><a class="p_ident" id="p-ETfHsxU1W4" href="#p-ETfHsxU1W4" tabindex="-1" role="presentation"></a>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 <code>next</code> en la interfaz del iterador.</p> +<p><a class="p_ident" id="p-ETfHsxU1W4" href="#p-ETfHsxU1W4" tabindex="-1" role="presentation"></a>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 <code>next</code> en la interfaz del iterador.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-6yglyj6Pat" href="#c-6yglyj6Pat" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">lastElement</span>(<span class="tok-definition">arreglo</span>) { - <span class="tok-keyword">if</span> (arreglo.length == <span class="tok-number">0</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-/05SBLwLph" href="#c-/05SBLwLph" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">últimoElemento</span>(<span class="tok-definition">array</span>) { + <span class="tok-keyword">if</span> (array.length == <span class="tok-number">0</span>) { <span class="tok-keyword">return</span> {<span class="tok-definition">falló</span>: true}; } <span class="tok-keyword">else</span> { - <span class="tok-keyword">return</span> {<span class="tok-definition">valor</span>: arreglo[arreglo.length - <span class="tok-number">1</span>]}; + <span class="tok-keyword">return</span> {<span class="tok-definition">valor</span>: array[array.length - <span class="tok-number">1</span>]}; } }</pre> -<p><a class="p_ident" id="p-dIo5IzVcnp" href="#p-dIo5IzVcnp" tabindex="-1" role="presentation"></a>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 <code>promptNumber</code> 10 veces, tendrá que verificar 10 veces si se devolvió <code>null</code>. Y si su respuesta al encontrar <code>null</code> es simplemente devolver <code>null</code> en sí mismo, los llamantes de la función a su vez tendrán que comprobarlo, y así sucesivamente.</p> +<p><a class="p_ident" id="p-dIo5IzVcnp" href="#p-dIo5IzVcnp" tabindex="-1" role="presentation"></a>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 <code>solicitarNúmero</code> 10 veces, tendrá que verificar 10 veces si se devolvió <code>null</code>. Y si su respuesta al encontrar <code>null</code> es simplemente devolver <code>null</code> en sí mismo, los que llamen a la función a su vez tendrán que comprobarlo, y así sucesivamente.</p> <h2><a class="h_ident" id="h-6MskLb8+Lo" href="#h-6MskLb8+Lo" tabindex="-1" role="presentation"></a>Excepciones</h2> <p><a class="p_ident" id="p-8kL59rmzNL" href="#p-8kL59rmzNL" tabindex="-1" role="presentation"></a>Cuando una función no puede proceder normalmente, lo que a menudo <em>queremos</em> 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 <em>manejo de excepciones</em>.</p> -<p><a class="p_ident" id="p-8kP1DbEOE1" href="#p-8kP1DbEOE1" tabindex="-1" role="presentation"></a>Las excepciones son un mecanismo que hace posible que el código que se encuentra con un problema <em>lanze</em> (o <em>emita</em>) 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 <em>desenrollar la pila</em>. Puede recordar la pila de llamadas a funciones que se mencionó en el <a href="03_functions.html#stack">Capítulo 3</a>. Una excepción recorre esta pila, descartando todos los contextos de llamada que encuentra.</p> +<p><a class="p_ident" id="p-8kP1DbEOE1" href="#p-8kP1DbEOE1" tabindex="-1" role="presentation"></a>Las excepciones son un mecanismo que hace posible que el código que se encuentra con un problema <em>lance</em> (o <em>emita</em>) 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 <em>desenrollar la pila</em>. Recordarás la pila de llamadas a funciones que se mencionó en el <a href="03_functions.html#stack">Capítulo 3</a>. Una excepción recorre esta pila, descartando todos los contextos de llamada que encuentra.</p> -<p><a class="p_ident" id="p-YezykK0rip" href="#p-YezykK0rip" tabindex="-1" role="presentation"></a>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 <em>capturar</em> 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.</p> +<p><a class="p_ident" id="p-YezykK0rip" href="#p-YezykK0rip" tabindex="-1" role="presentation"></a>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 <em>capturar</em> 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.</p> <p><a class="p_ident" id="p-Od/5LHikVf" href="#p-Od/5LHikVf" tabindex="-1" role="presentation"></a>Aquí tienes un ejemplo:</p> -<pre id="look" tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-swByM+OBOa" href="#c-swByM+OBOa" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">promptDirection</span>(<span class="tok-definition">question</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">result</span> = prompt(question); - <span class="tok-keyword">if</span> (result.toLowerCase() == <span class="tok-string">"left"</span>) <span class="tok-keyword">return</span> <span class="tok-string">"L"</span>; - <span class="tok-keyword">if</span> (result.toLowerCase() == <span class="tok-string">"right"</span>) <span class="tok-keyword">return</span> <span class="tok-string">"R"</span>; - <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> Error(<span class="tok-string">"Dirección inválida: "</span> + result); +<pre id="look" tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-CaXW6zGoY3" href="#c-CaXW6zGoY3" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">solicitarDirección</span>(<span class="tok-definition">pregunta</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">resultado</span> = prompt(pregunta); + <span class="tok-keyword">if</span> (resultado.toLowerCase() == <span class="tok-string">"izquierda"</span>) <span class="tok-keyword">return</span> <span class="tok-string">"L"</span>; + <span class="tok-keyword">if</span> (resultado.toLowerCase() == <span class="tok-string">"derecha"</span>) <span class="tok-keyword">return</span> <span class="tok-string">"R"</span>; + <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> Error(<span class="tok-string">"Dirección inválida: "</span> + resultado); } -<span class="tok-keyword">function</span> <span class="tok-definition">look</span>() { - <span class="tok-keyword">if</span> (promptDirection(<span class="tok-string">"¿Hacia dónde?"</span>) == <span class="tok-string">"L"</span>) { +<span class="tok-keyword">function</span> <span class="tok-definition">mirar</span>() { + <span class="tok-keyword">if</span> (solicitarDirección(<span class="tok-string">"¿Hacia dónde?"</span>) == <span class="tok-string">"L"</span>) { <span class="tok-keyword">return</span> <span class="tok-string">"una casa"</span>; } <span class="tok-keyword">else</span> { - <span class="tok-keyword">return</span> <span class="tok-string">"dos osos enojados"</span>; + <span class="tok-keyword">return</span> <span class="tok-string">"dos osos enfadados"</span>; } } <span class="tok-keyword">try</span> { - console.log(<span class="tok-string">"Ves"</span>, look()); + console.log(<span class="tok-string">"Ves"</span>, mirar()); } <span class="tok-keyword">catch</span> (<span class="tok-definition">error</span>) { console.log(<span class="tok-string">"Algo salió mal: "</span> + error); }</pre> -<p><a class="p_ident" id="p-9Kt5Hil9tT" href="#p-9Kt5Hil9tT" tabindex="-1" role="presentation"></a>La palabra clave <code>throw</code> se utiliza para lanzar una excepción. La captura de una excepción se realiza envolviendo un trozo de código en un bloque <code>try</code>, seguido de la palabra clave <code>catch</code>. Cuando el código en el bloque <code>try</code> provoca que se lance una excepción, se evalúa el bloque <code>catch</code>, con el nombre entre paréntesis vinculado al valor de la excepción. Después de que el bloque <code>catch</code> finalice, o si el bloque <code>try</code> finaliza sin problemas, el programa continúa debajo de toda la instrucción <code>try/catch</code>.</p> +<p><a class="p_ident" id="p-9Kt5Hil9tT" href="#p-9Kt5Hil9tT" tabindex="-1" role="presentation"></a>La palabra clave <code>throw</code> se utiliza para lanzar una excepción. La captura de una excepción se realiza envolviendo un trozo de código en un bloque <code>try</code>, seguido de la palabra clave <code>catch</code>. Cuando el código en el bloque <code>try</code> provoca que se lance una excepción, se evalúa el bloque <code>catch</code>, con el nombre entre paréntesis vinculado al valor de la excepción. Cuando el bloque <code>catch</code> acabe, o cuando el bloque <code>try</code> finalice sin problemas, el programa continúa debajo de toda la instrucción <code>try/catch</code>.</p> -<p><a class="p_ident" id="p-q0ChDezAsm" href="#p-q0ChDezAsm" tabindex="-1" role="presentation"></a>En este caso, utilizamos el constructor <code>Error</code> para crear nuestro valor de excepción. Este es un constructor de JavaScript estándar que crea un objeto con una propiedad <code>message</code>. Las instancias de <code>Error</code> también recopilan información sobre la pila de llamadas que existía cuando se creó la excepción, una llamada <em>traza de pila</em>. Esta información se almacena en la propiedad <code>stack</code> 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.</p> +<p><a class="p_ident" id="p-q0ChDezAsm" href="#p-q0ChDezAsm" tabindex="-1" role="presentation"></a>En este caso, utilizamos el constructor <code>Error</code> para crear nuestro valor de excepción. Este es un constructor de JavaScript estándar que crea un objeto con una propiedad <code>message</code>. Las instancias de <code>Error</code> 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 <em>traza de pila</em>. Esta información se almacena en la propiedad <code>stack</code> 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.</p> -<p><a class="p_ident" id="p-T1aoVSKCOg" href="#p-T1aoVSKCOg" tabindex="-1" role="presentation"></a>Ten en cuenta que la función <code>look</code> ignora por completo la posibilidad de que <code>promptDirection</code> 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.</p> +<p><a class="p_ident" id="p-T1aoVSKCOg" href="#p-T1aoVSKCOg" tabindex="-1" role="presentation"></a>Ten en cuenta que la función <code>mirar</code> ignora por completo la posibilidad de que <code>solicitarDirección</code> 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.</p> <p><a class="p_ident" id="p-+6sQLyxE9M" href="#p-+6sQLyxE9M" tabindex="-1" role="presentation"></a>Bueno, casi...</p> <h2><a class="h_ident" id="h-hqzvMe3/h2" href="#h-hqzvMe3/h2" tabindex="-1" role="presentation"></a>Limpiando después de excepciones</h2> -<p><a class="p_ident" id="p-7uCWVclhtM" href="#p-7uCWVclhtM" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-9pE1spI92B" href="#p-9pE1spI92B" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-JHIccV3ujU" href="#p-JHIccV3ujU" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-YwnT2m/26w" href="#p-YwnT2m/26w" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-5LKB1foGUZ" href="#p-5LKB1foGUZ" tabindex="-1" role="presentation"></a>Aquí tienes un código bancario realmente malo.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-F6LcCu8akJ" href="#c-F6LcCu8akJ" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">accounts</span> = { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ag+/9aG2bF" href="#c-ag+/9aG2bF" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">cuentas</span> = { <span class="tok-definition">a</span>: <span class="tok-number">100</span>, <span class="tok-definition">b</span>: <span class="tok-number">0</span>, <span class="tok-definition">c</span>: <span class="tok-number">20</span> }; -<span class="tok-keyword">function</span> <span class="tok-definition">getAccount</span>() { - <span class="tok-keyword">let</span> <span class="tok-definition">accountName</span> = prompt(<span class="tok-string">"Ingresa el nombre de una cuenta"</span>); - <span class="tok-keyword">if</span> (!Object.hasOwn(accounts, accountName)) { - <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> Error(<span class="tok-string2">`No existe esa cuenta: </span>${accountName}<span class="tok-string2">`</span>); +<span class="tok-keyword">function</span> <span class="tok-definition">obtenerCuenta</span>() { + <span class="tok-keyword">let</span> <span class="tok-definition">nombreCuenta</span> = prompt(<span class="tok-string">"Ingresa el nombre de una cuenta"</span>); + <span class="tok-keyword">if</span> (!Object.hasOwn(cuentas, nombreCuenta)) { + <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> Error(<span class="tok-string2">`No existe esa cuenta: </span>${nombreCuenta}<span class="tok-string2">`</span>); } - <span class="tok-keyword">return</span> accountName; + <span class="tok-keyword">return</span> nombreCuenta; } -<span class="tok-keyword">function</span> <span class="tok-definition">transfer</span>(<span class="tok-definition">from</span>, <span class="tok-definition">amount</span>) { - <span class="tok-keyword">if</span> (accounts[from] < amount) <span class="tok-keyword">return</span>; - accounts[from] -= amount; - accounts[getAccount()] += amount; +<span class="tok-keyword">function</span> <span class="tok-definition">transferir</span>(<span class="tok-definition">desde</span>, <span class="tok-definition">cantidad</span>) { + <span class="tok-keyword">if</span> (cuentas[desde] < cantidad) <span class="tok-keyword">return</span>; + cuentas[desde] -= cantidad; + cuentas[obtenerCuenta()] += cantidad; }</pre> -<p><a class="p_ident" id="p-Ue7mDsJTml" href="#p-Ue7mDsJTml" tabindex="-1" role="presentation"></a>La función <code>transfer</code> 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, <code>getAccount</code> lanza una excepción.</p> +<p><a class="p_ident" id="p-Ue7mDsJTml" href="#p-Ue7mDsJTml" tabindex="-1" role="presentation"></a>La función <code>transferir</code> 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, <code>obtenerCuenta</code> lanza una excepción.</p> -<p><a class="p_ident" id="p-tKLQSzwcma" href="#p-tKLQSzwcma" tabindex="-1" role="presentation"></a>Pero <code>transfer</code> <em>primero</em> retira el dinero de la cuenta y <em>luego</em> llama a <code>getAccount</code> antes de agregarlo a otra cuenta. Si se interrumpe por una excepción en ese momento, simplemente hará desaparecer el dinero.</p> +<p><a class="p_ident" id="p-tKLQSzwcma" href="#p-tKLQSzwcma" tabindex="-1" role="presentation"></a>Pero <code>transferir</code> <em>primero</em> retira el dinero de la cuenta y <em>luego</em> llama a <code>obtenerCuenta</code> antes de agregarlo a otra cuenta. Si se interrumpe por una excepción en ese momento, simplemente hará desaparecer el dinero.</p> -<p><a class="p_ident" id="p-5TO5JGAD50" href="#p-5TO5JGAD50" tabindex="-1" role="presentation"></a>Ese código podría haber sido escrito de manera un poco más inteligente, por ejemplo, llamando a <code>getAccount</code> 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.</p> +<p><a class="p_ident" id="p-5TO5JGAD50" href="#p-5TO5JGAD50" tabindex="-1" role="presentation"></a>Ese código podría haber sido escrito de manera un poco más inteligente, por ejemplo, llamando a <code>obtenerCuenta</code> 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.</p> -<p><a class="p_ident" id="p-FW9w5Bk33N" href="#p-FW9w5Bk33N" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-FW9w5Bk33N" href="#p-FW9w5Bk33N" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-7MIgFxyQWl" href="#p-7MIgFxyQWl" tabindex="-1" role="presentation"></a>Pero eso no siempre es práctico. Por eso existe otra característica que tienen las instrucciones <code>try</code>. Pueden estar seguidas de un bloque <code>finally</code> en lugar o además de un bloque <code>catch</code>. Un bloque <code>finally</code> dice “sin importar <em>qué</em> suceda, ejecuta este código después de intentar ejecutar el código en el bloque <code>try</code>.”</p> +<p><a class="p_ident" id="p-Ud9iqy00wY" href="#p-Ud9iqy00wY" tabindex="-1" role="presentation"></a>Como eso no siempre es práctico, las instrucciones <code>try</code> tienen otra funcionalidad: pueden estar seguidas de un bloque <code>finally</code> en lugar o además de un bloque <code>catch</code>. Un bloque <code>finally</code> dice “sin importar <em>qué</em> suceda, ejecuta este código después de intentar ejecutar el código en el bloque <code>try</code>.”</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-brWpzDAy4+" href="#c-brWpzDAy4+" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">transfer</span>(<span class="tok-definition">from</span>, <span class="tok-definition">amount</span>) { - <span class="tok-keyword">if</span> (accounts[from] < amount) <span class="tok-keyword">return</span>; - <span class="tok-keyword">let</span> <span class="tok-definition">progress</span> = <span class="tok-number">0</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-HpyAFY5eeO" href="#c-HpyAFY5eeO" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">transferir</span>(<span class="tok-definition">desde</span>, <span class="tok-definition">cantidad</span>) { + <span class="tok-keyword">if</span> (cuentas[desde] < cantidad) <span class="tok-keyword">return</span>; + <span class="tok-keyword">let</span> <span class="tok-definition">progreso</span> = <span class="tok-number">0</span>; <span class="tok-keyword">try</span> { - accounts[from] -= amount; - progress = <span class="tok-number">1</span>; - accounts[getAccount()] += amount; - progress = <span class="tok-number">2</span>; + cuentas[desde] -= cantidad; + progreso = <span class="tok-number">1</span>; + cuentas[obtenerCuenta()] += cantidad; + progreso = <span class="tok-number">2</span>; } <span class="tok-keyword">finally</span> { - <span class="tok-keyword">if</span> (progress == <span class="tok-number">1</span>) { - accounts[from] += amount; + <span class="tok-keyword">if</span> (progreso == <span class="tok-number">1</span>) { + cuentas[desde] += cantidad; } } }</pre> -<p><a class="p_ident" id="p-BUFc2zwQBC" href="#p-BUFc2zwQBC" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-BUFc2zwQBC" href="#p-BUFc2zwQBC" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-J7xmEOV61W" href="#p-J7xmEOV61W" tabindex="-1" role="presentation"></a>Cabe destacar que aunque el código <code>finally</code> se ejecuta cuando se lanza una excepción en el bloque <code>try</code>, no interfiere con la excepción. Después de que se ejecuta el bloque <code>finally</code>, la pila continúa desenrollándose.</p> -<p><a class="p_ident" id="p-mNFQ5eg1AO" href="#p-mNFQ5eg1AO" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-mNFQ5eg1AO" href="#p-mNFQ5eg1AO" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-PIKTwL5/C0" href="#h-PIKTwL5/C0" tabindex="-1" role="presentation"></a>Captura selectiva</h2> @@ -299,19 +303,19 @@ <h2><a class="h_ident" id="h-PIKTwL5/C0" href="#h-PIKTwL5/C0" tabindex="-1" role <p><a class="p_ident" id="p-UCYUVC0KwK" href="#p-UCYUVC0KwK" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-igAOH/6YWz" href="#p-igAOH/6YWz" tabindex="-1" role="presentation"></a>Para problemas que se <em>espera</em> que ocurran durante el uso rutinario, fallar con una excepción no manejada es una estrategia terrible.</p> +<p><a class="p_ident" id="p-dEMvGKrWI9" href="#p-dEMvGKrWI9" tabindex="-1" role="presentation"></a>Para problemas que se <em>espera</em> que puedan ocurrir de normal, fallar con una excepción no manejada es una muy mala estrategia.</p> <p><a class="p_ident" id="p-WgcUeDPSV4" href="#p-WgcUeDPSV4" tabindex="-1" role="presentation"></a>Usos incorrectos del lenguaje, como hacer referencia a un enlace inexistente, buscar una propiedad en <code>null</code> 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.</p> <p><a class="p_ident" id="p-OgoTGGt/RP" href="#p-OgoTGGt/RP" tabindex="-1" role="presentation"></a>Cuando se entra en un cuerpo <code>catch</code>, todo lo que sabemos es que <em>algo</em> en nuestro cuerpo <code>try</code> causó una excepción. Pero no sabemos <em>qué</em> lo hizo ni <em>qué</em> excepción causó.</p> -<p><a class="p_ident" id="p-Y7D20P1igb" href="#p-Y7D20P1igb" tabindex="-1" role="presentation"></a>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 <em>asumir</em> que la excepción que obtienes es la que tenías en mente cuando escribiste el bloque <code>catch</code>.</p> +<p><a class="p_ident" id="p-Y7D20P1igb" href="#p-Y7D20P1igb" tabindex="-1" role="presentation"></a>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 <em>asumir</em> que la excepción que obtienes es la que tenías en mente cuando escribiste el bloque <code>catch</code>.</p> -<p><a class="p_ident" id="p-UqZdrEXvRM" href="#p-UqZdrEXvRM" tabindex="-1" role="presentation"></a>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 <em>intenta</em> seguir llamando a <code>promptDirection</code> hasta obtener una respuesta válida:</p> +<p><a class="p_ident" id="p-UqZdrEXvRM" href="#p-UqZdrEXvRM" tabindex="-1" role="presentation"></a>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 <em>intenta</em> seguir llamando a <code>solicitarDirección</code> hasta obtener una respuesta válida:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NOeU9RGWf4" href="#c-NOeU9RGWf4" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (;;) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-VroZcSwwxp" href="#c-VroZcSwwxp" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (;;) { <span class="tok-keyword">try</span> { - <span class="tok-keyword">let</span> <span class="tok-definition">dir</span> = promptDirection(<span class="tok-string">"¿Dónde?"</span>); <span class="tok-comment">// ← ¡Error de tipeo!</span> + <span class="tok-keyword">let</span> <span class="tok-definition">dir</span> = soliitarDirección(<span class="tok-string">"¿Dónde?"</span>); <span class="tok-comment">// ← ¡Error de tipeo!</span> console.log(<span class="tok-string">"Elegiste "</span>, dir); <span class="tok-keyword">break</span>; } <span class="tok-keyword">catch</span> (<span class="tok-definition">e</span>) { @@ -319,32 +323,32 @@ <h2><a class="h_ident" id="h-PIKTwL5/C0" href="#h-PIKTwL5/C0" tabindex="-1" role } }</pre> -<p><a class="p_ident" id="p-UAeKsDMazt" href="#p-UAeKsDMazt" tabindex="-1" role="presentation"></a>La construcción <code>for (;;)</code> 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. <em>Pero</em> escribimos mal <code>promptDirection</code>, lo que resultará en un error de “variable no definida”. Debido a que el bloque <code>catch</code> ignora por completo el valor de la excepción (<code>e</code>), 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.</p> +<p><a class="p_ident" id="p-UAeKsDMazt" href="#p-UAeKsDMazt" tabindex="-1" role="presentation"></a>La construcción <code>for (;;)</code> 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. <em>Pero</em> escribimos mal <code>solicitarDirección</code>, lo que resultará en un error de “variable no definida”. Debido a que el bloque <code>catch</code> ignora por completo el valor de la excepción (<code>e</code>), 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.</p> -<p><a class="p_ident" id="p-1NuYJNWPUR" href="#p-1NuYJNWPUR" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-1NuYJNWPUR" href="#p-1NuYJNWPUR" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-0cwV1xSMNN" href="#p-0cwV1xSMNN" tabindex="-1" role="presentation"></a>Por lo tanto, queremos capturar un tipo <em>específico</em> de excepción. Podemos hacer esto verificando en el bloque <code>catch</code> si la excepción que recibimos es la que nos interesa y relanzándola en caso contrario. Pero, ¿cómo reconocemos una excepción?</p> +<p><a class="p_ident" id="p-+FVUOqu2nd" href="#p-+FVUOqu2nd" tabindex="-1" role="presentation"></a>Queremos capturar un tipo <em>específico</em> de excepción. Podemos hacer esto verificando en el bloque <code>catch</code> si la excepción que recibimos es la que nos interesa y relanzándola en caso contrario. Pero, ¿cómo reconocemos una excepción?</p> -<p><a class="p_ident" id="p-MZCZA8Qif4" href="#p-MZCZA8Qif4" tabindex="-1" role="presentation"></a>Podríamos comparar su propiedad <code>message</code> 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.</p> +<p><a class="p_ident" id="p-MZCZA8Qif4" href="#p-MZCZA8Qif4" tabindex="-1" role="presentation"></a>Podríamos comparar su propiedad <code>message</code> 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.</p> <p><a class="p_ident" id="p-wQ55TKoktB" href="#p-wQ55TKoktB" tabindex="-1" role="presentation"></a>En lugar de eso, definamos un nuevo tipo de error y usemos <code>instanceof</code> para identificarlo.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-r494S84CGw" href="#c-r494S84CGw" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> InputError <span class="tok-keyword">extends</span> Error {} +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-E3WVzm1AjN" href="#c-E3WVzm1AjN" tabindex="-1" role="presentation"></a><span class="tok-keyword">class</span> InputError <span class="tok-keyword">extends</span> Error {} -<span class="tok-keyword">function</span> <span class="tok-definition">promptDirection</span>(<span class="tok-definition">question</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">result</span> = prompt(question); - <span class="tok-keyword">if</span> (result.toLowerCase() == <span class="tok-string">"izquierda"</span>) <span class="tok-keyword">return</span> <span class="tok-string">"I"</span>; - <span class="tok-keyword">if</span> (result.toLowerCase() == <span class="tok-string">"derecha"</span>) <span class="tok-keyword">return</span> <span class="tok-string">"D"</span>; - <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> InputError(<span class="tok-string">"Dirección no válida: "</span> + result); +<span class="tok-keyword">function</span> <span class="tok-definition">solicitarDirección</span>(<span class="tok-definition">pregunta</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">resultado</span> = prompt(pregunta); + <span class="tok-keyword">if</span> (resultado.toLowerCase() == <span class="tok-string">"izquierda"</span>) <span class="tok-keyword">return</span> <span class="tok-string">"I"</span>; + <span class="tok-keyword">if</span> (resultado.toLowerCase() == <span class="tok-string">"derecha"</span>) <span class="tok-keyword">return</span> <span class="tok-string">"D"</span>; + <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> InputError(<span class="tok-string">"Dirección no válida: "</span> + resultado); }</pre> -<p><a class="p_ident" id="p-LXSi1q/9tP" href="#p-LXSi1q/9tP" tabindex="-1" role="presentation"></a>La nueva clase de error extiende <code>Error</code>. No define su propio constructor, lo que significa que hereda el constructor de <code>Error</code>, que espera un mensaje de cadena como argumento. De hecho, no define nada en absoluto, la clase está vacía. Los objetos <code>InputError</code> se comportan como objetos <code>Error</code>, excepto que tienen una clase diferente mediante la cual podemos reconocerlos.</p> +<p><a class="p_ident" id="p-LXSi1q/9tP" href="#p-LXSi1q/9tP" tabindex="-1" role="presentation"></a>La nueva clase de error extiende la clase <code>Error</code>. No define su propio constructor, lo que significa que hereda el constructor de <code>Error</code>, que espera un mensaje de cadena como argumento. De hecho, no define nada en absoluto, la clase está vacía. Los objetos <code>InputError</code> se comportan como objetos <code>Error</code>, excepto que tienen una clase diferente mediante la cual podemos reconocerlos.</p> <p><a class="p_ident" id="p-Jf9157PX0y" href="#p-Jf9157PX0y" tabindex="-1" role="presentation"></a>Ahora el bucle puede capturar esto con más cuidado.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-8cxyw/dOkq" href="#c-8cxyw/dOkq" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (;;) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-3oPX2PRKMF" href="#c-3oPX2PRKMF" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (;;) { <span class="tok-keyword">try</span> { - <span class="tok-keyword">let</span> <span class="tok-definition">dir</span> = promptDirection(<span class="tok-string">"¿Dónde?"</span>); + <span class="tok-keyword">let</span> <span class="tok-definition">dir</span> = solicitarDirección(<span class="tok-string">"¿Dónde?"</span>); console.log(<span class="tok-string">"Elegiste "</span>, dir); <span class="tok-keyword">break</span>; } <span class="tok-keyword">catch</span> (<span class="tok-definition">e</span>) { @@ -356,11 +360,11 @@ <h2><a class="h_ident" id="h-PIKTwL5/C0" href="#h-PIKTwL5/C0" tabindex="-1" role } }</pre> -<p><a class="p_ident" id="p-1HdJVWn4X+" href="#p-1HdJVWn4X+" tabindex="-1" role="presentation"></a>Esto capturará solo instancias de <code>InputError</code> y permitirá que pasen excepciones no relacionadas. Si vuelves a introducir el error de tipeo, el error de enlace no definido se informará correctamente.</p> +<p><a class="p_ident" id="p-1HdJVWn4X+" href="#p-1HdJVWn4X+" tabindex="-1" role="presentation"></a>Esto capturará solo instancias de <code>InputError</code> 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.</p> -<h2><a class="h_ident" id="h-cLlvsxFcXB" href="#h-cLlvsxFcXB" tabindex="-1" role="presentation"></a>Afirmaciones</h2> +<h2><a class="h_ident" id="h-6QSGaYmkGZ" href="#h-6QSGaYmkGZ" tabindex="-1" role="presentation"></a>Asertos</h2> -<p><a class="p_ident" id="p-ETMuysY1G8" href="#p-ETMuysY1G8" tabindex="-1" role="presentation"></a>Las <em>afirmaciones</em> 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.</p> +<p><a class="p_ident" id="p-4K5QKV9e/t" href="#p-4K5QKV9e/t" tabindex="-1" role="presentation"></a>Los <em>asertos</em> 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.</p> <p><a class="p_ident" id="p-QSJMiocH0E" href="#p-QSJMiocH0E" tabindex="-1" role="presentation"></a>Si, por ejemplo, se describe <code>primerElemento</code> como una función que nunca debería ser llamada en arrays vacíos, podríamos escribirla de la siguiente manera:</p> @@ -371,15 +375,15 @@ <h2><a class="h_ident" id="h-cLlvsxFcXB" href="#h-cLlvsxFcXB" tabindex="-1" role <span class="tok-keyword">return</span> array[<span class="tok-number">0</span>]; }</pre> -<p><a class="p_ident" id="p-d/otm+M+EY" href="#p-d/otm+M+EY" tabindex="-1" role="presentation"></a>Ahora, en lugar de devolver silenciosamente <code>undefined</code> (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.</p> +<p><a class="p_ident" id="p-d/otm+M+EY" href="#p-d/otm+M+EY" tabindex="-1" role="presentation"></a>Ahora, en lugar de devolver silenciosamente <code>undefined</code> (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.</p> -<p><a class="p_ident" id="p-qa66fHY3o/" href="#p-qa66fHY3o/" tabindex="-1" role="presentation"></a>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).</p> +<p><a class="p_ident" id="p-v9ojiAEAn5" href="#p-v9ojiAEAn5" tabindex="-1" role="presentation"></a>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).</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-X9IA4vkVjU" href="#p-X9IA4vkVjU" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-X9IA4vkVjU" href="#p-X9IA4vkVjU" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-baGWPNVr6u" href="#p-baGWPNVr6u" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-mQ7Clp8c5G" href="#p-mQ7Clp8c5G" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-nuQxuTcODL" href="#p-nuQxuTcODL" tabindex="-1" role="presentation"></a>Lanzar una excepción provoca que la pila de llamadas se desenrolle hasta el próximo bloque <code>try/catch</code> envolvente o hasta la base de la pila. El valor de la excepción será entregado al bloque <code>catch</code> 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 <code>finally</code> para asegurar que un trozo de código se ejecute <em>siempre</em> cuando un bloque termina.</p> @@ -412,7 +416,7 @@ <h3><a class="i_ident" id="i-rex7TyNReT" href="#i-rex7TyNReT" tabindex="-1" role <p><a class="p_ident" id="p-mI8+1MDBby" href="#p-mI8+1MDBby" tabindex="-1" role="presentation"></a>La llamada a <code>primitiveMultiply</code> definitivamente debería ocurrir en un bloque <code>try</code>. El bloque <code>catch</code> correspondiente debería relanzar la excepción cuando no sea una instancia de <code>MultiplicatorUnitFailure</code> y asegurarse de que la llamada se reintente cuando lo sea.</p> -<p><a class="p_ident" id="p-SZQFD46n4X" href="#p-SZQFD46n4X" tabindex="-1" role="presentation"></a>Para hacer el reintentamiento, puedes usar un bucle que se detenga solo cuando una llamada tiene éxito, como en el ejemplo de <a href="08_error.html#look"><code>look</code></a> 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).</p> +<p><a class="p_ident" id="p-SZQFD46n4X" href="#p-SZQFD46n4X" tabindex="-1" role="presentation"></a>Para hacer el reintento, puedes usar un bucle que se detenga solo cuando una llamada tiene éxito, como en el ejemplo de <a href="08_error.html#look"><code>mirar</code></a> 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).</p> </div></details> @@ -434,7 +438,7 @@ <h3><a class="i_ident" id="i-nTTe2xnK1+" href="#i-nTTe2xnK1+" tabindex="-1" role <p><a class="p_ident" id="p-a5WTrn1t1W" href="#p-a5WTrn1t1W" tabindex="-1" role="presentation"></a>Es una caja con una cerradura. Hay un array en la caja, pero solo puedes acceder a él cuando la caja está desbloqueada.</p> -<p><a class="p_ident" id="p-AU2xyE4g0d" href="#p-AU2xyE4g0d" tabindex="-1" role="presentation"></a>Escribe una función llamada <code>withBoxUnlocked</code> 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.</p> +<p><a class="p_ident" id="p-AU2xyE4g0d" href="#p-AU2xyE4g0d" tabindex="-1" role="presentation"></a>Escribe una función llamada <code>withBoxUnlocked</code> 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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-w1vGwb4pIE" href="#c-w1vGwb4pIE" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">box</span> = <span class="tok-keyword">new</span> <span class="tok-keyword">class</span> { <span class="tok-definition">locked</span> = true; @@ -466,7 +470,7 @@ <h3><a class="i_ident" id="i-nTTe2xnK1+" href="#i-nTTe2xnK1+" tabindex="-1" role console.log(box.locked); <span class="tok-comment">// → true</span></pre> -<p><a class="p_ident" id="p-UCwwvERdRK" href="#p-UCwwvERdRK" tabindex="-1" role="presentation"></a>Para puntos adicionales, asegúrate de que si llamas a <code>withBoxUnlocked</code> cuando la caja ya está desbloqueada, la caja permanezca desbloqueada.</p> +<p><a class="p_ident" id="p-LIcwpncUN0" href="#p-LIcwpncUN0" tabindex="-1" role="presentation"></a>Para más puntos, asegúrate de que si llamas a <code>withBoxUnlocked</code> cuando la caja ya está desbloqueada, la caja permanezca desbloqueada.</p> <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> diff --git a/html/09_regexp.html b/html/09_regexp.html index cfa8cac3..1607689f 100644 --- a/html/09_regexp.html +++ b/html/09_regexp.html @@ -14,7 +14,7 @@ <h1>Expresiones regulares</h1> <blockquote> -<p><a class="p_ident" id="p-48rajKViXI" href="#p-48rajKViXI" tabindex="-1" role="presentation"></a>Algunas personas, cuando se enfrentan a un problema, piensan '¡Ya sé, usaré expresiones regulares!’ Ahora tienen dos problemas.</p> +<p><a class="p_ident" id="p-0ftJ8IRDPp" href="#p-0ftJ8IRDPp" tabindex="-1" role="presentation"></a>Hay gente que, cuando se enfrenta a un problema, piensa '¡Ya sé, usaré expresiones regulares!’ Ahora tienen dos problemas.</p> <footer>Jamie Zawinski</footer> @@ -24,19 +24,19 @@ <h1>Expresiones regulares</h1> <p><a class="p_ident" id="p-93M9wGPUIB" href="#p-93M9wGPUIB" tabindex="-1" role="presentation"></a>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.</p> -<footer>Master Yuan-Ma, <cite>El Libro de la Programación</cite></footer> +<footer>Master Yuan-Ma, <cite>The Book of Programming</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_9.jpg" alt="Ilustración de un sistema de ferrocarril que representa la estructura sintáctica de las expresiones regulares"></figure> -<p><a class="p_ident" id="p-cUW+3W7fmu" href="#p-cUW+3W7fmu" tabindex="-1" role="presentation"></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 brillantes, sino aquellas que funcionan lo suficientemente bien dentro del nicho correcto o que se integran con otra pieza exitosa de tecnología.</p> +<p><a class="p_ident" id="p-K03APtl6Xy" href="#p-K03APtl6Xy" tabindex="-1" role="presentation"></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.</p> -<p><a class="p_ident" id="p-DXsuW+HRzs" href="#p-DXsuW+HRzs" tabindex="-1" role="presentation"></a>En este capítulo, discutiré una de esas herramientas, <em>expresiones regulares</em>. 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.</p> +<p><a class="p_ident" id="p-DXsuW+HRzs" href="#p-DXsuW+HRzs" tabindex="-1" role="presentation"></a>En este capítulo, discutiré una de esas herramientas: las <em>expresiones regulares</em>. 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.</p> -<p><a class="p_ident" id="p-7XhIkGJNrR" href="#p-7XhIkGJNrR" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-7XhIkGJNrR" href="#p-7XhIkGJNrR" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-SxIVkReIo1" href="#h-SxIVkReIo1" tabindex="-1" role="presentation"></a>Creando una expresión regular</h2> -<p><a class="p_ident" id="p-Xg1xoCrnF5" href="#p-Xg1xoCrnF5" tabindex="-1" role="presentation"></a>Una expresión regular es un tipo de objeto. Puede ser construido con el constructor <code>RegExp</code> o escrito como un valor literal al encerrar un patrón entre caracteres de barra diagonal (<code>/</code>).</p> +<p><a class="p_ident" id="p-2GPfqGBjgN" href="#p-2GPfqGBjgN" tabindex="-1" role="presentation"></a>Una expresión regular es un tipo de objeto. Se puede construir con el constructor <code>RegExp</code> o escrito como un valor literal al encerrar un patrón entre caracteres de barra hacia adelante (<code>/</code>).</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-O1I2rl+HTy" href="#c-O1I2rl+HTy" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">re1</span> = <span class="tok-keyword">new</span> RegExp(<span class="tok-string">"abc"</span>); <span class="tok-keyword">let</span> <span class="tok-definition">re2</span> = <span class="tok-string2">/abc/</span>;</pre> @@ -45,11 +45,11 @@ <h2><a class="h_ident" id="h-SxIVkReIo1" href="#h-SxIVkReIo1" tabindex="-1" role <p><a class="p_ident" id="p-AD2f7pq4at" href="#p-AD2f7pq4at" tabindex="-1" role="presentation"></a>Cuando se utiliza el constructor <code>RegExp</code>, el patrón se escribe como una cadena normal, por lo que se aplican las reglas habituales para las barras invertidas.</p> -<p><a class="p_ident" id="p-KZsxzenKjx" href="#p-KZsxzenKjx" tabindex="-1" role="presentation"></a>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 <em>parte</em> del patrón. Además, las barras invertidas que no forman parte de códigos de caracteres especiales (como <code>\n</code>) serán <em>preservadas</em>, 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.</p> +<p><a class="p_ident" id="p-KZsxzenKjx" href="#p-KZsxzenKjx" tabindex="-1" role="presentation"></a>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 <em>parte</em> del patrón. Además, las barras invertidas que no forman parte de códigos de caracteres especiales (como <code>\n</code>) serán <em>preservadas</em>, 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-QdJ95//DRD" href="#c-QdJ95//DRD" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">aPlus</span> = <span class="tok-string2">/A\+/</span>;</pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-n/5kiorCCY" href="#c-n/5kiorCCY" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">unMás</span> = <span class="tok-string2">/Un\+/</span>;</pre> -<h2><a class="h_ident" id="h-s5v2OAvvFA" href="#h-s5v2OAvvFA" tabindex="-1" role="presentation"></a>Pruebas de coincidencias</h2> +<h2><a class="h_ident" id="h-fT26N0d9uL" href="#h-fT26N0d9uL" tabindex="-1" role="presentation"></a>Testeo para coincidencias</h2> <p><a class="p_ident" id="p-VTSToxvyfB" href="#p-VTSToxvyfB" tabindex="-1" role="presentation"></a>Los objetos de expresiones regulares tienen varios métodos. El más simple es <code>test</code>. Si le pasas una cadena, devolverá un Booleano indicándote si la cadena contiene una coincidencia con el patrón de la expresión.</p> @@ -58,13 +58,13 @@ <h2><a class="h_ident" id="h-s5v2OAvvFA" href="#h-s5v2OAvvFA" tabindex="-1" role console.log(<span class="tok-string2">/abc/</span>.test(<span class="tok-string">"abxde"</span>)); <span class="tok-comment">// → false</span></pre> -<p><a class="p_ident" id="p-I9d7+CqxO+" href="#p-I9d7+CqxO+" tabindex="-1" role="presentation"></a>Una expresión regular que consiste solo en caracteres no especiales simplemente representa esa secuencia de caracteres. Si <em>abc</em> aparece en cualquier parte de la cadena contra la cual estamos probando (no solo al principio), <code>test</code> devolverá <code>true</code>.</p> +<p><a class="p_ident" id="p-I9d7+CqxO+" href="#p-I9d7+CqxO+" tabindex="-1" role="presentation"></a>Una expresión regular que consiste solo en caracteres no especiales simplemente representa esa secuencia de caracteres. Si <em>abc</em> aparece en cualquier parte de la cadena contra la cual estamos testeando (no solo al principio), <code>test</code> devolverá <code>true</code>.</p> <h2><a class="h_ident" id="h-JdVGOTeHyG" href="#h-JdVGOTeHyG" tabindex="-1" role="presentation"></a>Conjuntos de caracteres</h2> <p><a class="p_ident" id="p-z3JS3xx91L" href="#p-z3JS3xx91L" tabindex="-1" role="presentation"></a>Descubrir si una cadena contiene <em>abc</em> también se podría hacer con una llamada a <code>indexOf</code>. Las expresiones regulares son útiles porque nos permiten describir patrones más complicados.</p> -<p><a class="p_ident" id="p-Eh18fULy+Q" href="#p-Eh18fULy+Q" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-Eh18fULy+Q" href="#p-Eh18fULy+Q" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-kRrDHyV1gb" href="#p-kRrDHyV1gb" tabindex="-1" role="presentation"></a>Ambas expresiones siguientes hacen coincidir todas las cadenas que contienen un dígito:</p> @@ -73,13 +73,13 @@ <h2><a class="h_ident" id="h-JdVGOTeHyG" href="#h-JdVGOTeHyG" tabindex="-1" role console.log(<span class="tok-string2">/[0-9]/</span>.test(<span class="tok-string">"in 1992"</span>)); <span class="tok-comment">// → true</span></pre> -<p><a class="p_ident" id="p-5qaMU7hqLJ" href="#p-5qaMU7hqLJ" tabindex="-1" role="presentation"></a>Dentro de corchetes, un guion (<code>-</code>) 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 <code>[0-9]</code> abarca todos ellos y coincide con cualquier dígito.</p> +<p><a class="p_ident" id="p-5qaMU7hqLJ" href="#p-5qaMU7hqLJ" tabindex="-1" role="presentation"></a>Dentro de corchetes, se puede usar un guion (<code>-</code>) 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 <code>[0-9]</code> abarca todos ellos y coincide con cualquier dígito.</p> -<p><a class="p_ident" id="p-lrGSB7/f6l" href="#p-lrGSB7/f6l" tabindex="-1" role="presentation"></a>Varios grupos comunes de caracteres tienen sus propias abreviaturas incorporadas. Los dígitos son uno de ellos: <code>\d</code> significa lo mismo que <code>[0-9]</code>.</p> +<p><a class="p_ident" id="p-fQsGT4I5RM" href="#p-fQsGT4I5RM" tabindex="-1" role="presentation"></a>Algunos grupos comunes de caracteres tienen sus propias abreviaturas incorporadas. Los dígitos son uno de ellos: <code>\d</code> significa lo mismo que <code>[0-9]</code>.</p> <table> -<tr><td><code>\d</code></td><td>Cualquier carácter dígito</td> +<tr><td><code>\d</code></td><td>Cualquier carácter de dígito</td> </tr> @@ -111,27 +111,27 @@ <h2><a class="h_ident" id="h-JdVGOTeHyG" href="#h-JdVGOTeHyG" tabindex="-1" role <p><a class="p_ident" id="p-kqSlU9BE3X" href="#p-kqSlU9BE3X" tabindex="-1" role="presentation"></a>Así que podrías hacer coincidir un formato de fecha y hora como 01-30-2003 15:20 con la siguiente expresión:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-wJvOGyKwyj" href="#c-wJvOGyKwyj" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">dateTime</span> = <span class="tok-string2">/\d\d-\d\d-\d\d\d\d \d\d:\d\d/</span>; -console.log(dateTime.test(<span class="tok-string">"01-30-2003 15:20"</span>)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Rb8DI8Ewng" href="#c-Rb8DI8Ewng" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">fechaYHora</span> = <span class="tok-string2">/\d\d-\d\d-\d\d\d\d \d\d:\d\d/</span>; +console.log(fechaYHora.test(<span class="tok-string">"01-30-2003 15:20"</span>)); <span class="tok-comment">// → true</span> -console.log(dateTime.test(<span class="tok-string">"30-ene-2003 15:20"</span>)); +console.log(fechaYHora.test(<span class="tok-string">"30-ene-2003 15:20"</span>)); <span class="tok-comment">// → false</span></pre> -<p><a class="p_ident" id="p-1mBi7vFTKu" href="#p-1mBi7vFTKu" tabindex="-1" role="presentation"></a>¡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 <a href="09_regexp.html#date_regexp_counted">más adelante</a>.</p> +<p><a class="p_ident" id="p-aeeh2JGkXV" href="#p-aeeh2JGkXV" tabindex="-1" role="presentation"></a>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 <a href="09_regexp.html#date_regexp_counted">más adelante</a>.</p> <p><a class="p_ident" id="p-qWhaV0LfTF" href="#p-qWhaV0LfTF" tabindex="-1" role="presentation"></a>Estos códigos de barra invertida también se pueden usar dentro de corchetes. Por ejemplo, <code>[\d.]</code> 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 <code>+</code>.</p> <p><a class="p_ident" id="p-UfEuOyZI/2" href="#p-UfEuOyZI/2" tabindex="-1" role="presentation"></a>Para <em>invertir</em> un conjunto de caracteres, es decir, expresar que deseas hacer coincidir cualquier carácter <em>excepto</em> los que están en el conjunto, puedes escribir un carácter circunflejo (<code>^</code>) después del corchete de apertura.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-jj4NG87MXv" href="#c-jj4NG87MXv" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">nonBinary</span> = <span class="tok-string2">/[^01]/</span>; -console.log(nonBinary.test(<span class="tok-string">"1100100010100110"</span>)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-wTTx5O9hDE" href="#c-wTTx5O9hDE" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">noBinario</span> = <span class="tok-string2">/[^01]/</span>; +console.log(noBinario.test(<span class="tok-string">"1100100010100110"</span>)); <span class="tok-comment">// → false</span> -console.log(nonBinary.test(<span class="tok-string">"0111010112101001"</span>)); +console.log(noBinario.test(<span class="tok-string">"0111010112101001"</span>)); <span class="tok-comment">// → true</span></pre> <h2><a class="h_ident" id="h-z2+P4EyKss" href="#h-z2+P4EyKss" tabindex="-1" role="presentation"></a>Caracteres internacionales</h2> -<p><a class="p_ident" id="p-vKYjSGxrpB" href="#p-vKYjSGxrpB" tabindex="-1" role="presentation"></a>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 <em>é</em> o <em>β</em>, que definitivamente son caracteres de palabra, no coincidirán con <code>\w</code> (y <em>sí</em> coincidirán con <code>\W</code> en mayúsculas, la categoría de no palabras).</p> +<p><a class="p_ident" id="p-vKYjSGxrpB" href="#p-vKYjSGxrpB" tabindex="-1" role="presentation"></a>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 <em>é</em> o <em>β</em>, que claramente son caracteres de palabra, no coincidirán con <code>\w</code> (y <em>sí</em> coincidirán con <code>\W</code> en mayúsculas, la categoría de no palabras).</p> <p><a class="p_ident" id="p-1RUMuhSGvB" href="#p-1RUMuhSGvB" tabindex="-1" role="presentation"></a>Por un extraño accidente histórico, <code>\s</code> (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.</p> @@ -155,13 +155,13 @@ <h2><a class="h_ident" id="h-z2+P4EyKss" href="#h-z2+P4EyKss" tabindex="-1" role </tr> -<tr><td><code>\p{Script=Hangul}</code></td><td>Cualquier carácter del guion dado (ver <a href="05_higher_order.html#scripts">Capítulo 5</a>)</td> +<tr><td><code>\p{Script=Hangul}</code></td><td>Cualquier carácter del sistema de escritura dado (ver <a href="05_higher_order.html#scripts">Capítulo 5</a>)</td> </tr> </table> -<p><a class="p_ident" id="p-ArOE+RMcya" href="#p-ArOE+RMcya" tabindex="-1" role="presentation"></a>Usar <code>\w</code> 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 <code>\p</code> son más robustos.</p> +<p><a class="p_ident" id="p-ArOE+RMcya" href="#p-ArOE+RMcya" tabindex="-1" role="presentation"></a>Usar <code>\w</code> 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 <code>\p</code> son más robustos.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-s9MXvH0LEh" href="#c-s9MXvH0LEh" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string2">/\p{L}/u</span>.test(<span class="tok-string">"α"</span>)); <span class="tok-comment">// → true</span> @@ -203,8 +203,8 @@ <h2><a class="h_ident" id="h-jw7Q3yVJ5U" href="#h-jw7Q3yVJ5U" tabindex="-1" role <p id="date_regexp_counted"><a class="p_ident" id="p-lup45v+KG+" href="#p-lup45v+KG+" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Tw+K6Mxe45" href="#c-Tw+K6Mxe45" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">dateTime</span> = <span class="tok-string2">/\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/</span>; -console.log(dateTime.test(<span class="tok-string">"1-30-2003 8:45"</span>)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-pYLO5UPtoc" href="#c-pYLO5UPtoc" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">fechaYHora</span> = <span class="tok-string2">/\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{2}/</span>; +console.log(fechaYHora.test(<span class="tok-string">"1-30-2003 8:45"</span>)); <span class="tok-comment">// → true</span></pre> <p><a class="p_ident" id="p-PBrmor3xzD" href="#p-PBrmor3xzD" tabindex="-1" role="presentation"></a>También puedes especificar rangos abiertos al utilizar llaves omitiendo el número después de la coma. Así, <code>{5,}</code> significa cinco o más veces.</p> @@ -213,11 +213,11 @@ <h2><a class="h_ident" id="h-ajNAW/IU6r" href="#h-ajNAW/IU6r" tabindex="-1" role <p><a class="p_ident" id="p-v3uf4iDeF4" href="#p-v3uf4iDeF4" tabindex="-1" role="presentation"></a>Para usar un operador como <code>*</code> o <code>+</code> 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-P/f6a65XwI" href="#c-P/f6a65XwI" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">cartoonCrying</span> = <span class="tok-string2">/boo+(hoo+)+/i</span>; -console.log(cartoonCrying.test(<span class="tok-string">"Boohoooohoohooo"</span>)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-bxpEI3H8W7" href="#c-bxpEI3H8W7" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">dibujitoLlorando</span> = <span class="tok-string2">/boo+(hoo+)+/i</span>; +console.log(dibujitoLlorando.test(<span class="tok-string">"Boohoooohoohooo"</span>)); <span class="tok-comment">// → true</span></pre> -<p><a class="p_ident" id="p-1LZtgULsIM" href="#p-1LZtgULsIM" tabindex="-1" role="presentation"></a>Los primeros y segundos caracteres <code>+</code> aplican solo al segundo <em>o</em> en <em>boo</em> y <em>hoo</em>, respectivamente. El tercer <code>+</code> se aplica a todo el grupo <code>(hoo+)</code>, haciendo coincidir una o más secuencias como esa.</p> +<p><a class="p_ident" id="p-1LZtgULsIM" href="#p-1LZtgULsIM" tabindex="-1" role="presentation"></a>Los primeros y segundos caracteres <code>+</code> aplican solo a la segunda <em>o</em> en <em>boo</em> y <em>hoo</em>, respectivamente. El tercer <code>+</code> se aplica a todo el grupo <code>(hoo+)</code>, haciendo coincidir una o más secuencias como esa.</p> <p><a class="p_ident" id="p-uBIcF0MS0l" href="#p-uBIcF0MS0l" tabindex="-1" role="presentation"></a>La <code>i</code> 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 <em>B</em> mayúscula en la cadena de entrada, aunque el patrón en sí está completamente en minúsculas.</p> @@ -231,38 +231,38 @@ <h2><a class="h_ident" id="h-G8LUB/KGbs" href="#h-G8LUB/KGbs" tabindex="-1" role console.log(coincidencia.index); <span class="tok-comment">// → 8</span></pre> -<p><a class="p_ident" id="p-MEyBiymd+J" href="#p-MEyBiymd+J" tabindex="-1" role="presentation"></a>Un objeto devuelto por <code>exec</code> tiene una propiedad de <code>index</code> que nos dice <em>dónde</em> 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.</p> +<p><a class="p_ident" id="p-MEyBiymd+J" href="#p-MEyBiymd+J" tabindex="-1" role="presentation"></a>Un objeto devuelto por <code>exec</code> tiene una propiedad de <code>index</code> que nos dice <em>dónde</em> 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.</p> <p><a class="p_ident" id="p-e2o3R6JZyN" href="#p-e2o3R6JZyN" tabindex="-1" role="presentation"></a>Los valores de tipo string tienen un método <code>match</code> que se comporta de manera similar.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-KCEzXI85eI" href="#c-KCEzXI85eI" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"uno dos 100"</span>.match(<span class="tok-string2">/\d+/</span>)); <span class="tok-comment">// → ["100"]</span></pre> -<p><a class="p_ident" id="p-mvGBAzey4W" href="#p-mvGBAzey4W" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-mvGBAzey4W" href="#p-mvGBAzey4W" tabindex="-1" role="presentation"></a>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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-DhEu3yj+hp" href="#c-DhEu3yj+hp" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">textoEntreComillas</span> = <span class="tok-string2">/'([^']*)'/</span>; console.log(textoEntreComillas.exec(<span class="tok-string">"ella dijo 'hola'"</span>)); <span class="tok-comment">// → ["'hola'", "hola"]</span></pre> -<p><a class="p_ident" id="p-mesWUfRxLZ" href="#p-mesWUfRxLZ" tabindex="-1" role="presentation"></a>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á <code>undefined</code>. Y cuando un grupo coincide múltiples veces (por ejemplo, cuando está seguido por un <code>+</code>), solo la última coincidencia termina en el array.</p> +<p><a class="p_ident" id="p-mesWUfRxLZ" href="#p-mesWUfRxLZ" tabindex="-1" role="presentation"></a>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á <code>undefined</code>. Y cuando un grupo coincide múltiples veces (por ejemplo, cuando está seguido por un <code>+</code>), solo la última coincidencia termina estando en el array.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-SEbCBydGa3" href="#c-SEbCBydGa3" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string2">/mal(mente)?/</span>.exec(<span class="tok-string">"mal"</span>)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-5F9Wwcq350" href="#c-5F9Wwcq350" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string2">/mal(amente)?/</span>.exec(<span class="tok-string">"mal"</span>)); <span class="tok-comment">// → ["mal", undefined]</span> console.log(<span class="tok-string2">/(\d)+/</span>.exec(<span class="tok-string">"123"</span>)); <span class="tok-comment">// → ["123", "3"]</span></pre> -<p><a class="p_ident" id="p-SAuB3/3lpK" href="#p-SAuB3/3lpK" tabindex="-1" role="presentation"></a>Si quieres utilizar paréntesis puramente para agrupar, sin que aparezcan en el array de coincidencias, puedes colocar <code>?:</code> después del paréntesis de apertura.</p> +<p><a class="p_ident" id="p-SAuB3/3lpK" href="#p-SAuB3/3lpK" tabindex="-1" role="presentation"></a>Si quieres utilizar paréntesis solamente para agrupar, sin que aparezcan en el array de coincidencias, puedes colocar <code>?:</code> después del paréntesis de apertura.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-JYRtOm6gNP" href="#c-JYRtOm6gNP" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string2">/(?:na)+/</span>.exec(<span class="tok-string">"banana"</span>)); <span class="tok-comment">// → ["nana"]</span></pre> <p><a class="p_ident" id="p-kh0zXCIbfV" href="#p-kh0zXCIbfV" tabindex="-1" role="presentation"></a>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 <code>exec</code>.</p> -<p><a class="p_ident" id="p-YRmJQbp4Jt" href="#p-YRmJQbp4Jt" tabindex="-1" role="presentation"></a>Pero primero haremos un breve desvío, en el que discutiremos la forma incorporada de representar fechas y horas en JavaScript.</p> +<p><a class="p_ident" id="p-YRmJQbp4Jt" href="#p-YRmJQbp4Jt" tabindex="-1" role="presentation"></a>Pero primero haremos un breve paréntesis, en el que discutiremos la forma de representar fechas y horas en JavaScript.</p> <h2><a class="h_ident" id="h-Mi5NWDyYJW" href="#h-Mi5NWDyYJW" tabindex="-1" role="presentation"></a>La clase Date</h2> -<p><a class="p_ident" id="p-iS/kDBNVYY" href="#p-iS/kDBNVYY" tabindex="-1" role="presentation"></a>JavaScript tiene una clase estándar para representar fechas—o, más bien, puntos en tiempo. Se llama <code>Date</code>. Si simplemente creas un objeto de fecha usando <code>new</code>, obtendrás la fecha y hora actuales.</p> +<p><a class="p_ident" id="p-iS/kDBNVYY" href="#p-iS/kDBNVYY" tabindex="-1" role="presentation"></a>JavaScript tiene una clase estándar para representar fechas —o, más bien, puntos en tiempo. Se llama <code>Date</code>. Si simplemente creas un objeto de fecha usando <code>new</code>, obtendrás la fecha y hora actuales.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-pkHhVmn87o" href="#c-pkHhVmn87o" tabindex="-1" role="presentation"></a>console.log(<span class="tok-keyword">new</span> Date()); <span class="tok-comment">// → Fri Feb 02 2024 18:03:06 GMT+0100 (CET)</span></pre> @@ -274,11 +274,11 @@ <h2><a class="h_ident" id="h-Mi5NWDyYJW" href="#h-Mi5NWDyYJW" tabindex="-1" role console.log(<span class="tok-keyword">new</span> Date(<span class="tok-number">2009</span>, <span class="tok-number">11</span>, <span class="tok-number">9</span>, <span class="tok-number">12</span>, <span class="tok-number">59</span>, <span class="tok-number">59</span>, <span class="tok-number">999</span>)); <span class="tok-comment">// → Mié Dec 09 2009 12:59:59 GMT+0100 (CET)</span></pre> -<p><a class="p_ident" id="p-Bw0MeCio5N" href="#p-Bw0MeCio5N" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-7Iu+6mUVDN" href="#p-7Iu+6mUVDN" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-HgiufDrqlL" href="#p-HgiufDrqlL" tabindex="-1" role="presentation"></a>Los últimos cuatro argumentos (horas, minutos, segundos y milisegundos) son opcionales y se consideran cero cuando no se proporcionan.</p> -<p><a class="p_ident" id="p-gxwZAuY3o1" href="#p-gxwZAuY3o1" tabindex="-1" role="presentation"></a>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 <code>getTime</code> en un objeto de fecha retorna este número. Es grande, como te puedes imaginar.</p> +<p><a class="p_ident" id="p-gxwZAuY3o1" href="#p-gxwZAuY3o1" tabindex="-1" role="presentation"></a>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 <code>getTime</code> en un objeto de fecha retorna este número. Es grande, como te puedes imaginar.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Lj+Qss3ZWD" href="#c-Lj+Qss3ZWD" tabindex="-1" role="presentation"></a>console.log(<span class="tok-keyword">new</span> Date(<span class="tok-number">2013</span>, <span class="tok-number">11</span>, <span class="tok-number">19</span>).getTime()); <span class="tok-comment">// → 1387407600000</span> @@ -287,49 +287,49 @@ <h2><a class="h_ident" id="h-Mi5NWDyYJW" href="#h-Mi5NWDyYJW" tabindex="-1" role <p><a class="p_ident" id="p-nCAtjqvFCh" href="#p-nCAtjqvFCh" tabindex="-1" role="presentation"></a>Si le proporcionas un único argumento al constructor <code>Date</code>, ese argumento se tratará como un recuento de milisegundos. Puedes obtener el recuento actual de milisegundos creando un nuevo objeto <code>Date</code> y llamando a <code>getTime</code> en él o llamando a la función <code>Date.now</code>.</p> -<p><a class="p_ident" id="p-VscGabP21e" href="#p-VscGabP21e" tabindex="-1" role="presentation"></a>Los objetos de fecha proporcionan métodos como <code>getFullYear</code>, <code>getMonth</code>, <code>getDate</code>, <code>getHours</code>, <code>getMinutes</code> y <code>getSeconds</code> para extraer sus componentes. Además de <code>getFullYear</code>, también existe <code>getYear</code>, que te da el año menos 1900 (<code>98</code> o <code>119</code>) y es en su mayoría inútil.</p> +<p><a class="p_ident" id="p-GjntjYD2TL" href="#p-GjntjYD2TL" tabindex="-1" role="presentation"></a>Los objetos de fecha proporcionan métodos como <code>getFullYear</code>, <code>getMonth</code>, <code>getDate</code>, <code>getHours</code>, <code>getMinutes</code> y <code>getSeconds</code> para extraer sus componentes. Además de <code>getFullYear</code>, también existe <code>getYear</code>, que te da el año menos 1900 (<code>98</code> o <code>119</code>) y es en esencialmente inútil.</p> <p><a class="p_ident" id="p-eA5U6lb8Dx" href="#p-eA5U6lb8Dx" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-SU0rTpHzGn" href="#c-SU0rTpHzGn" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">getDate</span>(<span class="tok-definition">string</span>) { - <span class="tok-keyword">let</span> [<span class="tok-definition">_</span>, <span class="tok-definition">month</span>, <span class="tok-definition">day</span>, <span class="tok-definition">year</span>] = - <span class="tok-string2">/(\d{1,2})-(\d{1,2})-(\d{4})/</span>.exec(string); - <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Date(year, month - <span class="tok-number">1</span>, day); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Gllvqd0G+w" href="#c-Gllvqd0G+w" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">obtenerFecha</span>(<span class="tok-definition">cadena</span>) { + <span class="tok-keyword">let</span> [<span class="tok-definition">_</span>, <span class="tok-definition">mes</span>, <span class="tok-definition">día</span>, <span class="tok-definition">año</span>] = + <span class="tok-string2">/(\d{1,2})-(\d{1,2})-(\d{4})/</span>.exec(cadena); + <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Date(año, mes - <span class="tok-number">1</span>, díaday); } -console.log(getDate(<span class="tok-string">"1-30-2003"</span>)); +console.log(obtenerFecha(<span class="tok-string">"1-30-2003"</span>)); <span class="tok-comment">// → Jue Ene 30 2003 00:00:00 GMT+0100 (CET)</span></pre> -<p><a class="p_ident" id="p-rkfkh1qvgb" href="#p-rkfkh1qvgb" tabindex="-1" role="presentation"></a>La vinculación <code>_</code> (guion bajo) se ignora y se utiliza solo para omitir el elemento de coincidencia completa en el array devuelto por <code>exec</code>.</p> +<p><a class="p_ident" id="p-A1nbZm1IsX" href="#p-A1nbZm1IsX" tabindex="-1" role="presentation"></a>La asociación <code>_</code> (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 <code>exec</code>.</p> <h2><a class="h_ident" id="h-tNtw4L7Y+r" href="#h-tNtw4L7Y+r" tabindex="-1" role="presentation"></a>Límites y anticipación</h2> -<p><a class="p_ident" id="p-qbV1DRyGh9" href="#p-qbV1DRyGh9" tabindex="-1" role="presentation"></a>Desafortunadamente, <code>getDate</code> también extraerá felizmente una fecha de la cadena <code>"100-1-30000"</code>. 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.</p> +<p><a class="p_ident" id="p-qbV1DRyGh9" href="#p-qbV1DRyGh9" tabindex="-1" role="presentation"></a>Desafortunadamente, <code>obtenerFecha</code> también extraerá felizmente una fecha de la cadena <code>"100-1-30000"</code>. 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.</p> -<p><a class="p_ident" id="p-Mynhxs9BJv" href="#p-Mynhxs9BJv" tabindex="-1" role="presentation"></a>Si queremos asegurar que la coincidencia abarque toda la cadena, podemos agregar los marcadores <code>^</code> y <code>$</code>. 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, <code>/^\d+$/</code> coincide con una cadena que consiste completamente de uno o más dígitos, <code>/^!/</code> coincide con cualquier cadena que comience con un signo de exclamación y <code>/x^/</code> no coincide con ninguna cadena (no puede haber una <em>x</em> antes del inicio de la cadena).</p> +<p><a class="p_ident" id="p-Mynhxs9BJv" href="#p-Mynhxs9BJv" tabindex="-1" role="presentation"></a>Si queremos asegurar que la coincidencia abarque toda la cadena, podemos agregar los marcadores <code>^</code> y <code>$</code>. 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, <code>/^\d+$/</code> coincide con una cadena que consiste completamente de uno o más dígitos, <code>/^!/</code> coincide con cualquier cadena que comience con un signo de exclamación y <code>/x^/</code> no coincide con ninguna cadena (es imposible que haya una <em>x</em> antes del inicio de la cadena).</p> -<p><a class="p_ident" id="p-LrT5oRdCQy" href="#p-LrT5oRdCQy" tabindex="-1" role="presentation"></a>También existe un marcador <code>\b</code>, 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 <code>\w</code>, por lo que no son muy confiables.</p> +<p><a class="p_ident" id="p-hXXcKN/Evy" href="#p-hXXcKN/Evy" tabindex="-1" role="presentation"></a>También existe un marcador <code>\b</code>, 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 <code>\w</code>, por lo que no son muy fiables.</p> <p><a class="p_ident" id="p-RfgTmK5+t/" href="#p-RfgTmK5+t/" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-HmSpm3FjH9" href="#p-HmSpm3FjH9" tabindex="-1" role="presentation"></a>Las pruebas de <em>mirar adelante</em> 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 <code>(?=</code> y <code>)</code>.</p> +<p><a class="p_ident" id="p-HmSpm3FjH9" href="#p-HmSpm3FjH9" tabindex="-1" role="presentation"></a>Las pruebas de <em>anticipación</em> 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 <code>(?=</code> y <code>)</code>.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ryfOqCgM06" href="#c-ryfOqCgM06" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string2">/a(?=e)/</span>.exec(<span class="tok-string">"braeburn"</span>)); <span class="tok-comment">// → ["a"]</span> console.log(<span class="tok-string2">/a(?! )/</span>.exec(<span class="tok-string">"a b"</span>)); <span class="tok-comment">// → null</span></pre> -<p><a class="p_ident" id="p-RuQb3z5Mwx" href="#p-RuQb3z5Mwx" tabindex="-1" role="presentation"></a>Observa cómo la <code>e</code> en el primer ejemplo es necesaria para coincidir, pero no forma parte de la cadena coincidente. La notación <code>(?! )</code> expresa un mirar adelante <em>negativo</em>. Esto solo coincide si el patrón entre paréntesis <em>no</em> coincide, lo que hace que el segundo ejemplo solo coincida con caracteres “a” que no tienen un espacio después de ellos.</p> +<p><a class="p_ident" id="p-RuQb3z5Mwx" href="#p-RuQb3z5Mwx" tabindex="-1" role="presentation"></a>Observa cómo la <code>e</code> en el primer ejemplo es necesaria para coincidir, pero no forma parte de la cadena coincidente. La notación <code>(?! )</code> expresa una anticipación <em>negativa</em>. Esto solo coincide si el patrón entre paréntesis <em>no</em> coincide, lo que hace que el segundo ejemplo solo coincida con caracteres “a” que no tienen un espacio después de ellos.</p> <h2><a class="h_ident" id="h-TUSc554JnM" href="#h-TUSc554JnM" tabindex="-1" role="presentation"></a>Patrones de elección</h2> <p><a class="p_ident" id="p-g+0lsX1IVo" href="#p-g+0lsX1IVo" tabindex="-1" role="presentation"></a>Digamos que queremos saber si un texto contiene no solo un número, sino un número seguido de una de las palabras <em>pig</em>, <em>cow</em> o <em>chicken</em>, o cualquiera de sus formas en plural.</p> -<p><a class="p_ident" id="p-faCL6B9cwG" href="#p-faCL6B9cwG" tabindex="-1" role="presentation"></a>Podríamos escribir tres expresiones regulares y probarlas sucesivamente, pero hay una forma más sencilla. El carácter de barra vertical (<code>|</code>) denota una elección entre el patrón a su izquierda y el patrón a su derecha. Así que puedo decir esto:</p> +<p><a class="p_ident" id="p-qMM4tqEiiV" href="#p-qMM4tqEiiV" tabindex="-1" role="presentation"></a>Podríamos escribir tres expresiones regulares y probarlas sucesivamente, pero hay una forma más sencilla. El carácter de barra vertical (<code>|</code>) denota una elección entre el patrón a su izquierda y el patrón a su derecha. Podemos usarlo en expresiones como est:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-tEyQxhonoR" href="#c-tEyQxhonoR" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">animalCount</span> = <span class="tok-string2">/\d+ (pig|cow|chicken)s?/</span>; -console.log(animalCount.test(<span class="tok-string">"15 pigs"</span>)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-8nZmqefy0e" href="#c-8nZmqefy0e" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">recuentoAnimal</span> = <span class="tok-string2">/\d+ (pig|cow|chicken)s?/</span>; +console.log(recuentoAnimal.test(<span class="tok-string">"15 pigs"</span>)); <span class="tok-comment">// → true</span> -console.log(animalCount.test(<span class="tok-string">"15 pugs"</span>)); +console.log(recuentoAnimal.test(<span class="tok-string">"15 pugs"</span>)); <span class="tok-comment">// → false</span></pre> <p><a class="p_ident" id="p-o/w2hW+vwa" href="#p-o/w2hW+vwa" tabindex="-1" role="presentation"></a>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.</p> @@ -338,17 +338,17 @@ <h2><a class="h_ident" id="h-LDlMA55Hgg" href="#h-LDlMA55Hgg" tabindex="-1" role <p><a class="p_ident" id="p-yKsAs46XQh" href="#p-yKsAs46XQh" tabindex="-1" role="presentation"></a>Conceptualmente, cuando utilizas <code>exec</code> o <code>test</code>, 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.</p> -<p><a class="p_ident" id="p-F6uLAaHlXA" href="#p-F6uLAaHlXA" tabindex="-1" role="presentation"></a>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:</p><figure><img src="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> +<p><a class="p_ident" id="p-J3P4jYGb9O" href="#p-J3P4jYGb9O" tabindex="-1" role="presentation"></a>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:</p><figure><img src="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."></figure> -<p><a class="p_ident" id="p-6KuJyUkARn" href="#p-6KuJyUkARn" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-6KuJyUkARn" href="#p-6KuJyUkARn" tabindex="-1" role="presentation"></a>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.</p> <h2 id="retroceso"><a class="h_ident" id="h-nmeiqrCjN8" href="#h-nmeiqrCjN8" tabindex="-1" role="presentation"></a>Retroceso</h2> -<p><a class="p_ident" id="p-QOY2sD8j/J" href="#p-QOY2sD8j/J" tabindex="-1" role="presentation"></a>La expresión regular <code>/<wbr>^([01]+b|[\da-f]+h|\d+)$/<wbr></code> coincide ya sea con un número binario seguido de una <em>b</em>, un número hexadecimal (es decir, base 16, con las letras <em>a</em> a <em>f</em> representando los dígitos del 10 al 15) seguido de un <em>h</em>, o un número decimal regular sin un carácter de sufijo. Este es el diagrama correspondiente:</p><figure><img src="img/re_number.svg" alt="Diagrama de ferrocarril para la expresión regular '^([01]+b|\d+|[\da-f]+h)$'"></figure> +<p><a class="p_ident" id="p-QOY2sD8j/J" href="#p-QOY2sD8j/J" tabindex="-1" role="presentation"></a>La expresión regular <code>/<wbr>^([01]+b|[\da-f]+h|\d+)$/<wbr></code> coincide ya sea con un número binario seguido de una <em>b</em>, un número hexadecimal (es decir, base 16, con las letras <em>a</em> a <em>f</em> representando los dígitos del 10 al 15) seguido de un <em>h</em>, o un número decimal normal sin un carácter de sufijo. Este es el diagrama correspondiente:</p><figure><img src="img/re_number.svg" alt="Diagrama de ferrocarril para la expresión regular '^([01]+b|\d+|[\da-f]+h)$'"></figure> -<p><a class="p_ident" id="p-F8PggnyHoN" href="#p-F8PggnyHoN" tabindex="-1" role="presentation"></a>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 <code>"103"</code>, por ejemplo, solo se aclara en el 3 que estamos en la rama incorrecta. La cadena <em>coincide</em> con la expresión, simplemente no con la rama en la que nos encontramos actualmente.</p> +<p><a class="p_ident" id="p-F8PggnyHoN" href="#p-F8PggnyHoN" tabindex="-1" role="presentation"></a>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 <code>"103"</code>, por ejemplo, solo se aclara en el 3 que estamos en la rama incorrecta. La cadena <em>coincide</em> con la expresión, solo que no con la rama en la que nos encontramos actualmente.</p> -<p><a class="p_ident" id="p-7qbjzwRdSE" href="#p-7qbjzwRdSE" tabindex="-1" role="presentation"></a>Entonces, el coincidente <em>retrocede</em>. 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 <code>"103"</code>, 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 <em>h</em> 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.</p> +<p><a class="p_ident" id="p-7qbjzwRdSE" href="#p-7qbjzwRdSE" tabindex="-1" role="presentation"></a>Entonces, el coincidente <em>retrocede</em> (o hace <em>backtracking</em>). 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 <code>"103"</code>, 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 <em>h</em> 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.</p> <p><a class="p_ident" id="p-0RSYS7890V" href="#p-0RSYS7890V" tabindex="-1" role="presentation"></a>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).</p> @@ -356,7 +356,7 @@ <h2 id="retroceso"><a class="h_ident" id="h-nmeiqrCjN8" href="#h-nmeiqrCjN8" tab <p><a class="p_ident" id="p-PFas9MdMqI" href="#p-PFas9MdMqI" tabindex="-1" role="presentation"></a>Es posible escribir expresiones regulares que realizarán <em>mucho</em> 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 <code>/([01]+)+b/</code>.</p><figure><img src="img/re_slow.svg" alt="Diagrama de ferrocarril para la expresión regular '([01]+)+b'"></figure> -<p><a class="p_ident" id="p-jqlLoLqI3m" href="#p-jqlLoLqI3m" tabindex="-1" role="presentation"></a>Si intenta hacer coincidir una serie larga de ceros y unos sin un caracter <em>b</em> 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 <em>b</em>, 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 <em>duplica</em> con cada carácter adicional. Incluso con apenas unas pocas docenas de caracteres, la coincidencia resultante tomará prácticamente para siempre.</p> +<p><a class="p_ident" id="p-fHu3xKM2OO" href="#p-fHu3xKM2OO" tabindex="-1" role="presentation"></a>Si intenta hacer coincidir una serie larga de ceros y unos sin un carácter <em>b</em> 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 <em>b</em>, 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 <em>duplica</em> con cada carácter adicional. Incluso con apenas unas pocas docenas de caracteres, la coincidencia resultante llevará prácticamente una eternidad.</p> <h2><a class="h_ident" id="h-f49QceigRC" href="#h-f49QceigRC" tabindex="-1" role="presentation"></a>El método replace</h2> @@ -387,8 +387,8 @@ <h2><a class="h_ident" id="h-f49QceigRC" href="#h-f49QceigRC" tabindex="-1" role <p><a class="p_ident" id="p-Od/5LHikVf" href="#p-Od/5LHikVf" tabindex="-1" role="presentation"></a>Aquí tienes un ejemplo:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-uAXkxyRZne" href="#c-uAXkxyRZne" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">stock</span> = <span class="tok-string">"1 limón, 2 repollos y 101 huevos"</span>; -<span class="tok-keyword">function</span> <span class="tok-definition">menosUno</span>(<span class="tok-definition">match</span>, <span class="tok-definition">cantidad</span>, <span class="tok-definition">unidad</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-JFejLMsRpV" href="#c-JFejLMsRpV" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">stock</span> = <span class="tok-string">"1 limón, 2 repollos y 101 huevos"</span>; +<span class="tok-keyword">function</span> <span class="tok-definition">menosUno</span>(<span class="tok-definition">coincidencia</span>, <span class="tok-definition">cantidad</span>, <span class="tok-definition">unidad</span>) { cantidad = Number(cantidad) - <span class="tok-number">1</span>; <span class="tok-keyword">if</span> (cantidad == <span class="tok-number">1</span>) { <span class="tok-comment">// solo queda uno, se elimina la 's'</span> unidad = unidad.slice(<span class="tok-number">0</span>, unidad.length - <span class="tok-number">1</span>); @@ -402,20 +402,20 @@ <h2><a class="h_ident" id="h-f49QceigRC" href="#h-f49QceigRC" tabindex="-1" role <p><a class="p_ident" id="p-VHv2obV4AF" href="#p-VHv2obV4AF" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-ni452X7vOx" href="#p-ni452X7vOx" tabindex="-1" role="presentation"></a>El grupo <code>(\d+)</code> termina siendo el argumento <code>amount</code> de la función, y el grupo <code>(\p{L}+)</code> se asigna a <code>unit</code>. La función convierte <code>amount</code> a un número, lo cual siempre funciona ya que coincide con <code>\d+</code>, y realiza algunos ajustes en caso de que solo quede uno o ninguno.</p> +<p><a class="p_ident" id="p-ni452X7vOx" href="#p-ni452X7vOx" tabindex="-1" role="presentation"></a>El grupo <code>(\d+)</code> termina siendo el argumento <code>cantidad</code> de la función, y el grupo <code>(\p{L}+)</code> se asigna a <code>unidad</code>. La función convierte <code>cantidad</code> a un número, lo cual siempre funciona ya que coincide con <code>\d+</code>, y realiza algunos ajustes en caso de que solo quede uno o ninguno.</p> <h2><a class="h_ident" id="h-cI0ExIcqOA" href="#h-cI0ExIcqOA" tabindex="-1" role="presentation"></a>Avaricia</h2> -<p><a class="p_ident" id="p-xH5+BJjvA9" href="#p-xH5+BJjvA9" tabindex="-1" role="presentation"></a>Es posible usar <code>replace</code> para escribir una función que elimine todos los comentarios de un fragmento de código JavaScript. Aquí tienes un primer intento:</p> +<p><a class="p_ident" id="p-KWOvNinmL8" href="#p-KWOvNinmL8" tabindex="-1" role="presentation"></a>Podemos usar <code>replace</code> para escribir una función que elimine todos los comentarios de un fragmento de código JavaScript. Aquí tienes un primer intento:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-hqiQQzO7k6" href="#c-hqiQQzO7k6" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">stripComments</span>(<span class="tok-definition">code</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-R/bNJ9+ZEX" href="#c-R/bNJ9+ZEX" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">quitarComentarios</span>(<span class="tok-definition">code</span>) { <span class="tok-keyword">return</span> code.replace(<span class="tok-string2">/\/\/.*|\/\*[^]*\*\//g</span>, <span class="tok-string">""</span>); } -console.log(stripComments(<span class="tok-string">"1 + /* 2 */3"</span>)); +console.log(quitarComentarios(<span class="tok-string">"1 + /* 2 */3"</span>)); <span class="tok-comment">// → 1 + 3</span> -console.log(stripComments(<span class="tok-string">"x = 10;// ¡diez!"</span>)); +console.log(quitarComentarios(<span class="tok-string">"x = 10;// ¡diez!"</span>)); <span class="tok-comment">// → x = 10;</span> -console.log(stripComments(<span class="tok-string">"1 /* a */+/* b */ 1"</span>)); +console.log(quitarComentarios(<span class="tok-string">"1 /* a */+/* b */ 1"</span>)); <span class="tok-comment">// → 1 1</span></pre> <p><a class="p_ident" id="p-lareD54U0I" href="#p-lareD54U0I" tabindex="-1" role="presentation"></a>La parte antes del operador <em>or</em> 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 <code>[^]</code> (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.</p> @@ -424,126 +424,129 @@ <h2><a class="h_ident" id="h-cI0ExIcqOA" href="#h-cI0ExIcqOA" tabindex="-1" role <p><a class="p_ident" id="p-KYpv5oZkiP" href="#p-KYpv5oZkiP" tabindex="-1" role="presentation"></a>La parte <code>[^]*</code> 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 <code>*/</code> 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.</p> -<p><a class="p_ident" id="p-fC+SPfUUr7" href="#p-fC+SPfUUr7" tabindex="-1" role="presentation"></a>Debido a este comportamiento, decimos que los operadores de repetición (<code>+</code>, <code>*</code>, <code>?</code>, y <code>{}</code>) son <em>avariciosos</em>, 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 (<code>+?</code>, <code>*?</code>, <code>??</code>, <code>{}?</code>), 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.</p> +<p><a class="p_ident" id="p-fC+SPfUUr7" href="#p-fC+SPfUUr7" tabindex="-1" role="presentation"></a>Debido a este comportamiento, decimos que los operadores de repetición (<code>+</code>, <code>*</code>, <code>?</code>, y <code>{}</code>) son <em>avariciosos</em>, 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 (<code>+?</code>, <code>*?</code>, <code>??</code>, <code>{}?</code>), 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.</p> <p><a class="p_ident" id="p-MUXpQzArez" href="#p-MUXpQzArez" tabindex="-1" role="presentation"></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 <code>*/</code>, consumimos un comentario de bloque y nada más.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-MCNF7GxfR1" href="#c-MCNF7GxfR1" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">stripComments</span>(<span class="tok-definition">code</span>) { - <span class="tok-keyword">return</span> code.replace(<span class="tok-string2">/\/\/.*|\/\*[^]*?\*\//g</span>, <span class="tok-string">""</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-IniS6cGmKP" href="#c-IniS6cGmKP" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">quitarComentarios</span>(<span class="tok-definition">código</span>) { + <span class="tok-keyword">return</span> código.replace(<span class="tok-string2">/\/\/.*|\/\*[^]*?\*\//g</span>, <span class="tok-string">""</span>); } -console.log(stripComments(<span class="tok-string">"1 /* a */+/* b */ 1"</span>)); +console.log(quitarComentarios(<span class="tok-string">"1 /* a */+/* b */ 1"</span>)); <span class="tok-comment">// → 1 + 1</span></pre> -<p><a class="p_ident" id="p-ALsLlfRqmw" href="#p-ALsLlfRqmw" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-pfShnb95OE" href="#p-pfShnb95OE" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-iuSjEFI6BB" href="#h-iuSjEFI6BB" tabindex="-1" role="presentation"></a>Creación dinámica de objetos RegExp</h2> -<p><a class="p_ident" id="p-VeBLJxBP+2" href="#p-VeBLJxBP+2" tabindex="-1" role="presentation"></a>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 <code>constructor</code> <code>RegExp</code> en ello. Aquí tienes un ejemplo:</p> +<p><a class="p_ident" id="p-VeBLJxBP+2" href="#p-VeBLJxBP+2" tabindex="-1" role="presentation"></a>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 <code>constructor</code> <code>RegExp</code> sobre ella. Aquí tienes un ejemplo:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-WfHBCcn+gy" href="#c-WfHBCcn+gy" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">name</span> = <span class="tok-string">"harry"</span>; -<span class="tok-keyword">let</span> <span class="tok-definition">regexp</span> = <span class="tok-keyword">new</span> RegExp(<span class="tok-string">"(^|</span><span class="tok-string2">\\</span><span class="tok-string">s)"</span> + name + <span class="tok-string">"($|</span><span class="tok-string2">\\</span><span class="tok-string">s)"</span>, <span class="tok-string">"gi"</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-2PZPS+G8K7" href="#c-2PZPS+G8K7" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">nombre</span> = <span class="tok-string">"harry"</span>; +<span class="tok-keyword">let</span> <span class="tok-definition">regexp</span> = <span class="tok-keyword">new</span> RegExp(<span class="tok-string">"(^|</span><span class="tok-string2">\\</span><span class="tok-string">s)"</span> + nombre + <span class="tok-string">"($|</span><span class="tok-string2">\\</span><span class="tok-string">s)"</span>, <span class="tok-string">"gi"</span>); console.log(regexp.test(<span class="tok-string">"Harry es un personaje dudoso."</span>)); <span class="tok-comment">// → true</span></pre> -<p><a class="p_ident" id="p-t/sBXmwE44" href="#p-t/sBXmwE44" tabindex="-1" role="presentation"></a>Al crear la parte <code>\s</code> 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 <code>RegExp</code> contiene las opciones para la expresión regular, en este caso, <code>"gi"</code> para global e insensible a mayúsculas y minúsculas.</p> +<p><a class="p_ident" id="p-9jSavkbqMI" href="#p-9jSavkbqMI" tabindex="-1" role="presentation"></a>Al crear la parte <code>\s</code> 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 <code>RegExp</code> contiene las opciones para la expresión regular, en este caso, <code>"gi"</code> 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.</p> <p><a class="p_ident" id="p-kTObyW6aHr" href="#p-kTObyW6aHr" tabindex="-1" role="presentation"></a>Pero ¿qué pasa si el nombre es <code>"dea+hl[]rd"</code> 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.</p> <p><a class="p_ident" id="p-PLgFCFdANG" href="#p-PLgFCFdANG" tabindex="-1" role="presentation"></a>Para solucionar esto, podemos agregar barras invertidas antes de cualquier carácter que tenga un significado especial.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-E6w+CZN7CT" href="#c-E6w+CZN7CT" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">name</span> = <span class="tok-string">"dea+hl[]rd"</span>; -<span class="tok-keyword">let</span> <span class="tok-definition">escaped</span> = name.replace(<span class="tok-string2">/[\\[.+*?(){|^$]/g</span>, <span class="tok-string">"</span><span class="tok-string2">\\</span><span class="tok-string">$&"</span>); -<span class="tok-keyword">let</span> <span class="tok-definition">regexp</span> = <span class="tok-keyword">new</span> RegExp(<span class="tok-string">"(^|</span><span class="tok-string2">\\</span><span class="tok-string">s)"</span> + escaped + <span class="tok-string">"($|</span><span class="tok-string2">\\</span><span class="tok-string">s)"</span>, +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-etlL/QPFEB" href="#c-etlL/QPFEB" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">nombre</span> = <span class="tok-string">"dea+hl[]rd"</span>; +<span class="tok-keyword">let</span> <span class="tok-definition">escapado</span> = nombre.replace(<span class="tok-string2">/[\\[.+*?(){|^$]/g</span>, <span class="tok-string">"</span><span class="tok-string2">\\</span><span class="tok-string">$&"</span>); +<span class="tok-keyword">let</span> <span class="tok-definition">regexp</span> = <span class="tok-keyword">new</span> RegExp(<span class="tok-string">"(^|</span><span class="tok-string2">\\</span><span class="tok-string">s)"</span> + escapado + <span class="tok-string">"($|</span><span class="tok-string2">\\</span><span class="tok-string">s)"</span>, <span class="tok-string">"gi"</span>); -<span class="tok-keyword">let</span> <span class="tok-definition">text</span> = <span class="tok-string">"Este chico dea+hl[]rd es súper molesto."</span>; -console.log(regexp.test(text)); +<span class="tok-keyword">let</span> <span class="tok-definition">texto</span> = <span class="tok-string">"Este chico dea+hl[]rd es súper pesado."</span>; +console.log(regexp.test(texto)); <span class="tok-comment">// → true</span></pre> +<div class="translator-note"><p><strong>N. del T.:</strong> Recordemos que, dentro de <code>[...]</code>, casi los caracteres especiales pierden su significado, exceptuando en este caso la barra invertida, <code>\</code>, que debe escaparse.</p> +</div> + <h2><a class="h_ident" id="h-uCPhVoLsMf" href="#h-uCPhVoLsMf" tabindex="-1" role="presentation"></a>El método search</h2> -<p><a class="p_ident" id="p-3qxpU8WQEB" href="#p-3qxpU8WQEB" tabindex="-1" role="presentation"></a>El método <code>indexOf</code> en las cadenas no puede ser llamado con una expresión regular. Pero hay otro método, <code>search</code>, que espera una expresión regular. Al igual que <code>indexOf</code>, devuelve el primer índice en el que se encontró la expresión, o -1 cuando no se encontró.</p> +<p><a class="p_ident" id="p-RfRHH6yEq4" href="#p-RfRHH6yEq4" tabindex="-1" role="presentation"></a>Con una expresión regular no podemos usar el método <code>indexOf</code> de las cadenas. Pero hay otro método, <code>search</code>, que espera una expresión regular. Al igual que <code>indexOf</code>, devuelve el primer índice en el que se encuentra la expresión, o -1 cuando no se encuentra.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-hINt/qpuxG" href="#c-hINt/qpuxG" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">" palabra"</span>.search(<span class="tok-string2">/\S/</span>)); <span class="tok-comment">// → 2</span> console.log(<span class="tok-string">" "</span>.search(<span class="tok-string2">/\S/</span>)); <span class="tok-comment">// → -1</span></pre> -<p><a class="p_ident" id="p-bCpmiIwqTW" href="#p-bCpmiIwqTW" tabindex="-1" role="presentation"></a>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 <code>indexOf</code>), lo cual a menudo sería útil.</p> +<p><a class="p_ident" id="p-XJ5hF4U6it" href="#p-XJ5hF4U6it" tabindex="-1" role="presentation"></a>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 <code>indexOf</code>), lo que podría ser bastante útil a veces.</p> <h2><a class="h_ident" id="h-61OHLlWPwV" href="#h-61OHLlWPwV" tabindex="-1" role="presentation"></a>La propiedad lastIndex</h2> -<p><a class="p_ident" id="p-OpmhI72Qfi" href="#p-OpmhI72Qfi" tabindex="-1" role="presentation"></a>El método <code>exec</code> 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 <em>in</em>conveniente.</p> +<p><a class="p_ident" id="p-6LWZvA0Zva" href="#p-6LWZvA0Zva" tabindex="-1" role="presentation"></a>De manera parecida, el método <code>exec</code> no proporciona una forma conveniente de comenzar a buscar desde una posición dada en la cadena. Pero sí proporciona una forma <em>in</em>cómoda de hacerlo.</p> <p><a class="p_ident" id="p-JYAjlYrL9h" href="#p-JYAjlYrL9h" tabindex="-1" role="presentation"></a>Los objetos de expresión regular tienen propiedades. Una de esas propiedades es <code>source</code>, que contiene la cadena de la que se creó la expresión. Otra propiedad es <code>lastIndex</code>, que controla, en algunas circunstancias limitadas, desde dónde comenzará la siguiente coincidencia.</p> -<p><a class="p_ident" id="p-59IpNLm2WF" href="#p-59IpNLm2WF" tabindex="-1" role="presentation"></a>Estas circunstancias implican que la expresión regular debe tener la opción global (<code>g</code>) o pegajosa (<code>y</code>) activada, y la coincidencia debe ocurrir a través del método <code>exec</code>. Nuevamente, una solución menos confusa habría sido simplemente permitir que se pase un argumento adicional a <code>exec</code>, pero la confusión es una característica esencial de la interfaz de expresiones regulares de JavaScript.</p> +<p><a class="p_ident" id="p-kC5kE4+MbC" href="#p-kC5kE4+MbC" tabindex="-1" role="presentation"></a>Estas circunstancias son que la expresión regular debe tener la opción global (<code>g</code>) o pegajosa (<code>y</code>) activadas, y la coincidencia debe ocurrir a través del método <code>exec</code>. De nuevo, una solución menos confusa habría sido simplemente permitir que se pase un argumento adicional a <code>exec</code>, pero la confusión es una característica esencial de la interfaz de expresiones regulares de JavaScript.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-nXsHtqIJdF" href="#c-nXsHtqIJdF" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">pattern</span> = <span class="tok-string2">/y/g</span>; -pattern.lastIndex = <span class="tok-number">3</span>; -<span class="tok-keyword">let</span> <span class="tok-definition">match</span> = pattern.exec(<span class="tok-string">"xyzzy"</span>); -console.log(match.index); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ffTM+fA0EA" href="#c-ffTM+fA0EA" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">patrón</span> = <span class="tok-string2">/y/g</span>; +patrón.lastIndex = <span class="tok-number">3</span>; +<span class="tok-keyword">let</span> <span class="tok-definition">coincidencia</span> = patrón.exec(<span class="tok-string">"xyzzy"</span>); +console.log(coincidencia.index); <span class="tok-comment">// → 4</span> -console.log(pattern.lastIndex); +console.log(patrón.lastIndex); <span class="tok-comment">// → 5</span></pre> <p><a class="p_ident" id="p-0DBtft8C4S" href="#p-0DBtft8C4S" tabindex="-1" role="presentation"></a>Si la coincidencia tuvo éxito, la llamada a <code>exec</code> actualiza automáticamente la propiedad <code>lastIndex</code> para que apunte después de la coincidencia. Si no se encontró ninguna coincidencia, <code>lastIndex</code> se restablece a cero, que es también el valor que tiene en un objeto de expresión regular recién construido.</p> -<p><a class="p_ident" id="p-1u3O8Smqjw" href="#p-1u3O8Smqjw" tabindex="-1" role="presentation"></a>La diferencia entre las opciones global y sticky es que, cuando se habilita sticky, la coincidencia solo se producirá si comienza directamente en <code>lastIndex</code>, mientras que con global se buscará una posición donde pueda comenzar una coincidencia.</p> +<p><a class="p_ident" id="p-1u3O8Smqjw" href="#p-1u3O8Smqjw" tabindex="-1" role="presentation"></a>La diferencia entre las opciones global y pegajosa (<em>sticky</em>) es que, cuando se habilita la opción pegajosa, la coincidencia solo se produce si comienza directamente en <code>lastIndex</code>, mientras que con global se buscará una posición donde pueda comenzar una coincidencia.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-98GwGRIMj8" href="#c-98GwGRIMj8" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">global</span> = <span class="tok-string2">/abc/g</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-iz7/UrlAAg" href="#c-iz7/UrlAAg" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">global</span> = <span class="tok-string2">/abc/g</span>; console.log(global.exec(<span class="tok-string">"xyz abc"</span>)); <span class="tok-comment">// → ["abc"]</span> -<span class="tok-keyword">let</span> <span class="tok-definition">sticky</span> = <span class="tok-string2">/abc/y</span>; -console.log(sticky.exec(<span class="tok-string">"xyz abc"</span>)); +<span class="tok-keyword">let</span> <span class="tok-definition">pegajosa</span> = <span class="tok-string2">/abc/y</span>; +console.log(pegajosa.exec(<span class="tok-string">"xyz abc"</span>)); <span class="tok-comment">// → null</span></pre> <p><a class="p_ident" id="p-SPnGt76q5R" href="#p-SPnGt76q5R" tabindex="-1" role="presentation"></a>Al usar un valor de expresión regular compartido para múltiples llamadas a <code>exec</code>, estas actualizaciones automáticas a la propiedad <code>lastIndex</code> pueden causar problemas. Es posible que tu expresión regular comience accidentalmente en un índice que quedó de una llamada previa.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-kE2WKkyjwG" href="#c-kE2WKkyjwG" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">digit</span> = <span class="tok-string2">/\d/g</span>; -console.log(digit.exec(<span class="tok-string">"aquí está: 1"</span>)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-7PCJAkEw9X" href="#c-7PCJAkEw9X" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">dígito</span> = <span class="tok-string2">/\d/g</span>; +console.log(dígito.exec(<span class="tok-string">"aquí está: 1"</span>)); <span class="tok-comment">// → ["1"]</span> -console.log(digit.exec(<span class="tok-string">"ahora: 1"</span>)); +console.log(dígito.exec(<span class="tok-string">"ahora: 1"</span>)); <span class="tok-comment">// → null</span></pre> -<p><a class="p_ident" id="p-M58YL9ghmR" href="#p-M58YL9ghmR" tabindex="-1" role="presentation"></a>Otro efecto interesante de la opción global es que cambia la forma en que funciona el método <code>match</code> en las cadenas. Cuando se llama con una expresión global, en lugar de devolver una matriz similar a la devuelta por <code>exec</code>, <code>match</code> encontrará <em>todas</em> las coincidencias del patrón en la cadena y devolverá una matriz que contiene las cadenas coincidentes.</p> +<p><a class="p_ident" id="p-M58YL9ghmR" href="#p-M58YL9ghmR" tabindex="-1" role="presentation"></a>Otro efecto interesante de la opción global es que cambia la forma en que funciona el método <code>match</code> en las cadenas. Cuando se llama con una expresión global, en lugar de devolver un array como el que devuelve <code>exec</code>, <code>match</code> encontrará <em>todas</em> las coincidencias del patrón en la cadena y devolverá un array que contiene las cadenas coincidentes.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-weT/d5+8vE" href="#c-weT/d5+8vE" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string">"Banana"</span>.match(<span class="tok-string2">/an/g</span>)); <span class="tok-comment">// → ["an", "an"]</span></pre> -<p><a class="p_ident" id="p-P0n0q5qqiM" href="#p-P0n0q5qqiM" tabindex="-1" role="presentation"></a>Así que ten cuidado con las expresiones regulares globales. Los casos en los que son necesarias, como las llamadas a <code>replace</code> y los lugares donde quieres usar explícitamente <code>lastIndex</code>, son típicamente los únicos lugares donde las deseas utilizar.</p> +<p><a class="p_ident" id="p-mgPnBw25kv" href="#p-mgPnBw25kv" tabindex="-1" role="presentation"></a>Así que ten cuidado con las expresiones regulares globales. Los casos en los que son necesarias, como las llamadas a <code>replace</code> y los lugares donde quieres usar explícitamente <code>lastIndex</code>, son normalmente los únicos lugares donde querrás utilizarlas.</p> <h3><a class="i_ident" id="i-vqvHDgpGfT" href="#i-vqvHDgpGfT" tabindex="-1" role="presentation"></a>Obteniendo todas las coincidencias</h3> -<p><a class="p_ident" id="p-2/FZJ5j0h9" href="#p-2/FZJ5j0h9" tabindex="-1" role="presentation"></a>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 <code>matchAll</code>.</p> +<p><a class="p_ident" id="p-VwuAfQEsx3" href="#p-VwuAfQEsx3" tabindex="-1" role="presentation"></a>Algo se suele hacer es encontrar todas las coincidencias de una expresión regular en una cadena. Podemos hacer esto usando el método <code>matchAll</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-otfl+S6eNg" href="#c-otfl+S6eNg" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">input</span> = <span class="tok-string">"Una cadena con 3 números... 42 y 88."</span>; -<span class="tok-keyword">let</span> <span class="tok-definition">matches</span> = input.matchAll(<span class="tok-string2">/\d+/g</span>); -<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">match</span> <span class="tok-keyword">of</span> matches) { - console.log(<span class="tok-string">"Encontrado"</span>, match[<span class="tok-number">0</span>], <span class="tok-string">"en"</span>, match.index); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-FpEbVBlAoX" href="#c-FpEbVBlAoX" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">input</span> = <span class="tok-string">"Una cadena con 3 números... 42 y 88."</span>; +<span class="tok-keyword">let</span> <span class="tok-definition">coincidencias</span> = input.matchAll(<span class="tok-string2">/\d+/g</span>); +<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">coincidencia</span> <span class="tok-keyword">of</span> coincidencias) { + console.log(<span class="tok-string">"Encontrado"</span>, coincidencia[<span class="tok-number">0</span>], <span class="tok-string">"en"</span>, coincidencia.index); } <span class="tok-comment">// → Encontrado 3 en 14</span> <span class="tok-comment">// Encontrado 42 en 33</span> <span class="tok-comment">// Encontrado 88 en 40</span></pre> -<p><a class="p_ident" id="p-+4Amv+gHld" href="#p-+4Amv+gHld" tabindex="-1" role="presentation"></a>Este método devuelve una matriz de matrices de coincidencias. La expresión regular que se le proporciona <em>debe</em> tener <code>g</code> habilitado.</p> +<p><a class="p_ident" id="p-+4Amv+gHld" href="#p-+4Amv+gHld" tabindex="-1" role="presentation"></a>Este método devuelve un array de arrays de coincidencias. La expresión regular que se le proporciona <em>debe</em> tener <code>g</code> habilitado.</p> <h2 id="ini"><a class="h_ident" id="h-aqUstUx5TS" href="#h-aqUstUx5TS" tabindex="-1" role="presentation"></a>Analizando un archivo INI</h2> -<p><a class="p_ident" id="p-sGp7Jxx2uG" href="#p-sGp7Jxx2uG" tabindex="-1" role="presentation"></a>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í:</p> +<p><a class="p_ident" id="p-L/8aMHKojg" href="#p-L/8aMHKojg" tabindex="-1" role="presentation"></a>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:</p> -<pre class="snippet" data-language="null" ><a class="c_ident" id="c-sNjz/ec/W5" href="#c-sNjz/ec/W5" tabindex="-1" role="presentation"></a>motorbusqueda=https://duckduckgo.com/?q=$1 +<pre class="snippet" data-language="null" ><a class="c_ident" id="c-Ef8DuyCVyH" href="#c-Ef8DuyCVyH" tabindex="-1" role="presentation"></a>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</pre> <p><a class="p_ident" id="p-2XRX7atQld" href="#p-2XRX7atQld" tabindex="-1" role="presentation"></a>Las reglas exactas para este formato (que es un formato ampliamente utilizado, generalmente llamado un archivo <em>INI</em>) son las siguientes:</p> @@ -560,7 +563,7 @@ <h2 id="ini"><a class="h_ident" id="h-aqUstUx5TS" href="#h-aqUstUx5TS" tabindex= <li> -<p><a class="p_ident" id="p-A63YtLp+/Z" href="#p-A63YtLp+/Z" tabindex="-1" role="presentation"></a>Las líneas que contienen un identificador alfanumérico seguido de un caracter <code>=</code> agregan una configuración a la sección actual.</p></li> +<p><a class="p_ident" id="p-A63YtLp+/Z" href="#p-A63YtLp+/Z" tabindex="-1" role="presentation"></a>Las líneas que contienen un identificador alfanumérico seguido de un carácter <code>=</code> agregan una configuración a la sección actual.</p></li> <li> @@ -570,42 +573,42 @@ <h2 id="ini"><a class="h_ident" id="h-aqUstUx5TS" href="#h-aqUstUx5TS" tabindex= <p><a class="p_ident" id="p-7z9fdAqy4R" href="#p-7z9fdAqy4R" tabindex="-1" role="presentation"></a>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 <code>split</code> en el <a href="04_data.html#split">Capítulo 4</a>. 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 (<code>"\r\n"</code>). Dado que el método <code>split</code> también permite una expresión regular como argumento, podemos usar una expresión regular como <code>/\r?\n/</code> para dividir de una manera que permita tanto <code>"\n"</code> como <code>"\r\n"</code> entre líneas.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-WalMuQxa8W" href="#c-WalMuQxa8W" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">parseINI</span>(<span class="tok-definition">string</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ojVLY1ywVD" href="#c-ojVLY1ywVD" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">procesarINI</span>(<span class="tok-definition">cadena</span>) { <span class="tok-comment">// Comenzar con un objeto para contener los campos de nivel superior</span> - <span class="tok-keyword">let</span> <span class="tok-definition">result</span> = {}; - <span class="tok-keyword">let</span> <span class="tok-definition">section</span> = result; - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">line</span> <span class="tok-keyword">of</span> string.split(<span class="tok-string2">/\r?\n/</span>)) { - <span class="tok-keyword">let</span> <span class="tok-definition">match</span>; - <span class="tok-keyword">if</span> (match = line.match(<span class="tok-string2">/^(\w+)=(.*)$/</span>)) { - section[match[<span class="tok-number">1</span>]] = match[<span class="tok-number">2</span>]; - } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (match = line.match(<span class="tok-string2">/^\[(.*)\]$/</span>)) { - section = result[match[<span class="tok-number">1</span>]] = {}; - } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (!<span class="tok-string2">/^\s*(;|$)/</span>.test(line)) { - <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> Error(<span class="tok-string">"La línea '"</span> + line + <span class="tok-string">"' no es válida."</span>); + <span class="tok-keyword">let</span> <span class="tok-definition">resultado</span> = {}; + <span class="tok-keyword">let</span> <span class="tok-definition">sección</span> = resultado; + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">línea</span> <span class="tok-keyword">of</span> cadena.split(<span class="tok-string2">/\r?\n/</span>)) { + <span class="tok-keyword">let</span> <span class="tok-definition">coincidencia</span>; + <span class="tok-keyword">if</span> (coincidencia = línea.match(<span class="tok-string2">/^(\w+)=(.*)$/</span>)) { + sección[coincidencia[<span class="tok-number">1</span>]] = coincidencia[<span class="tok-number">2</span>]; + } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (coincidencia = línea.match(<span class="tok-string2">/^\[(.*)\]$/</span>)) { + sección = resultado[coincidencia[<span class="tok-number">1</span>]] = {}; + } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (!<span class="tok-string2">/^\s*(;|$)/</span>.test(línea)) { + <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> Error(<span class="tok-string">"La línea '"</span> + línea + <span class="tok-string">"' no es válida."</span>); } }; - <span class="tok-keyword">return</span> result; + <span class="tok-keyword">return</span> resultado; } -console.log(parseINI(<span class="tok-string2">`</span> -<span class="tok-string2">name=Vasilis</span> -<span class="tok-string2">[address]</span> -<span class="tok-string2">city=Tessaloniki`</span>)); -<span class="tok-comment">// → {name: "Vasilis", address: {city: "Tessaloniki"}}</span></pre> +console.log(procesarINI(<span class="tok-string2">`</span> +<span class="tok-string2">nombre=Vasilis</span> +<span class="tok-string2">[dirección]</span> +<span class="tok-string2">ciudad=Tessaloniki`</span>)); +<span class="tok-comment">// → {nombre: "Vasilis", dirección: {ciudad: "Tessaloniki"}}</span></pre> <p><a class="p_ident" id="p-LsLS8mCe4B" href="#p-LsLS8mCe4B" tabindex="-1" role="presentation"></a>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 <code>section</code> apunta al objeto para la sección actual.</p> -<p><a class="p_ident" id="p-W38Z15dwza" href="#p-W38Z15dwza" tabindex="-1" role="presentation"></a>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 <code>section</code> se establece para apuntar a él.</p> +<p><a class="p_ident" id="p-ldwFTvhBQO" href="#p-ldwFTvhBQO" tabindex="-1" role="presentation"></a>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 <code>section</code> apunte a él.</p> -<p><a class="p_ident" id="p-WRi9IuRJQq" href="#p-WRi9IuRJQq" tabindex="-1" role="presentation"></a>Observa el uso recurrente de <code>^</code> y <code>$</code> 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.</p> +<p><a class="p_ident" id="p-WRi9IuRJQq" href="#p-WRi9IuRJQq" tabindex="-1" role="presentation"></a>Observa el uso recurrente de <code>^</code> y <code>$</code> 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.</p> -<p><a class="p_ident" id="p-w+p65GaQlw" href="#p-w+p65GaQlw" tabindex="-1" role="presentation"></a>```El patrón <code>if (match = string.<wbr>match(.<wbr>.<wbr>.<wbr>))</code> hace uso del hecho de que el valor de una expresión de asignación (<code>=</code>) es el valor asignado. A menudo no estás seguro de que tu llamada a <code>match</code> tendrá éxito, por lo que solo puedes acceder al objeto resultante dentro de una declaración <code>if</code> que comprueba esto. Para no romper la agradable cadena de formas de <code>else if</code>, asignamos el resultado de la coincidencia a un enlace y usamos inmediatamente esa asignación como la prueba para la declaración <code>if</code>.</p> +<p><a class="p_ident" id="p-w+p65GaQlw" href="#p-w+p65GaQlw" tabindex="-1" role="presentation"></a>El patrón <code>if (coincidencia = string.<wbr>match(.<wbr>.<wbr>.<wbr>))</code> hace uso del hecho de que el valor de una expresión de asignación (<code>=</code>) es el valor asignado. A menudo no estás seguro de que tu llamada a <code>match</code> tendrá éxito, por lo que solo puedes acceder al objeto resultante dentro de una declaración <code>if</code> que comprueba esto. Para no romper la agradable cadena de formularios <code>else if</code>, asignamos el resultado de la coincidencia a una asociación y usamos inmediatamente esa asignación como comprobación para la declaración <code>if</code>.</p> <p><a class="p_ident" id="p-zg+5BDK7MN" href="#p-zg+5BDK7MN" tabindex="-1" role="presentation"></a>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 <code>/^\s*(;|$)/</code> 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.</p> <h2><a class="h_ident" id="h-boAsI3PiWP" href="#h-boAsI3PiWP" tabindex="-1" role="presentation"></a>Unidades de código y caracteres</h2> -<p><a class="p_ident" id="p-CzHGvvAVs9" href="#p-CzHGvvAVs9" tabindex="-1" role="presentation"></a>Otro error de diseño que se ha estandarizado en las expresiones regulares de JavaScript es que, por defecto, operadores como <code>.</code> o <code>?</code> trabajan en unidades de código, como se discute en el <a href="05_higher_order.html#code_units">Capítulo 5</a>, 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.</p> +<p><a class="p_ident" id="p-CzHGvvAVs9" href="#p-CzHGvvAVs9" tabindex="-1" role="presentation"></a>Otro error de diseño que se ha estandarizado en las expresiones regulares de JavaScript es que, por defecto, operadores como <code>.</code> o <code>?</code> trabajan en unidades de código, como se discute en el <a href="05_higher_order.html#code_units">Capítulo 5</a>, 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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-CfMTYxun8D" href="#c-CfMTYxun8D" tabindex="-1" role="presentation"></a>console.log(<span class="tok-string2">/🍎{3}/</span>.test(<span class="tok-string">"🍎🍎🍎"</span>)); <span class="tok-comment">// → false</span> @@ -647,7 +650,7 @@ <h2 id="summary_regexp"><a class="h_ident" id="h-NUFOUyK+lw" href="#h-NUFOUyK+lw </tr> -<tr><td><code>/x+?/</code></td><td>Una o más ocurrencias, perezoso</td> +<tr><td><code>/x+?/</code></td><td>Una o más ocurrencias, no avaricioso</td> </tr> @@ -699,21 +702,21 @@ <h2 id="summary_regexp"><a class="h_ident" id="h-NUFOUyK+lw" href="#h-NUFOUyK+lw </tr> -<tr><td><code>/(?=a)/</code></td><td>Una prueba de vistazo hacia adelante</td> +<tr><td><code>/(?=a)/</code></td><td>Una prueba de anticipación</td> </tr> </table> -<p><a class="p_ident" id="p-CFboyM9HDn" href="#p-CFboyM9HDn" tabindex="-1" role="presentation"></a>Una expresión regular tiene un método <code>test</code> para comprobar si una cadena dada coincide con ella. También tiene un método <code>exec</code> que, cuando se encuentra una coincidencia, devuelve un array que contiene todos los grupos coincidentes. Dicho array tiene una propiedad <code>index</code> que indica dónde empezó la coincidencia.Las cadenas tienen un método <code>match</code> para compararlas con una expresión regular y un método <code>search</code> para buscar una, devolviendo solo la posición de inicio de la coincidencia. Su método <code>replace</code> puede reemplazar coincidencias de un patrón con una cadena o función de reemplazo.</p> +<p><a class="p_ident" id="p-CFboyM9HDn" href="#p-CFboyM9HDn" tabindex="-1" role="presentation"></a>Una expresión regular tiene un método <code>test</code> para comprobar si una cadena dada coincide con ella. También tiene un método <code>exec</code> que, cuando se encuentra una coincidencia, devuelve un array que contiene todos los grupos coincidentes. Dicho array tiene una propiedad <code>index</code> que indica dónde empezó la coincidencia. Las cadenas tienen un método <code>match</code> para compararlas con una expresión regular y un método <code>search</code> para buscar una, devolviendo solo la posición de inicio de la coincidencia. Su método <code>replace</code> puede reemplazar coincidencias de un patrón con una cadena o función de reemplazo.</p> -<p><a class="p_ident" id="p-5tlP70sWEp" href="#p-5tlP70sWEp" tabindex="-1" role="presentation"></a>Las expresiones regulares pueden tener opciones, que se escriben después de la barra de cierre. La opción <code>i</code> hace que la coincidencia no distinga entre mayúsculas y minúsculas. La opción <code>g</code> hace que la expresión sea <em>global</em>, lo que, entre otras cosas, hace que el método <code>replace</code> reemplace todas las instancias en lugar de solo la primera. La opción <code>y</code> la hace persistente, lo que significa que no buscará por delante ni omitirá parte de la cadena al buscar una coincidencia. La opción <code>u</code> activa el modo Unicode, que habilita la sintaxis <code>\p</code> y soluciona varios problemas en torno al manejo de caracteres que ocupan dos unidades de código.</p> +<p><a class="p_ident" id="p-5tlP70sWEp" href="#p-5tlP70sWEp" tabindex="-1" role="presentation"></a>Las expresiones regulares pueden tener opciones, que se escriben después de la barra de cierre. La opción <code>i</code> hace que la coincidencia no distinga entre mayúsculas y minúsculas. La opción <code>g</code> hace que la expresión sea <em>global</em>, lo que, entre otras cosas, hace que el método <code>replace</code> reemplace todas las instancias en lugar de solo la primera. La opción <code>y</code> la hace “pegajosa”, lo que significa que no buscará por delante ni omitirá parte de la cadena al buscar una coincidencia. La opción <code>u</code> activa el modo Unicode, que habilita la sintaxis <code>\p</code> y soluciona varios problemas en torno al manejo de caracteres que ocupan dos unidades de código.</p> <p><a class="p_ident" id="p-Hw5k4JvKlE" href="#p-Hw5k4JvKlE" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2> -<p><a class="p_ident" id="p-7luPllNrX9" href="#p-7luPllNrX9" tabindex="-1" role="presentation"></a>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 <a href="https://www.debuggex.com/"><em>debuggex.com</em></a> 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.</p> +<p><a class="p_ident" id="p-7luPllNrX9" href="#p-7luPllNrX9" tabindex="-1" role="presentation"></a>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 <a href="https://www.debuggex.com/"><em>debuggex.com</em></a> 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.</p> <h3><a class="i_ident" id="i-vDM8PzwQWU" href="#i-vDM8PzwQWU" tabindex="-1" role="presentation"></a>Regexp golf</h3> @@ -818,7 +821,7 @@ <h3><a class="i_ident" id="i-efCNhNV5nJ" href="#i-efCNhNV5nJ" tabindex="-1" role <h3><a class="i_ident" id="i-YSgOufjbNE" href="#i-YSgOufjbNE" tabindex="-1" role="presentation"></a>Números nuevamente</h3> -<p><a class="p_ident" id="p-oeGH9sXraQ" href="#p-oeGH9sXraQ" tabindex="-1" role="presentation"></a>Escribe una expresión que coincida solo con los números al estilo de JavaScript. Debe admitir un signo menos <em>o</em> más opcional delante del número, el punto decimal y la notación de exponente—<code>5e-3</code> o <code>1E10</code>—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, <code>.5</code> y <code>5.</code> son números de JavaScript válidos, pero un punto solitario <em>no</em> lo es.</p> +<p><a class="p_ident" id="p-oeGH9sXraQ" href="#p-oeGH9sXraQ" tabindex="-1" role="presentation"></a>Escribe una expresión que coincida solo con los números al estilo de JavaScript. Debe admitir un signo menos <em>o</em> más opcional delante del número, el punto decimal y la notación de exponente —<code>5e-3</code> o <code>1E10</code>— 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, <code>.5</code> y <code>5.</code> son números de JavaScript válidos, pero un punto solitario <em>no</em> lo es.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-gXUEPT1mXR" href="#c-gXUEPT1mXR" tabindex="-1" role="presentation"></a><span class="tok-comment">// Completa esta expresión regular.</span> <span class="tok-keyword">let</span> <span class="tok-definition">number</span> = <span class="tok-string2">/^...$/</span>; @@ -841,7 +844,7 @@ <h3><a class="i_ident" id="i-YSgOufjbNE" href="#i-YSgOufjbNE" tabindex="-1" role <p><a class="p_ident" id="p-4VDCgxohNU" href="#p-4VDCgxohNU" tabindex="-1" role="presentation"></a>Primero, no olvides la barra invertida delante del punto.</p> -<p><a class="p_ident" id="p-aGz24Obe10" href="#p-aGz24Obe10" tabindex="-1" role="presentation"></a>Para hacer coincidir el signo opcional delante del número, así como delante del exponente, se puede hacer con <code>[+\-]?</code> o <code>(\+|-|)</code> (más, menos, o nada).</p> +<p><a class="p_ident" id="p-DDAkVdKom8" href="#p-DDAkVdKom8" tabindex="-1" role="presentation"></a>Si queremos hacer coincidir el signo opcional delante del número, así como delante del exponente, esto se puede hacer con <code>[+\-]?</code> o <code>(\+|-|)</code> (más, menos, o nada).</p> <p><a class="p_ident" id="p-+Tjzh9qDET" href="#p-+Tjzh9qDET" tabindex="-1" role="presentation"></a>La parte más complicada del ejercicio es el problema de hacer coincidir tanto <code>"5."</code> como <code>".5"</code> sin hacer coincidir también <code>"."</code>. Para esto, una buena solución es usar el operador <code>|</code> para separar los dos casos: uno o más dígitos seguidos opcionalmente por un punto y cero o más dígitos <em>o</em> un punto seguido por uno o más dígitos.</p> 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 @@ <h1>Módulos</h1> <p><a class="p_ident" id="p-WivTB15C7Z" href="#p-WivTB15C7Z" tabindex="-1" role="presentation"></a>Escribe código que sea fácil de borrar, no fácil de extender</p> -<footer>Tef, <cite>La programación es terrible</cite></footer> +<footer>Tef, <cite>programming is terrible</cite></footer> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_10.jpg" alt="Ilustración de un edificio complicado construido a partir de piezas modulares"></figure> -<p><a class="p_ident" id="p-EX80tfve2c" href="#p-EX80tfve2c" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-EX80tfve2c" href="#p-EX80tfve2c" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-yilY+En/5q" href="#p-yilY+En/5q" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-yilY+En/5q" href="#p-yilY+En/5q" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-NIf+VlR8cH" href="#p-NIf+VlR8cH" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-NIf+VlR8cH" href="#p-NIf+VlR8cH" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-dm9xK/uXlF" href="#p-dm9xK/uXlF" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-dm9xK/uXlF" href="#p-dm9xK/uXlF" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-U7RnowObxe" href="#h-U7RnowObxe" tabindex="-1" role="presentation"></a>Programas modulares</h2> -<p><a class="p_ident" id="p-yGNt7m0TYo" href="#p-yGNt7m0TYo" tabindex="-1" role="presentation"></a>Los <em>módulos</em> 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 <em>interfaz</em>).</p> +<p><a class="p_ident" id="p-yGNt7m0TYo" href="#p-yGNt7m0TYo" tabindex="-1" role="presentation"></a>Los <em>módulos</em> 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 <em>interfaz</em>).</p> <p><a class="p_ident" id="p-625KdRW5dL" href="#p-625KdRW5dL" tabindex="-1" role="presentation"></a>Las interfaces de los módulos tienen mucho en común con las interfaces de objetos, como las vimos en el <a href="06_object.html#interface">Capítulo 6</a>. Permiten que una parte del módulo esté disponible para el mundo exterior y mantienen el resto privado.</p> -<p><a class="p_ident" id="p-gChcqY1zpK" href="#p-gChcqY1zpK" tabindex="-1" role="presentation"></a>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 <em>ellos</em> utilizan de otros módulos. Estas relaciones se llaman <em>dependencias</em>. Si el módulo A utiliza funcionalidad del módulo B, se dice que <em>depende</em> 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.</p> +<p><a class="p_ident" id="p-gChcqY1zpK" href="#p-gChcqY1zpK" tabindex="-1" role="presentation"></a>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 <em>ellos</em> de otros módulos. Estas relaciones se llaman <em>dependencias</em>. Si el módulo A utiliza funcionalidad del módulo B, se dice que <em>depende</em> 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.</p> -<p><a class="p_ident" id="p-JTdQELjKXQ" href="#p-JTdQELjKXQ" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-JTdQELjKXQ" href="#p-JTdQELjKXQ" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-YC3EaTrkii" href="#h-YC3EaTrkii" tabindex="-1" role="presentation"></a>Módulos ES</h2> -<p><a class="p_ident" id="p-cFWVY8dCZa" href="#p-cFWVY8dCZa" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-n/IglitsC2" href="#p-n/IglitsC2" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-/LwUigk9I1" href="#p-/LwUigk9I1" tabindex="-1" role="presentation"></a>Desde ECMAScript 2015, JavaScript admite dos tipos diferentes de programas. Los <em>scripts</em> se comportan de la manera antigua: sus vinculaciones se definen en el ámbito global y no tienen forma de referenciar directamente otros scripts. Los <em>módulos</em> obtienen su propio ámbito separado y admiten las palabras clave <code>import</code> y <code>export</code>, que no están disponibles en los scripts, para declarar sus dependencias e interfaz. Este sistema de módulos se suele llamar <em>módulos de ES</em> (donde “ES” significa “ECMAScript”).</p> +<p><a class="p_ident" id="p-/LwUigk9I1" href="#p-/LwUigk9I1" tabindex="-1" role="presentation"></a>Desde ECMAScript 2015, JavaScript admite dos tipos diferentes de programas. Los <em>scripts</em> se comportan de la manera antigua: sus asociaciones se definen en el ámbito global y no tienen forma de referenciar directamente otros scripts. Los <em>módulos</em> obtienen su propio ámbito separado y admiten las palabras clave <code>import</code> y <code>export</code>, que no están disponibles en los scripts, para declarar sus dependencias e interfaz. Este sistema de módulos se suele llamar <em>módulos de ES</em> (donde <em>ES</em> significa “ECMAScript”).</p> <p><a class="p_ident" id="p-oP+GsCLjlM" href="#p-oP+GsCLjlM" tabindex="-1" role="presentation"></a>Un programa modular está compuesto por varios de estos módulos, conectados a través de sus importaciones y exportaciones.</p> -<p><a class="p_ident" id="p-TTzOObeni+" href="#p-TTzOObeni+" tabindex="-1" role="presentation"></a>Este ejemplo de módulo convierte entre nombres de días y números (como los devueltos por el método <code>getDay</code> de <code>Date</code>). Define una constante que no forma parte de su interfaz y dos funciones que sí lo son. No tiene dependencias.</p> +<p><a class="p_ident" id="p-TTzOObeni+" href="#p-TTzOObeni+" tabindex="-1" role="presentation"></a>Este ejemplo de módulo intercambia entre nombres de días y números (como los devueltos por el método <code>getDay</code> de <code>Date</code>). Define una constante que no forma parte de su interfaz y dos funciones que sí lo son. No tiene dependencias.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-8bQqoNLteX" href="#c-8bQqoNLteX" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">names</span> = [<span class="tok-string">"Domingo"</span>, <span class="tok-string">"Lunes"</span>, <span class="tok-string">"Martes"</span>, <span class="tok-string">"Miércoles"</span>, +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Jdl4dOS/qA" href="#c-Jdl4dOS/qA" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">nombres</span> = [<span class="tok-string">"Domingo"</span>, <span class="tok-string">"Lunes"</span>, <span class="tok-string">"Martes"</span>, <span class="tok-string">"Miércoles"</span>, <span class="tok-string">"Jueves"</span>, <span class="tok-string">"Viernes"</span>, <span class="tok-string">"Sábado"</span>]; -<span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-definition">dayName</span>(<span class="tok-definition">number</span>) { - <span class="tok-keyword">return</span> names[number]; +<span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-definition">nombreDía</span>(<span class="tok-definition">número</span>) { + <span class="tok-keyword">return</span> nombres[número]; } -<span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-definition">dayNumber</span>(<span class="tok-definition">name</span>) { - <span class="tok-keyword">return</span> names.indexOf(name); +<span class="tok-keyword">export</span> <span class="tok-keyword">function</span> <span class="tok-definition">númeroDía</span>(<span class="tok-definition">nombre</span>) { + <span class="tok-keyword">return</span> nombres.indexOf(nombre); }</pre> -<p><a class="p_ident" id="p-HBLfYi8Cx0" href="#p-HBLfYi8Cx0" tabindex="-1" role="presentation"></a>La palabra clave <code>export</code> 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.</p> +<p><a class="p_ident" id="p-HBLfYi8Cx0" href="#p-HBLfYi8Cx0" tabindex="-1" role="presentation"></a>La palabra clave <code>export</code> 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Sub1Ei6VzK" href="#c-Sub1Ei6VzK" tabindex="-1" role="presentation"></a><span class="tok-keyword">import</span> {<span class="tok-definition">dayName</span>} <span class="tok-keyword">from</span> <span class="tok-string">"./dayname.js"</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-A0LfNQesB3" href="#c-A0LfNQesB3" tabindex="-1" role="presentation"></a><span class="tok-keyword">import</span> {<span class="tok-definition">nombreDía</span>} <span class="tok-keyword">from</span> <span class="tok-string">"./dayname.js"</span>; <span class="tok-keyword">let</span> <span class="tok-definition">ahora</span> = <span class="tok-keyword">new</span> Date(); -console.log(<span class="tok-string2">`Hoy es </span>${dayName(ahora.getDay())}<span class="tok-string2">`</span>); +console.log(<span class="tok-string2">`Hoy es </span>${nombreDía(ahora.getDay())}<span class="tok-string2">`</span>); <span class="tok-comment">// → Hoy es Lunes</span></pre> -<p><a class="p_ident" id="p-/pMtoDSh3q" href="#p-/pMtoDSh3q" tabindex="-1" role="presentation"></a>La palabra clave <code>import</code>, 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.</p> +<p><a class="p_ident" id="p-/pMtoDSh3q" href="#p-/pMtoDSh3q" tabindex="-1" role="presentation"></a>La palabra clave <code>import</code>, 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.</p> -<p><a class="p_ident" id="p-bC1R/meysi" href="#p-bC1R/meysi" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-bC1R/meysi" href="#p-bC1R/meysi" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-Ie1lG7aO5o" href="#p-Ie1lG7aO5o" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-RuAH6Ttnx1" href="#p-RuAH6Ttnx1" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-dP1OiguXg/" href="#p-dP1OiguXg/" tabindex="-1" role="presentation"></a>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 <code>as</code> después de su nombre.</p> +<p><a class="p_ident" id="p-dP1OiguXg/" href="#p-dP1OiguXg/" tabindex="-1" role="presentation"></a>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 <code>as</code> después de su nombre.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-BDdYywvaeN" href="#c-BDdYywvaeN" tabindex="-1" role="presentation"></a><span class="tok-keyword">import</span> {dayName <span class="tok-keyword">as</span> <span class="tok-definition">nomDeJour</span>} <span class="tok-keyword">from</span> <span class="tok-string">"./dayname.js"</span>; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-iN54dYMjUT" href="#c-iN54dYMjUT" tabindex="-1" role="presentation"></a><span class="tok-keyword">import</span> {nombreDía <span class="tok-keyword">as</span> <span class="tok-definition">nomDeJour</span>} <span class="tok-keyword">from</span> <span class="tok-string">"./nombredia.js"</span>; console.log(nomDeJour(<span class="tok-number">3</span>)); <span class="tok-comment">// → Miércoles</span></pre> -<p><a class="p_ident" id="p-S+JFe+/ZwS" href="#p-S+JFe+/ZwS" tabindex="-1" role="presentation"></a>También es posible que un módulo tenga una exportación especial llamada <code>default</code>, que a menudo se usa para módulos que solo exportan un único enlace. Para definir una exportación predeterminada, se escribe <code>export default</code> antes de una expresión, una declaración de función o una declaración de clase.</p> +<p><a class="p_ident" id="p-S+JFe+/ZwS" href="#p-S+JFe+/ZwS" tabindex="-1" role="presentation"></a>También es posible que un módulo tenga una exportación especial llamada <code>default</code>, que a menudo se usa para módulos que solo exportan <em>un único</em> enlace. Para definir una exportación predeterminada, se escribe <code>export default</code> antes de una expresión, una declaración de función o una declaración de clase.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-X+WnaBkeaP" href="#c-X+WnaBkeaP" tabindex="-1" role="presentation"></a><span class="tok-keyword">export</span> <span class="tok-keyword">default</span> [<span class="tok-string">"Invierno"</span>, <span class="tok-string">"Primavera"</span>, <span class="tok-string">"Verano"</span>, <span class="tok-string">"Otoño"</span>];</pre> <p><a class="p_ident" id="p-9S8FRU/waO" href="#p-9S8FRU/waO" tabindex="-1" role="presentation"></a>Este enlace se importa omitiendo las llaves alrededor del nombre de la importación.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-+zRyOTSTAA" href="#c-+zRyOTSTAA" tabindex="-1" role="presentation"></a><span class="tok-keyword">import</span> <span class="tok-definition">nombresEstaciones</span> <span class="tok-keyword">from</span> <span class="tok-string">"./nombrsestaciones.js"</span>;</pre> +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-t1BnTMhvOF" href="#c-t1BnTMhvOF" tabindex="-1" role="presentation"></a><span class="tok-keyword">import</span> <span class="tok-definition">nombresEstaciones</span> <span class="tok-keyword">from</span> <span class="tok-string">"./nombresestaciones.js"</span>;</pre> <h2><a class="h_ident" id="h-/mspf0oEoK" href="#h-/mspf0oEoK" tabindex="-1" role="presentation"></a>Paquetes</h2> <p><a class="p_ident" id="p-5s4XEpecqW" href="#p-5s4XEpecqW" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-1YfLMLVIb+" href="#p-1YfLMLVIb+" tabindex="-1" role="presentation"></a>Pero, ¿cómo se configura esto? Digamos que quiero usar la función <code>parseINI</code> de <a href="09_regexp.html#ini">Capítulo 9</a> 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.</p> +<p><a class="p_ident" id="p-1YfLMLVIb+" href="#p-1YfLMLVIb+" tabindex="-1" role="presentation"></a>Pero, ¿cómo se configura esto? Digamos que quiero usar la función <code>procesarINI</code> de <a href="09_regexp.html#ini">Capítulo 9</a> 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.</p> <p><a class="p_ident" id="p-vGdlRRXoCw" href="#p-vGdlRRXoCw" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-fQS8IzdsnB" href="#p-fQS8IzdsnB" tabindex="-1" role="presentation"></a>Ahí es donde entran los <em>paquetes</em>. 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.</p> +<p><a class="p_ident" id="p-fQS8IzdsnB" href="#p-fQS8IzdsnB" tabindex="-1" role="presentation"></a>Ahí es donde entran los <em>paquetes</em>. 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.</p> -<p><a class="p_ident" id="p-xBz/AUEgrE" href="#p-xBz/AUEgrE" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-xBz/AUEgrE" href="#p-xBz/AUEgrE" tabindex="-1" role="presentation"></a>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.</p> -<p id="modules_npm"><a class="p_ident" id="p-gLwdFzz5mL" href="#p-gLwdFzz5mL" tabindex="-1" role="presentation"></a>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 (<a href="https://npmjs.org"><em>https://npmjs.org</em></a>).</p> +<p id="modules_npm"><a class="p_ident" id="p-gLwdFzz5mL" href="#p-gLwdFzz5mL" tabindex="-1" role="presentation"></a>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 (<a href="https://npmjs.org"><em>https://npmjs.org</em></a>).</p> -<p><a class="p_ident" id="p-kkiTHcsTLq" href="#p-kkiTHcsTLq" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-kkiTHcsTLq" href="#p-kkiTHcsTLq" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-tOycnX3WYO" href="#p-tOycnX3WYO" tabindex="-1" role="presentation"></a>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 <a href="09_regexp.html">Capítulo 9</a>, está disponible bajo el nombre del paquete <code>ini</code>.</p> +<p><a class="p_ident" id="p-c/IOk66jMl" href="#p-c/IOk66jMl" tabindex="-1" role="presentation"></a>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 <a href="09_regexp.html">Capítulo 9</a>, está disponible bajo el nombre de paquete <code>ini</code>.</p> -<p><a class="p_ident" id="p-yqgOfKlOJr" href="#p-yqgOfKlOJr" tabindex="-1" role="presentation"></a><a href="20_node.html">Capítulo 20</a> mostrará cómo instalar tales paquetes localmente usando el programa de línea de comandos <code>npm</code>.</p> +<p><a class="p_ident" id="p-gNnvjRkJk6" href="#p-gNnvjRkJk6" tabindex="-1" role="presentation"></a>El <a href="20_node.html">Capítulo 20</a> mostrará cómo instalar tales paquetes localmente usando el programa de línea de comandos <code>npm</code>.</p> <p><a class="p_ident" id="p-APTB7TqcHI" href="#p-APTB7TqcHI" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-WISEoIhSHx" href="#p-WISEoIhSHx" tabindex="-1" role="presentation"></a>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 <em>es</em> 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.</p> +<p><a class="p_ident" id="p-WISEoIhSHx" href="#p-WISEoIhSHx" tabindex="-1" role="presentation"></a>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 <em>es un trabajo</em>, 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.</p> -<p><a class="p_ident" id="p-KBJ7XL1akK" href="#p-KBJ7XL1akK" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-KBJ7XL1akK" href="#p-KBJ7XL1akK" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-qxh3ylw/2A" href="#p-qxh3ylw/2A" tabindex="-1" role="presentation"></a>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.</p> @@ -122,9 +122,9 @@ <h2><a class="h_ident" id="h-/mspf0oEoK" href="#h-/mspf0oEoK" tabindex="-1" role <h2 id="commonjs"><a class="h_ident" id="h-aoKuTQ1GvS" href="#h-aoKuTQ1GvS" tabindex="-1" role="presentation"></a>Módulos CommonJS</h2> -<p><a class="p_ident" id="p-gL2Y0VXAPv" href="#p-gL2Y0VXAPv" tabindex="-1" role="presentation"></a>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 <em>necesitaban</em> módulos.</p> +<p><a class="p_ident" id="p-gL2Y0VXAPv" href="#p-gL2Y0VXAPv" tabindex="-1" role="presentation"></a>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, <em>necesitaban</em> módulos.</p> -<p><a class="p_ident" id="p-wJ8IhSNMb2" href="#p-wJ8IhSNMb2" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-wJ8IhSNMb2" href="#p-wJ8IhSNMb2" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-38+Catnyuk" href="#p-38+Catnyuk" tabindex="-1" role="presentation"></a>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.</p> @@ -144,9 +144,9 @@ <h2 id="commonjs"><a class="h_ident" id="h-aoKuTQ1GvS" href="#h-aoKuTQ1GvS" tabi <p><a class="p_ident" id="p-6mhHLUjbEJ" href="#p-6mhHLUjbEJ" tabindex="-1" role="presentation"></a>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 <em>Módulos CommonJS</em>. 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.</p> -<p><a class="p_ident" id="p-4CiiEAoKta" href="#p-4CiiEAoKta" tabindex="-1" role="presentation"></a>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 <code>require</code>. 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 <code>exports</code>, que es el objeto de interfaz para el módulo. Comienza vacío y agregas propiedades para definir los valores exportados.</p> +<p><a class="p_ident" id="p-4CiiEAoKta" href="#p-4CiiEAoKta" tabindex="-1" role="presentation"></a>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 <code>require</code>. 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 <code>exports</code>, que es el objeto de interfaz para el módulo. Comienza vacío y agregas propiedades para definir los valores exportados.</p> -<p><a class="p_ident" id="p-T5/54GHTSi" href="#p-T5/54GHTSi" tabindex="-1" role="presentation"></a>Este módulo de ejemplo CommonJS proporciona una función de formateo de fechas. Utiliza dos packages de NPM: <code>ordinal</code> para convertir números en strings como <code>"1st"</code> y <code>"2nd"</code>, y <code>date-names</code> para obtener los nombres en inglés de los días de la semana y los meses. Exporta una única función, <code>formatDate</code>, que recibe un objeto <code>Date</code> y una cadena template.</p> +<p><a class="p_ident" id="p-T5/54GHTSi" href="#p-T5/54GHTSi" tabindex="-1" role="presentation"></a>Este módulo de ejemplo CommonJS proporciona una función de formateo de fechas. Utiliza dos paquetes de NPM: <code>ordinal</code> para convertir números en strings como <code>"1st"</code> y <code>"2nd"</code>, y <code>date-names</code> para obtener los nombres en inglés de los días de la semana y los meses. Exporta una única función, <code>formatDate</code>, que recibe un objeto <code>Date</code> y una cadena template.</p> <p><a class="p_ident" id="p-xUg6iYrQ1R" href="#p-xUg6iYrQ1R" tabindex="-1" role="presentation"></a>La cadena de template puede contener códigos que indican el formato, como <code>YYYY</code> para el año completo y <code>Do</code> para el día ordinal del mes. Puede pasársele una cadena como <code>"MMMM Do YYYY"</code> para obtener una salida como “22 de noviembre de 2017”.</p> @@ -164,7 +164,7 @@ <h2 id="commonjs"><a class="h_ident" id="h-aoKuTQ1GvS" href="#h-aoKuTQ1GvS" tabi }); };</pre> -<p><a class="p_ident" id="p-rSv7twJ8lD" href="#p-rSv7twJ8lD" tabindex="-1" role="presentation"></a>La interfaz de <code>ordinal</code> es una única función, mientras que <code>date-names</code> exporta un objeto que contiene múltiples cosas: <code>days</code> y <code>months</code> son arrays de nombres. La técnica de desestructuración es muy conveniente al crear enlaces para las interfaces importadas.</p> +<p><a class="p_ident" id="p-rSv7twJ8lD" href="#p-rSv7twJ8lD" tabindex="-1" role="presentation"></a>La interfaz de <code>ordinal</code> es una única función, mientras que <code>date-names</code> exporta un objeto que contiene múltiples cosas: <code>days</code> y <code>months</code> son arrays de nombres. La técnica de desestructuración es muy conveniente al crear asociaciones para las interfaces importadas.</p> <p><a class="p_ident" id="p-GEs894RmkW" href="#p-GEs894RmkW" tabindex="-1" role="presentation"></a>El módulo añade su función de interfaz a <code>exports</code> para que los módulos que dependen de él tengan acceso a ella. Podemos usar el módulo de la siguiente manera:</p> @@ -193,66 +193,66 @@ <h2 id="commonjs"><a class="h_ident" id="h-aoKuTQ1GvS" href="#h-aoKuTQ1GvS" tabi <p><a class="p_ident" id="p-hzt+dyc2ak" href="#p-hzt+dyc2ak" tabindex="-1" role="presentation"></a>JavaScript estándar no proporciona una función como <code>readFile</code>, 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 <code>readFile</code> existe.</p> -<p><a class="p_ident" id="p-DdUC56tlYx" href="#p-DdUC56tlYx" tabindex="-1" role="presentation"></a>Para evitar cargar el mismo módulo múltiples veces, <code>require</code> 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.</p> +<p><a class="p_ident" id="p-DdUC56tlYx" href="#p-DdUC56tlYx" tabindex="-1" role="presentation"></a>Para evitar cargar el mismo módulo múltiples veces, <code>require</code> 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.</p> <p><a class="p_ident" id="p-SN2EFHIPHc" href="#p-SN2EFHIPHc" tabindex="-1" role="presentation"></a>Al definir <code>require</code>, <code>exports</code> 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.</p> <p><a class="p_ident" id="p-2Efp/NfTnP" href="#p-2Efp/NfTnP" tabindex="-1" role="presentation"></a>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 <code>require</code> es una función normal, invocada cuando el módulo ya está en ejecución. A diferencia de las declaraciones <code>import</code>, las llamadas a <code>require</code> <em>pueden</em> aparecer dentro de funciones, y el nombre de la dependencia puede ser cualquier expresión que se evalúe a una cadena, mientras que <code>import</code> solo permite cadenas simples entre comillas.</p> -<p><a class="p_ident" id="p-e5KpMKLZw3" href="#p-e5KpMKLZw3" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-X5ztDrouMF" href="#p-X5ztDrouMF" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-MTtaNNMhF9" href="#h-MTtaNNMhF9" tabindex="-1" role="presentation"></a>Compilación y empaquetado</h2> -<p><a class="p_ident" id="p-yzlMlomsVd" href="#p-yzlMlomsVd" tabindex="-1" role="presentation"></a>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 <a href="08_error.html#typing">Capítulo 8</a>, 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.</p> +<p><a class="p_ident" id="p-yzlMlomsVd" href="#p-yzlMlomsVd" tabindex="-1" role="presentation"></a>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 <a href="08_error.html#typing">Capítulo 8</a>, 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.</p> <p><a class="p_ident" id="p-WF1sdYjiV8" href="#p-WF1sdYjiV8" tabindex="-1" role="presentation"></a>Para hacer esto posible, <em>compilan</em> 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.</p> <p><a class="p_ident" id="p-qcgO5h1rKl" href="#p-qcgO5h1rKl" tabindex="-1" role="presentation"></a>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 <em>bundlers</em>.</p> -<p><a class="p_ident" id="p-QbvQ4uTYWV" href="#p-QbvQ4uTYWV" tabindex="-1" role="presentation"></a>Y podemos ir más allá. Aparte del número de archivos, el <em>tamaño</em> 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 <em>minificadores</em>. 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.</p> +<p><a class="p_ident" id="p-QbvQ4uTYWV" href="#p-QbvQ4uTYWV" tabindex="-1" role="presentation"></a>Y podemos ir más allá. Aparte del número de archivos, el <em>tamaño</em> 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 <em>minificadores</em>. 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.</p> -<p><a class="p_ident" id="p-vTIW0dWF7B" href="#p-vTIW0dWF7B" tabindex="-1" role="presentation"></a>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 <em>múltiples</em> 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.</p> +<p><a class="p_ident" id="p-vTIW0dWF7B" href="#p-vTIW0dWF7B" tabindex="-1" role="presentation"></a>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 <em>múltiples</em> 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.</p> <h2><a class="h_ident" id="h-1WDZH0cb8f" href="#h-1WDZH0cb8f" tabindex="-1" role="presentation"></a>Diseño de módulos</h2> <p><a class="p_ident" id="p-R7pz6CLzKn" href="#p-R7pz6CLzKn" tabindex="-1" role="presentation"></a>Estructurar programas es uno de los aspectos más sutiles de la programación. Cualquier funcionalidad no trivial puede ser organizada de diversas formas.</p> -<p><a class="p_ident" id="p-gQbZwhLBAU" href="#p-gQbZwhLBAU" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-gQbZwhLBAU" href="#p-gQbZwhLBAU" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-42LbB+uOK4" href="#p-42LbB+uOK4" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-42LbB+uOK4" href="#p-42LbB+uOK4" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-KeQc3IaY1/" href="#p-KeQc3IaY1/" tabindex="-1" role="presentation"></a>Eso puede significar seguir convenciones existentes. Un buen ejemplo es el paquete <code>ini</code>. Este módulo imita el objeto estándar <code>JSON</code> al proporcionar funciones <code>parse</code> y <code>stringify</code> (para escribir un archivo INI), y, como <code>JSON</code>, 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.</p> -<p><a class="p_ident" id="p-T3tXk/ILoH" href="#p-T3tXk/ILoH" tabindex="-1" role="presentation"></a>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 <em>componiendo</em> el módulo con alguna función de lectura de archivos.</p> +<p><a class="p_ident" id="p-T3tXk/ILoH" href="#p-T3tXk/ILoH" tabindex="-1" role="presentation"></a>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 <em>componiendo</em> el módulo con alguna función de lectura de archivos.</p> -<p><a class="p_ident" id="p-PdHEezCz8n" href="#p-PdHEezCz8n" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-PdHEezCz8n" href="#p-PdHEezCz8n" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-f+ylIT0L4/" href="#p-f+ylIT0L4/" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-JsFAJjYsmz" href="#p-JsFAJjYsmz" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-lgijdltjB6" href="#p-lgijdltjB6" tabindex="-1" role="presentation"></a>Un ejemplo de una estructura de datos ligeramente más compleja es el grafo de <a href="07_robot.html">Capítulo 7</a>. 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.</p> +<p><a class="p_ident" id="p-lgijdltjB6" href="#p-lgijdltjB6" tabindex="-1" role="presentation"></a>Un ejemplo de una estructura de datos ligeramente más compleja es el grafo de <a href="07_robot.html">Capítulo 7</a>. 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.</p> -<p><a class="p_ident" id="p-ENslNQQZif" href="#p-ENslNQQZif" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-ENslNQQZif" href="#p-ENslNQQZif" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-Nyp7yR1Ls4" href="#p-Nyp7yR1Ls4" tabindex="-1" role="presentation"></a>Por ejemplo, está el paquete <code>dijkstrajs</code>. Un enfoque conocido para la búsqueda de rutas, bastante similar a nuestra función <code>findRoute</code>, se llama <em>algoritmo de Dijkstra</em>, en honor a Edsger Dijkstra, quien lo escribió por primera vez. A menudo se agrega el sufijo <code>js</code> a los nombres de los paquetes para indicar que están escritos en JavaScript. Este paquete <code>dijkstrajs</code> 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.</p> -<p><a class="p_ident" id="p-yhx4gxUsUs" href="#p-yhx4gxUsUs" tabindex="-1" role="presentation"></a>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).</p> +<p><a class="p_ident" id="p-1laDeZJ/tX" href="#p-1laDeZJ/tX" tabindex="-1" role="presentation"></a>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).</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-qllW3k2edE" href="#c-qllW3k2edE" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> {find_path} = require(<span class="tok-string">"dijkstrajs"</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-FfKCtLv56p" href="#c-FfKCtLv56p" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> {find_path} = require(<span class="tok-string">"dijkstrajs"</span>); -<span class="tok-keyword">let</span> <span class="tok-definition">graph</span> = {}; -<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">node</span> <span class="tok-keyword">of</span> Object.keys(roadGraph)) { - <span class="tok-keyword">let</span> <span class="tok-definition">edges</span> = graph[node] = {}; - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">dest</span> <span class="tok-keyword">of</span> roadGraph[node]) { - edges[dest] = <span class="tok-number">1</span>; +<span class="tok-keyword">let</span> <span class="tok-definition">grafo</span> = {}; +<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">nodo</span> <span class="tok-keyword">of</span> Object.keys(roadGraph)) { + <span class="tok-keyword">let</span> <span class="tok-definition">aristas</span> = grafo[nodo] = {}; + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">dest</span> <span class="tok-keyword">of</span> roadGraph[nodo]) { + aristas[dest] = <span class="tok-number">1</span>; } } -console.log(find_path(graph, <span class="tok-string">"Oficina de Correos"</span>, <span class="tok-string">"Cabaña"</span>)); +console.log(find_path(grafo, <span class="tok-string">"Oficina de Correos"</span>, <span class="tok-string">"Cabaña"</span>)); <span class="tok-comment">// → ["Oficina de Correos", "Casa de Alicia", "Cabaña"]</span></pre> -<p><a class="p_ident" id="p-mvorll6y2z" href="#p-mvorll6y2z" tabindex="-1" role="presentation"></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.</p> +<p><a class="p_ident" id="p-mvorll6y2z" href="#p-mvorll6y2z" tabindex="-1" role="presentation"></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 de cara a la composabilidad, averigua qué estructuras de datos están utilizando otras personas y, cuando sea posible, sigue su ejemplo.</p> <p><a class="p_ident" id="p-eCjSAo88Cd" href="#p-eCjSAo88Cd" tabindex="-1" role="presentation"></a>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.</p> @@ -262,13 +262,13 @@ <h2><a class="h_ident" id="h-NUFOUyK+lw" href="#h-NUFOUyK+lw" tabindex="-1" role <p><a class="p_ident" id="p-6+U0N4+Lmm" href="#p-6+U0N4+Lmm" tabindex="-1" role="presentation"></a>Dado que JavaScript históricamente no proporcionaba un sistema de módulos, se construyó el sistema CommonJS sobre él. Luego, en algún momento <em>obtuvo</em> un sistema incorporado, que ahora coexiste incómodamente con el sistema CommonJS.</p> -<p><a class="p_ident" id="p-V//HjvGu1k" href="#p-V//HjvGu1k" tabindex="-1" role="presentation"></a>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í.</p> +<p><a class="p_ident" id="p-V//HjvGu1k" href="#p-V//HjvGu1k" tabindex="-1" role="presentation"></a>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í.</p> <h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2> <h3><a class="i_ident" id="i-C94iKMHHsz" href="#i-C94iKMHHsz" tabindex="-1" role="presentation"></a>Un robot modular</h3> -<p id="modular_robot"><a class="p_ident" id="p-KYRgkkdGWG" href="#p-KYRgkkdGWG" tabindex="-1" role="presentation"></a>Estos son los enlaces que crea el proyecto del <a href="07_robot.html">Capítulo 7</a>:</p> +<p id="modular_robot"><a class="p_ident" id="p-l+FSmRgGYx" href="#p-l+FSmRgGYx" tabindex="-1" role="presentation"></a>Estas son las asociaciones que crea el proyecto del <a href="07_robot.html">Capítulo 7</a>:</p> <pre class="snippet" data-language="null" ><a class="c_ident" id="c-/nxTd1W0Sy" href="#c-/nxTd1W0Sy" tabindex="-1" role="presentation"></a>roads buildGraph @@ -290,25 +290,25 @@ <h3><a class="i_ident" id="i-C94iKMHHsz" href="#i-C94iKMHHsz" tabindex="-1" role <p><a class="p_ident" id="p-2jVU/smvtt" href="#p-2jVU/smvtt" tabindex="-1" role="presentation"></a>Esto es lo que habría hecho (pero de nuevo, no hay una única forma <em>correcta</em> de diseñar un módulo dado):</p> -<p><a class="p_ident" id="p-UjzpfJAtXv" href="#p-UjzpfJAtXv" tabindex="-1" role="presentation"></a>El código utilizado para construir el gráfico de carreteras se encuentra en el módulo <code>graph</code>. Como preferiría usar <code>dijkstrajs</code> 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 <code>dijkstrajs</code>. Este módulo exporta una única función, <code>buildGraph</code>. Haría que <code>buildGraph</code> 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.</p> +<p><a class="p_ident" id="p-UjzpfJAtXv" href="#p-UjzpfJAtXv" tabindex="-1" role="presentation"></a>El código utilizado para construir el grafo de carreteras se encuentra en el módulo <code>graph</code>. Como preferiría usar <code>dijkstrajs</code> 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 <code>dijkstrajs</code>. Este módulo exporta una única función, <code>buildGraph</code>. Haría que <code>buildGraph</code> 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.</p> -<p><a class="p_ident" id="p-zidvnGHVmE" href="#p-zidvnGHVmE" tabindex="-1" role="presentation"></a>El módulo <code>roads</code> contiene los datos crudos de las carreteras (el arreglo <code>roads</code>) y el enlace <code>roadGraph</code>. Este módulo depende de <code>./graph.js</code> y exporta el grafo de carreteras.</p> +<p><a class="p_ident" id="p-zidvnGHVmE" href="#p-zidvnGHVmE" tabindex="-1" role="presentation"></a>El módulo <code>roads</code> contiene los datos en bruto de las carreteras (el array <code>roads</code>) y el enlace <code>roadGraph</code>. Este módulo depende de <code>./graph.js</code> y exporta el grafo de carreteras.</p> <p><a class="p_ident" id="p-T1TF77/3do" href="#p-T1TF77/3do" tabindex="-1" role="presentation"></a>La clase <code>VillageState</code> se encuentra en el módulo <code>state</code>. Depende del módulo <code>./roads</code> porque necesita poder verificar que una carretera dada exista. También necesita <code>randomPick</code>. Dado que es una función de tres líneas, podríamos simplemente ponerla en el módulo <code>state</code> como una función auxiliar interna. Pero <code>randomRobot</code> 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 <code>random-item</code>, una solución razonable es hacer que ambos módulos dependan de eso. También podemos agregar la función <code>runRobot</code> 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 <code>VillageState</code> como la función <code>runRobot</code>.</p> -<p><a class="p_ident" id="p-iXSO7deUX8" href="#p-iXSO7deUX8" tabindex="-1" role="presentation"></a>Finalmente, los robots, junto con los valores en los que dependen, como <code>mailRoute</code>, podrían ir en un módulo <code>example-robots</code>, que depende de <code>./roads</code> y exporta las funciones del robot. Para que <code>goalOrientedRobot</code> pueda realizar la búsqueda de rutas, este módulo también depende de <code>dijkstrajs</code>.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 <code>VillageState</code> 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).</p> +<p><a class="p_ident" id="p-iXSO7deUX8" href="#p-iXSO7deUX8" tabindex="-1" role="presentation"></a>Finalmente, los robots, junto con los valores en los que dependen, como <code>mailRoute</code>, podrían ir en un módulo <code>example-robots</code>, que depende de <code>./roads</code> y exporta las funciones del robot. Para que <code>goalOrientedRobot</code> pueda realizar la búsqueda de rutas, este módulo también depende de <code>dijkstrajs</code>.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 <code>VillageState</code> 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).</p> <p><a class="p_ident" id="p-5qAcyeu8nT" href="#p-5qAcyeu8nT" tabindex="-1" role="presentation"></a>¿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 <code>random-item</code>, escribirlas por ti mismo es bastante fácil. Pero añadirlas donde las necesitas tiende a saturar tus módulos.</p> -<p><a class="p_ident" id="p-hBKwtogikk" href="#p-hBKwtogikk" tabindex="-1" role="presentation"></a>Sin embargo, tampoco debes subestimar el trabajo involucrado en <em>encontrar</em> 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.</p> +<p><a class="p_ident" id="p-hBKwtogikk" href="#p-hBKwtogikk" tabindex="-1" role="presentation"></a>Sin embargo, tampoco debes subestimar el trabajo empleado en <em>encontrar</em> 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.</p> -<p><a class="p_ident" id="p-BSb1XGC70+" href="#p-BSb1XGC70+" tabindex="-1" role="presentation"></a>Así que de nuevo, esto es un compromiso, y puedes decidir de cualquier manera dependiendo de cuánto te ayude realmente un paquete dado.</p> +<p><a class="p_ident" id="p-BSb1XGC70+" href="#p-BSb1XGC70+" tabindex="-1" role="presentation"></a>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.</p> </div></details> <h3><a class="i_ident" id="i-OksNSVKWEy" href="#i-OksNSVKWEy" tabindex="-1" role="presentation"></a>Módulo de caminos</h3> -<p><a class="p_ident" id="p-To5WmsUN/B" href="#p-To5WmsUN/B" tabindex="-1" role="presentation"></a>Escribe un módulo ES, basado en el ejemplo del <a href="07_robot.html">Capítulo 7</a>, que contenga el array de caminos y exporte la estructura de datos de gráfico que los representa como <code>roadGraph</code>. Debería depender de un módulo <code>./graph.js</code>, que exporta una función <code>buildGraph</code> 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).</p> +<p><a class="p_ident" id="p-To5WmsUN/B" href="#p-To5WmsUN/B" tabindex="-1" role="presentation"></a>Escribe un módulo ES, basado en el ejemplo del <a href="07_robot.html">Capítulo 7</a>, que contenga el array de caminos y exporte la estructura de datos de grafo que los representa como <code>roadGraph</code>. Debería depender de un módulo <code>./graph.js</code>, que exporta una función <code>buildGraph</code> 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).</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-aUNoo52xRY" href="#c-aUNoo52xRY" tabindex="-1" role="presentation"></a><span class="tok-comment">// Añade dependencias y exportaciones</span> @@ -331,7 +331,7 @@ <h3><a class="i_ident" id="i-OksNSVKWEy" href="#i-OksNSVKWEy" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-L9cRUPvKkw" href="#p-L9cRUPvKkw" tabindex="-1" role="presentation"></a>Dado que este es un módulo ES, debes usar <code>import</code> para acceder al módulo de gráfico. Esto se describió como exportando una función de <code>buildGraph</code>, la cual puedes seleccionar de su objeto de interfaz con una declaración de desestructuración <code>const</code>.</p> +<p><a class="p_ident" id="p-L9cRUPvKkw" href="#p-L9cRUPvKkw" tabindex="-1" role="presentation"></a>Dado que este es un módulo ES, debes usar <code>import</code> para acceder al módulo de grafo. Esto se describió como exportando una función de <code>buildGraph</code>, la cual puedes seleccionar de su objeto de interfaz con una declaración de desestructuración <code>const</code>.</p> <p><a class="p_ident" id="p-XPWdEl/Mff" href="#p-XPWdEl/Mff" tabindex="-1" role="presentation"></a>Para exportar <code>roadGraph</code>, colocas la palabra clave <code>export</code> antes de su definición. Debido a que <code>buildGraph</code> toma una estructura de datos que no coincide exactamente con <code>roads</code>, la división de las cadenas de carretera debe ocurrir en tu módulo.</p> diff --git a/html/11_async.html b/html/11_async.html index 1f7f6642..87a6568b 100644 --- a/html/11_async.html +++ b/html/11_async.html @@ -20,45 +20,45 @@ <h1>Programación Asíncrona</h1> </blockquote><figure class="chapter framed"><img src="img/chapter_picture_11.jpg" alt="Ilustración que muestra dos cuervos en una rama de árbol"></figure> -<p><a class="p_ident" id="p-VOCX31yYLX" href="#p-VOCX31yYLX" tabindex="-1" role="presentation"></a>La parte central de una computadora, la parte que lleva a cabo los pasos individuales que componen nuestros programas, se llama el <em>procesador</em>. 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.</p> +<p><a class="p_ident" id="p-VOCX31yYLX" href="#p-VOCX31yYLX" tabindex="-1" role="presentation"></a>La parte central de una computadora, la parte que lleva a cabo los pasos individuales que componen nuestros programas, se llama <em>procesador</em>. 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.</p> -<p><a class="p_ident" id="p-hbyVo1hWzb" href="#p-hbyVo1hWzb" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-hbyVo1hWzb" href="#p-hbyVo1hWzb" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-sSdVomtbXO" href="#p-sSdVomtbXO" tabindex="-1" role="presentation"></a>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 <em>único</em> programa pueda avanzar mientras espera una solicitud de red.</p> +<p><a class="p_ident" id="p-sSdVomtbXO" href="#p-sSdVomtbXO" tabindex="-1" role="presentation"></a>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 <em>único</em> programa pueda avanzar mientras espera una solicitud de red.</p> <h2><a class="h_ident" id="h-tfdiSubDV/" href="#h-tfdiSubDV/" tabindex="-1" role="presentation"></a>Asincronía</h2> -<p><a class="p_ident" id="p-hOUZrWQWAe" href="#p-hOUZrWQWAe" tabindex="-1" role="presentation"></a>En un modelo de programación <em>sincrónico</em>, 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.</p> +<p><a class="p_ident" id="p-hOUZrWQWAe" href="#p-hOUZrWQWAe" tabindex="-1" role="presentation"></a>En un modelo de programación <em>sincrónico</em>, 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.</p> -<p><a class="p_ident" id="p-AfqGZZISHU" href="#p-AfqGZZISHU" tabindex="-1" role="presentation"></a>Un modelo <em>asincrónico</em> 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).</p> +<p><a class="p_ident" id="p-IlgtHDuXQZ" href="#p-IlgtHDuXQZ" tabindex="-1" role="presentation"></a>Un modelo <em>asíncrono</em> 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).</p> -<p><a class="p_ident" id="p-uV05EDkpgy" href="#p-uV05EDkpgy" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-9kP4nAsEsK" href="#p-9kP4nAsEsK" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-lPwjR+DoPb" href="#p-lPwjR+DoPb" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-lPwjR+DoPb" href="#p-lPwjR+DoPb" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-CzT2t5gZTO" href="#p-CzT2t5gZTO" tabindex="-1" role="presentation"></a>La solución a este problema, en un sistema sincrónico, es iniciar hebras de control adicionales. Una <em>hebra</em> 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.</p> +<p><a class="p_ident" id="p-dqKneyp1kQ" href="#p-dqKneyp1kQ" tabindex="-1" role="presentation"></a>La solución a este problema, en un sistema sincrónico, es iniciar hilos de control adicionales. Un <em>hilo</em> 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.</p> -<p><a class="p_ident" id="p-Wkrmfutpwr" href="#p-Wkrmfutpwr" tabindex="-1" role="presentation"></a>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 <em>parte</em> 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.</p><figure><img src="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."></figure> +<p><a class="p_ident" id="p-Wkrmfutpwr" href="#p-Wkrmfutpwr" tabindex="-1" role="presentation"></a>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 <em>parte</em> 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.</p><figure><img src="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."></figure> -<p><a class="p_ident" id="p-m0bMwsi7Hj" href="#p-m0bMwsi7Hj" tabindex="-1" role="presentation"></a>Otra forma de describir la diferencia es que esperar a que las acciones terminen es <em>implícito</em> en el modelo síncrono, mientras que es <em>explícito</em>, bajo nuestro control, en el modelo asíncrono.</p> +<p><a class="p_ident" id="p-m0bMwsi7Hj" href="#p-m0bMwsi7Hj" tabindex="-1" role="presentation"></a>Otra forma de describir la diferencia es que esperar a que las acciones terminen es <em>implícito</em> en el modelo sincrónico, mientras que es <em>explícito</em>, bajo nuestro control, en el modelo asíncrono.</p> -<p><a class="p_ident" id="p-Gt0CDXTJDZ" href="#p-Gt0CDXTJDZ" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-Gt0CDXTJDZ" href="#p-Gt0CDXTJDZ" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-obm8VsybKv" href="#p-obm8VsybKv" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-QDJln/phk5" href="#p-QDJln/phk5" tabindex="-1" role="presentation"></a>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.</p> -<h2><a class="h_ident" id="h-r4RRRjuEnZ" href="#h-r4RRRjuEnZ" tabindex="-1" role="presentation"></a>Retrollamadas</h2> +<h2><a class="h_ident" id="h-n9ws/jdPpb" href="#h-n9ws/jdPpb" tabindex="-1" role="presentation"></a>Callbacks</h2> -<p><a class="p_ident" id="p-F908df1kGc" href="#p-F908df1kGc" tabindex="-1" role="presentation"></a>Un enfoque para la programación asíncrona es hacer que las funciones que necesitan esperar por algo tomen un argumento adicional, una <em>función de devolución de llamada</em>. 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.</p> +<p><a class="p_ident" id="p-F908df1kGc" href="#p-F908df1kGc" tabindex="-1" role="presentation"></a>Un enfoque para la programación asíncrona es hacer que las funciones que necesitan esperar por algo tomen un argumento adicional, una <em>función de devolución de llamada</em>, o <em>función de callback</em>. 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.</p> <p><a class="p_ident" id="p-D6ZI0eQstT" href="#p-D6ZI0eQstT" tabindex="-1" role="presentation"></a>Como ejemplo, la función <code>setTimeout</code>, 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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-RyFm7Uoiuv" href="#c-RyFm7Uoiuv" tabindex="-1" role="presentation"></a>setTimeout(() => console.log(<span class="tok-string">"Tick"</span>), <span class="tok-number">500</span>);</pre> -<p><a class="p_ident" id="p-hLrWLhn03U" href="#p-hLrWLhn03U" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-hLrWLhn03U" href="#p-hLrWLhn03U" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-IUCZ3FiQHh" href="#p-IUCZ3FiQHh" tabindex="-1" role="presentation"></a>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 <code>readTextFile</code>, la cual lee el contenido de un archivo como una cadena y lo pasa a una función de devolución de llamada.</p> +<p><a class="p_ident" id="p-wyrH3ZpP9/" href="#p-wyrH3ZpP9/" tabindex="-1" role="presentation"></a>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 <code>readTextFile</code>, la cual lee el contenido de un archivo como una cadena y lo pasa a una función de callback.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-W+pevJMeqq" href="#c-W+pevJMeqq" tabindex="-1" role="presentation"></a>readTextFile(<span class="tok-string">"lista_de_compras.txt"</span>, <span class="tok-definition">contenido</span> => { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Zg8BmUPy+9" href="#c-Zg8BmUPy+9" tabindex="-1" role="presentation"></a>readTextFile(<span class="tok-string">"lista_compra.txt"</span>, <span class="tok-definition">contenido</span> => { console.log(<span class="tok-string2">`Lista de Compras:\n</span>${contenido}<span class="tok-string2">`</span>); }); <span class="tok-comment">// → Lista de Compras:</span> @@ -67,25 +67,25 @@ <h2><a class="h_ident" id="h-r4RRRjuEnZ" href="#h-r4RRRjuEnZ" tabindex="-1" role <p><a class="p_ident" id="p-+9d/1JmuUd" href="#p-+9d/1JmuUd" tabindex="-1" role="presentation"></a>La función <code>readTextFile</code> no es parte del estándar de JavaScript. Veremos cómo leer archivos en el navegador y en Node.js en capítulos posteriores.</p> -<p><a class="p_ident" id="p-YF9vn5sToC" href="#p-YF9vn5sToC" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-YF9vn5sToC" href="#p-YF9vn5sToC" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NvOoAKarwY" href="#c-NvOoAKarwY" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">compararArchivos</span>(<span class="tok-definition">archivoA</span>, <span class="tok-definition">archivoB</span>, <span class="tok-definition">devolucionLlamada</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-xRsv3/rEga" href="#c-xRsv3/rEga" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">compararArchivos</span>(<span class="tok-definition">archivoA</span>, <span class="tok-definition">archivoB</span>, <span class="tok-definition">callback</span>) { readTextFile(archivoA, <span class="tok-definition">contenidoA</span> => { readTextFile(archivoB, <span class="tok-definition">contenidoB</span> => { - devolucionLlamada(contenidoA == contenidoB); + callback(contenidoA == contenidoB); }); }); }</pre> -<p><a class="p_ident" id="p-BF6VvMVpzu" href="#p-BF6VvMVpzu" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-hw6hqXhrRg" href="#p-hw6hqXhrRg" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-E60GIBKpBQ" href="#p-E60GIBKpBQ" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-E60GIBKpBQ" href="#p-E60GIBKpBQ" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-O5NEmIaSuD" href="#h-O5NEmIaSuD" tabindex="-1" role="presentation"></a>Promesas</h2> -<p><a class="p_ident" id="p-ltS3in1H1E" href="#p-ltS3in1H1E" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-yfagraLtYh" href="#p-yfagraLtYh" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-3ArPEeb3po" href="#p-3ArPEeb3po" tabindex="-1" role="presentation"></a>Para esto sirve la clase estándar <code>Promise</code>. Una <em>promesa</em> es un recibo que representa un valor que aún puede no estar disponible. Proporciona un método <code>then</code> 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 <em>resuelve</em>, es decir, su valor se vuelve disponible, esas funciones (puede haber varias) son llamadas con el valor del resultado. Es posible llamar a <code>then</code> en una promesa que ya ha sido resuelta; tu función seguirá siendo llamada.</p> +<p><a class="p_ident" id="p-XD820mj0vG" href="#p-XD820mj0vG" tabindex="-1" role="presentation"></a>Para esto sirve la clase estándar <code>Promise</code>. Una <em>promesa</em> es un recibo que representa un valor que aún puede no estar disponible. Proporciona un método <code>then</code> 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 <em>resuelve</em>, es decir, cuando su valor se vuelve disponible, esas funciones (puede haber varias) son llamadas con el valor del resultado. Es posible llamar a <code>then</code> en una promesa que ya ha sido resuelta —tu función aún será llamada.</p> <p><a class="p_ident" id="p-RGlDUtOJ93" href="#p-RGlDUtOJ93" tabindex="-1" role="presentation"></a>La forma más sencilla de crear una promesa es llamando a <code>Promise.resolve</code>. 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.</p> @@ -93,71 +93,71 @@ <h2><a class="h_ident" id="h-O5NEmIaSuD" href="#h-O5NEmIaSuD" tabindex="-1" role quince.then(<span class="tok-definition">valor</span> => console.log(<span class="tok-string2">`Obtenido </span>${valor}<span class="tok-string2">`</span>)); <span class="tok-comment">// → Obtenido 15</span></pre> -<p><a class="p_ident" id="p-c4wtF3+bV/" href="#p-c4wtF3+bV/" tabindex="-1" role="presentation"></a>Para crear una promesa que no se resuelva inmediatamente, puedes utilizar <code>Promise</code> 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.</p> +<p><a class="p_ident" id="p-c4wtF3+bV/" href="#p-c4wtF3+bV/" tabindex="-1" role="presentation"></a>Para crear una promesa que no se resuelva inmediatamente, puedes utilizar <code>Promise</code> 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 (<code>resolver</code>, en el ejemplo) que puede utilizar para resolver la promesa.</p> <p><a class="p_ident" id="p-C4xSB0FUME" href="#p-C4xSB0FUME" tabindex="-1" role="presentation"></a>Así es como podrías crear una interfaz basada en promesas para la función <code>readTextFile</code>:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-2C6M2ihbOc" href="#c-2C6M2ihbOc" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">textFile</span>(<span class="tok-definition">nombreArchivo</span>) { - <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise(<span class="tok-definition">resolve</span> => { - readTextFile(nombreArchivo, <span class="tok-definition">texto</span> => resolve(texto)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-RNQP/MOsHy" href="#c-RNQP/MOsHy" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">archivoTexto</span>(<span class="tok-definition">nombreArchivo</span>) { + <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise(<span class="tok-definition">resolver</span> => { + readTextFile(nombreArchivo, <span class="tok-definition">texto</span> => resolver(texto)); }); } -textFile(<span class="tok-string">"planes.txt"</span>).then(console.log);</pre> +archivoTexto(<span class="tok-string">"planes.txt"</span>).then(console.log);</pre> -<p><a class="p_ident" id="p-sr4id/GYVf" href="#p-sr4id/GYVf" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-sr4id/GYVf" href="#p-sr4id/GYVf" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-JzjtMWC7B5" href="#p-JzjtMWC7B5" tabindex="-1" role="presentation"></a>Una característica útil del método <code>then</code> 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 <code>then</code> para configurar una secuencia de acciones asíncronas.</p> +<p><a class="p_ident" id="p-JzjtMWC7B5" href="#p-JzjtMWC7B5" tabindex="-1" role="presentation"></a>Una característica útil del método <code>then</code> 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 <code>then</code> para configurar una secuencia de acciones asíncronas.</p> <p><a class="p_ident" id="p-kO8eylKtGY" href="#p-kO8eylKtGY" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Hb690LALZU" href="#c-Hb690LALZU" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">randomFile</span>(<span class="tok-definition">archivoLista</span>) { - <span class="tok-keyword">return</span> textFile(archivoLista) +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-tgcWPWseIi" href="#c-tgcWPWseIi" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">archivoAleatorio</span>(<span class="tok-definition">archivoLista</span>) { + <span class="tok-keyword">return</span> archivoTexto(archivoLista) .then(<span class="tok-definition">contenido</span> => contenido.trim().split(<span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>)) .then(<span class="tok-definition">ls</span> => ls[Math.floor(Math.random() * ls.length)]) - .then(<span class="tok-definition">nombreArchivo</span> => textFile(nombreArchivo)); + .then(<span class="tok-definition">nombreArchivo</span> => archivoTexto(nombreArchivo)); }</pre> -<p><a class="p_ident" id="p-vwGbLsp+ut" href="#p-vwGbLsp+ut" tabindex="-1" role="presentation"></a>La función devuelve el resultado de esta cadena de llamadas a <code>then</code>. La promesa inicial obtiene la lista de archivos como una cadena. La primera llamada a <code>then</code> transforma esa cadena en un array de líneas, produciendo una nueva promesa. La segunda llamada a <code>then</code> elige una línea aleatoria de eso, produciendo una tercera promesa que arroja un único nombre de archivo. La llamada final a <code>then</code> 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.</p> +<p><a class="p_ident" id="p-vwGbLsp+ut" href="#p-vwGbLsp+ut" tabindex="-1" role="presentation"></a>La función devuelve el resultado de esta cadena de llamadas a <code>then</code>. La promesa inicial obtiene la lista de archivos como una cadena. La primera llamada a <code>then</code> transforma esa cadena en un array de líneas, produciendo una nueva promesa. La segunda llamada a <code>then</code> 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 <code>then</code> 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.</p> -<p><a class="p_ident" id="p-WSdli2Whfb" href="#p-WSdli2Whfb" tabindex="-1" role="presentation"></a>En este código, las funciones utilizadas en las primeras dos llamadas a <code>then</code> devuelven un valor regular, que se pasará inmediatamente a la promesa devuelta por <code>then</code> cuando la función regrese. La última devuelve una promesa (<code>textFile(nombreArchivo)</code>), convirtiéndola en un paso asincrónico real.</p> +<p><a class="p_ident" id="p-RfQgfieqda" href="#p-RfQgfieqda" tabindex="-1" role="presentation"></a>En este código, las funciones utilizadas en las primeras dos llamadas a <code>then</code> devuelven un valor normal, que se pasará inmediatamente a la promesa devuelta por <code>then</code> cuando la función retorne. La última devuelve una promesa (<code>archivoTexto(nombreArchivo)</code>), lo que la convierte en un paso asíncrono de verdad.</p> -<p><a class="p_ident" id="p-z9ZsQuS8Cj" href="#p-z9ZsQuS8Cj" tabindex="-1" role="presentation"></a>También habría sido posible realizar todos estos pasos dentro de un solo callback de <code>then</code>, ya que solo el último paso es realmente asíncrono. Pero los tipos de envolturas <code>then</code> 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.</p> +<p><a class="p_ident" id="p-z9ZsQuS8Cj" href="#p-z9ZsQuS8Cj" tabindex="-1" role="presentation"></a>También habría sido posible realizar todos estos pasos dentro de un solo callback de <code>then</code>, ya que solo el último paso es realmente asíncrono. Pero el tipo de envolturas <code>then</code> 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Q3vBSBU3QB" href="#c-Q3vBSBU3QB" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">jsonFile</span>(<span class="tok-definition">nombreArchivo</span>) { - <span class="tok-keyword">return</span> textFile(nombreArchivo).then(JSON.parse); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-BcfrCnNsnL" href="#c-BcfrCnNsnL" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">archivoJson</span>(<span class="tok-definition">nombreArchivo</span>) { + <span class="tok-keyword">return</span> archivoTexto(nombreArchivo).then(JSON.parse); } -jsonFile(<span class="tok-string">"package.json"</span>).then(console.log);</pre> +archivoJson(<span class="tok-string">"package.json"</span>).then(console.log);</pre> <p><a class="p_ident" id="p-YrcahdlIgs" href="#p-YrcahdlIgs" tabindex="-1" role="presentation"></a>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 <em>puede</em> 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 <code>then</code>, se ejecutan de forma asíncrona a medida que sus entradas están disponibles.</p> -<h2><a class="h_ident" id="h-itgMGyRo24" href="#h-itgMGyRo24" tabindex="-1" role="presentation"></a>Falla</h2> +<h2><a class="h_ident" id="h-M4lb98nplJ" href="#h-M4lb98nplJ" tabindex="-1" role="presentation"></a>Fallo</h2> -<p><a class="p_ident" id="p-+fxovQGwNA" href="#p-+fxovQGwNA" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-szOVDrRnwN" href="#p-szOVDrRnwN" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-8XWhkwl0eo" href="#p-8XWhkwl0eo" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-YIfhZsaqoF" href="#p-YIfhZsaqoF" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-r4LVn4wZ0P" href="#p-r4LVn4wZ0P" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-nEBtfDSRB+" href="#p-nEBtfDSRB+" tabindex="-1" role="presentation"></a>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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-/lUgiJQG9L" href="#c-/lUgiJQG9L" tabindex="-1" role="presentation"></a>unaFuncionAsincrona((<span class="tok-definition">error</span>, <span class="tok-definition">valor</span>) => { <span class="tok-keyword">if</span> (error) manejarError(error); <span class="tok-keyword">else</span> procesarValor(valor); });</pre> -<p><a class="p_ident" id="p-WOsDKk7EQx" href="#p-WOsDKk7EQx" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-WOsDKk7EQx" href="#p-WOsDKk7EQx" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-4XJanqagJR" href="#p-4XJanqagJR" tabindex="-1" role="presentation"></a>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 <code>then</code>) se llaman solo cuando la acción es exitosa, y los rechazos se propagan a la nueva promesa que es devuelta por <code>then</code>. Cuando un manejador lanza una excepción, esto causa automáticamente que la promesa producida por la llamada a su <code>then</code> 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ó.</p> +<p><a class="p_ident" id="p-xKl/89KNJb" href="#p-xKl/89KNJb" tabindex="-1" role="presentation"></a>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 <code>then</code>) se llaman solo cuando la acción es exitosa, y los rechazos se propagan a la nueva promesa devuelta por <code>then</code>. Cuando un manejador lanza una excepción, esto causa automáticamente que la promesa producida por su llamada a <code>then</code> 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.</p> -<p><a class="p_ident" id="p-JZgXPDvBUG" href="#p-JZgXPDvBUG" tabindex="-1" role="presentation"></a>Al igual que resolver una promesa proporciona un valor, rechazar una también lo hace, generalmente llamado el <em>motivo</em> 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 <code>Promise.reject</code> que crea una nueva promesa inmediatamente rechazada.</p> +<p><a class="p_ident" id="p-JZgXPDvBUG" href="#p-JZgXPDvBUG" tabindex="-1" role="presentation"></a>Al igual que resolver una promesa proporciona un valor, rechazar una también lo hace, generalmente llamado el <em>motivo</em> 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 <code>Promise.reject</code> que crea una nueva promesa inmediatamente rechazada.</p> <p><a class="p_ident" id="p-gAl8dny3ZI" href="#p-gAl8dny3ZI" tabindex="-1" role="presentation"></a>Para manejar explícitamente tales rechazos, las promesas tienen un método <code>catch</code> que registra un manejador para ser llamado cuando la promesa es rechazada, similar a cómo los manejadores de <code>then</code> manejan la resolución normal. También es muy similar a <code>then</code> 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 <code>catch</code> en caso contrario. Si un manejador de <code>catch</code> lanza un error, la nueva promesa también se rechaza.</p> -<p><a class="p_ident" id="p-SHY7aonJ/O" href="#p-SHY7aonJ/O" tabindex="-1" role="presentation"></a>Como un atajo, <code>then</code> también acepta un manejador de rechazo como segundo argumento, para poder instalar ambos tipos de manejadores en una sola llamada de método.</p> +<p><a class="p_ident" id="p-YEPo8wATzV" href="#p-YEPo8wATzV" tabindex="-1" role="presentation"></a>Como atajo, <code>then</code> también acepta un manejador de rechazo como segundo argumento, conque puedes instalar ambos tipos de manejadores en una sola llamada de método: <code>.<wbr>then(manejadorDeAceptación, manejadorDeRechazo)</code>.</p> -<p><a class="p_ident" id="p-cAXjTS7KlW" href="#p-cAXjTS7KlW" tabindex="-1" role="presentation"></a>Una función pasada al constructor <code>Promise</code> recibe un segundo argumento, junto con la función de resolución, que puede usar para rechazar la nueva promesa.Cuando nuestra función <code>readTextFile</code> encuentra un problema, pasa el error a su función de devolución de llamada como segundo argumento. Nuestro envoltorio <code>textFile</code> debería realmente examinar ese argumento, de manera que un fallo cause que la promesa que devuelve sea rechazada.</p> +<p><a class="p_ident" id="p-cAXjTS7KlW" href="#p-cAXjTS7KlW" tabindex="-1" role="presentation"></a>Una función pasada al constructor <code>Promise</code> recibe un segundo argumento, junto con la función de resolución, que puede usar para rechazar la nueva promesa. Cuando nuestra función <code>readTextFile</code> encuentra un problema, pasa el error a su función callback como segundo argumento. Nuestro envoltorio <code>archivoTexto</code> debería realmente examinar ese argumento, de manera que un fallo cause que la promesa que devuelve sea rechazada.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Z3TGL4evDx" href="#c-Z3TGL4evDx" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">textFile</span>(<span class="tok-definition">filename</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-BXbh8xpAiG" href="#c-BXbh8xpAiG" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">archivoTexto</span>(<span class="tok-definition">filename</span>) { <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise((<span class="tok-definition">resolve</span>, <span class="tok-definition">reject</span>) => { readTextFile(filename, (<span class="tok-definition">text</span>, <span class="tok-definition">error</span>) => { <span class="tok-keyword">if</span> (error) reject(error); @@ -168,7 +168,7 @@ <h2><a class="h_ident" id="h-itgMGyRo24" href="#h-itgMGyRo24" tabindex="-1" role <p><a class="p_ident" id="p-LtILeb1bZh" href="#p-LtILeb1bZh" tabindex="-1" role="presentation"></a>Las cadenas de valores de promesa creadas por llamadas a <code>then</code> y <code>catch</code> 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-2Eh0hD89OR" href="#c-2Eh0hD89OR" tabindex="-1" role="presentation"></a><span class="tok-keyword">new</span> Promise((<span class="tok-definition">_</span>, <span class="tok-definition">reject</span>) => reject(<span class="tok-keyword">new</span> Error(<span class="tok-string">"Fail"</span>))) +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-7aeG1gDVR8" href="#c-7aeG1gDVR8" tabindex="-1" role="presentation"></a><span class="tok-keyword">new</span> Promise((<span class="tok-definition">_</span>, <span class="tok-definition">rechazar</span>) => rechazar(<span class="tok-keyword">new</span> Error(<span class="tok-string">"Fail"</span>))) .then(<span class="tok-definition">value</span> => console.log(<span class="tok-string">"Manejador 1:"</span>, value)) .catch(<span class="tok-definition">reason</span> => { console.log(<span class="tok-string">"Error capturado "</span> + reason); @@ -176,130 +176,137 @@ <h2><a class="h_ident" id="h-itgMGyRo24" href="#h-itgMGyRo24" tabindex="-1" role }) .then(<span class="tok-definition">value</span> => console.log(<span class="tok-string">"Manejador 2:"</span>, value)); <span class="tok-comment">// → Error capturado Error: Fail</span> -<span class="tok-comment">// → Handler 2: nothing</span></pre> +<span class="tok-comment">// → Manejador 2: nada</span></pre> + +<div class="translator-note"><p><strong>N. del T.:</strong> nótese cómo el parámetro que se pasa al constructor <code>Promise</code> 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 <code>resolve</code> para el primer caso y <code>reject</code> para el segundo o, como en este ejemplo, <code>_</code> para el primero (porque ni siquiera lo necesitamos) y <code>rechazar</code> para el segundo.</p> +</div> -<p><a class="p_ident" id="p-iBD0Lbg2Bk" href="#p-iBD0Lbg2Bk" tabindex="-1" role="presentation"></a>La primera función de manejador regular no es llamada, porque en ese punto del pipeline la promesa contiene un rechazo. El manejador <code>catch</code> maneja ese rechazo y devuelve un valor, que se le da a la segunda función de manejador.</p> +<p><a class="p_ident" id="p-IbdqRNj4zc" href="#p-IbdqRNj4zc" tabindex="-1" role="presentation"></a>El primer manejador <code>then</code> no es llamado porque, en ese punto del pipeline, la promesa contiene un rechazo. El manejador <code>catch</code> maneja ese rechazo y devuelve un valor, que se le da al segundo manejador <code>then</code>.</p> <p><a class="p_ident" id="p-nAXpd8J86T" href="#p-nAXpd8J86T" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-XxJsV0JUaZ" href="#h-XxJsV0JUaZ" tabindex="-1" role="presentation"></a>Carla</h2> -<p><a class="p_ident" id="p-sI2r0mTENb" href="#p-sI2r0mTENb" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-sI2r0mTENb" href="#p-sI2r0mTENb" tabindex="-1" role="presentation"></a>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.</p> + +<p><a class="p_ident" id="p-jIHoxx51eu" href="#p-jIHoxx51eu" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-jIHoxx51eu" href="#p-jIHoxx51eu" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-cvIv/A82B2" href="#p-cvIv/A82B2" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-cvIv/A82B2" href="#p-cvIv/A82B2" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-Msy2NLY8h2" href="#p-Msy2NLY8h2" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-LznaJcdrKY" href="#p-LznaJcdrKY" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-xilnVxK0xf" href="#p-xilnVxK0xf" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-qyFUB1V+JF" href="#p-qyFUB1V+JF" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-qyFUB1V+JF" href="#p-qyFUB1V+JF" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-/5q4Gr2m3m" href="#h-/5q4Gr2m3m" tabindex="-1" role="presentation"></a>Infiltración</h2> -<p><a class="p_ident" id="p-lIH7NawKaH" href="#p-lIH7NawKaH" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-lIH7NawKaH" href="#p-lIH7NawKaH" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-8ZznJOIiK/" href="#p-8ZznJOIiK/" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-bzZjZThivN" href="#p-bzZjZThivN" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-Jzo5cZ0BBA" href="#p-Jzo5cZ0BBA" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-oe48QPvm/G" href="#p-oe48QPvm/G" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-RIZ6rVDtx9" href="#p-RIZ6rVDtx9" tabindex="-1" role="presentation"></a>Supongamos que tenemos una función <code>joinWifi</code>. 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-yGIEN33W5z" href="#c-yGIEN33W5z" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">withTimeout</span>(<span class="tok-definition">promise</span>, <span class="tok-definition">tiempo</span>) { - <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise((<span class="tok-definition">resolve</span>, <span class="tok-definition">reject</span>) => { - promise.then(resolve, reject); - setTimeout(() => reject(<span class="tok-string">"Se agotó el tiempo"</span>), tiempo); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-m5DqK0OfBr" href="#c-m5DqK0OfBr" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">conTiempoDeEspera</span>(<span class="tok-definition">promesa</span>, <span class="tok-definition">tiempo</span>) { + <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise((<span class="tok-definition">resolver</span>, <span class="tok-definition">rechazar</span>) => { + promesa.then(resolver, rechazar); + setTimeout(() => rechazar(<span class="tok-string">"Se agotó el tiempo"</span>), tiempo); }); }</pre> -<p><a class="p_ident" id="p-5b6JDzOqB2" href="#p-5b6JDzOqB2" tabindex="-1" role="presentation"></a>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 <code>withTimeout</code>. Si, por otro lado, el <code>setTimeout</code> se ejecuta primero, rechazando la promesa, se ignoran cualquier llamada posterior a resolve o reject.</p> +<p><a class="p_ident" id="p-851dYoRdgk" href="#p-851dYoRdgk" tabindex="-1" role="presentation"></a>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 <code>conTiempoDeEspera</code>. Si, por otro lado, el <code>setTimeout</code> se ejecuta primero, rechazando la promesa, se ignora cualquier llamada posterior de resolución o rechazo.</p> + +<p><a class="p_ident" id="p-aGXBBNJk0l" href="#p-aGXBBNJk0l" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-aGXBBNJk0l" href="#p-aGXBBNJk0l" tabindex="-1" role="presentation"></a>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 <code>for</code>, 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.</p> +<p><a class="p_ident" id="p-H9/JmKGCa9" href="#p-H9/JmKGCa9" tabindex="-1" role="presentation"></a>Como no puedes esperar una promesa dentro de un bucle <code>for</code>, 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-rKTBHF8p2l" href="#c-rKTBHF8p2l" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">crackPasscode</span>(<span class="tok-definition">networkID</span>) { - <span class="tok-keyword">function</span> <span class="tok-definition">nextDigit</span>(<span class="tok-definition">code</span>, <span class="tok-definition">digit</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">newCode</span> = code + digit; - <span class="tok-keyword">return</span> withTimeout(joinWifi(networkID, newCode), <span class="tok-number">50</span>) - .then(() => newCode) - .catch(<span class="tok-definition">failure</span> => { - <span class="tok-keyword">if</span> (failure == <span class="tok-string">"Timed out"</span>) { - <span class="tok-keyword">return</span> nextDigit(newCode, <span class="tok-number">0</span>); - } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (digit < <span class="tok-number">9</span>) { - <span class="tok-keyword">return</span> nextDigit(code, digit + <span class="tok-number">1</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-rS6USRJVfv" href="#c-rS6USRJVfv" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">crackearContraseña</span>(<span class="tok-definition">identificadorDeRed</span>) { + <span class="tok-keyword">function</span> <span class="tok-definition">siguienteDígito</span>(<span class="tok-definition">código</span>, <span class="tok-definition">dígito</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">nuevoCódigo</span> = código + dígito; + <span class="tok-keyword">return</span> conTiempoDeEspera(joinWifi(identificadorDeRed, nuevoCódigo), <span class="tok-number">50</span>) + .then(() => nuevoCódigo) + .catch(<span class="tok-definition">fallo</span> => { + <span class="tok-keyword">if</span> (fallo == <span class="tok-string">"Se agotó el tiempo"</span>) { + <span class="tok-keyword">return</span> siguienteDígito(nuevoCódigo, <span class="tok-number">0</span>); + } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (dígito < <span class="tok-number">9</span>) { + <span class="tok-keyword">return</span> siguienteDígito(código, dígito + <span class="tok-number">1</span>); } <span class="tok-keyword">else</span> { - <span class="tok-keyword">throw</span> failure; + <span class="tok-keyword">throw</span> fallo; } }); } - <span class="tok-keyword">return</span> nextDigit(<span class="tok-string">""</span>, <span class="tok-number">0</span>); + <span class="tok-keyword">return</span> siguienteDígito(<span class="tok-string">""</span>, <span class="tok-number">0</span>); }</pre> <p><a class="p_ident" id="p-7c/57tY3Ft" href="#p-7c/57tY3Ft" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-QKY6kJy25c" href="#c-QKY6kJy25c" tabindex="-1" role="presentation"></a>crackPasscode(<span class="tok-string">"HANGAR 2"</span>).then(console.log); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-LaAB7vFIXo" href="#c-LaAB7vFIXo" tabindex="-1" role="presentation"></a>crackearContraseña(<span class="tok-string">"HANGAR 2"</span>).then(console.log); <span class="tok-comment">// → 555555</span></pre> <p><a class="p_ident" id="p-KpmbzaGvuk" href="#p-KpmbzaGvuk" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-NGTlK4NQNX" href="#h-NGTlK4NQNX" tabindex="-1" role="presentation"></a>Funciones asíncronas</h2> -<p><a class="p_ident" id="p-1bcWQeAU8b" href="#p-1bcWQeAU8b" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-FbcOLp9fV4" href="#p-FbcOLp9fV4" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-JTBQPMRuqH" href="#p-JTBQPMRuqH" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-GZ6dLvPHxn" href="#p-GZ6dLvPHxn" tabindex="-1" role="presentation"></a>Lo que la función <code>crackearContraseña</code> 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.</p> -<p><a class="p_ident" id="p-G5SArOKa1V" href="#p-G5SArOKa1V" tabindex="-1" role="presentation"></a>La buena noticia es que JavaScript te permite escribir código pseudo-sincrónico para describir la computación asíncrona. Una función <code>async</code> es una función que implícitamente devuelve una promesa y que puede, en su cuerpo, <code>await</code> otras promesas de una manera que <em>parece</em> sincrónica.</p> +<p><a class="p_ident" id="p-G5SArOKa1V" href="#p-G5SArOKa1V" tabindex="-1" role="presentation"></a>La buena noticia es que JavaScript te permite escribir código pseudo-sincrónico para describir procedimientos asíncronos. Una función <code>async</code> es una función que implícitamente devuelve una promesa y que puede, en su cuerpo, esperar (<code>await</code>) otras promesas de una manera que <em>parece</em> sincrónica.</p> -<p><a class="p_ident" id="p-QE3XBITstL" href="#p-QE3XBITstL" tabindex="-1" role="presentation"></a>Podemos reescribir <code>crackPasscode</code> de la siguiente manera:</p> +<p><a class="p_ident" id="p-QE3XBITstL" href="#p-QE3XBITstL" tabindex="-1" role="presentation"></a>Podemos reescribir <code>crackearContraseña</code> de la siguiente manera:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-f8fODDM35B" href="#c-f8fODDM35B" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">crackPasscode</span>(<span class="tok-definition">networkID</span>) { - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">code</span> = <span class="tok-string">""</span>;;) { - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">digit</span> = <span class="tok-number">0</span>;; digit++) { - <span class="tok-keyword">let</span> <span class="tok-definition">newCode</span> = code + digit; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-WVlzd+NWdu" href="#c-WVlzd+NWdu" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">crackearContraseña</span>(<span class="tok-definition">identificadorDeRed</span>) { + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">código</span> = <span class="tok-string">""</span>;;) { + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">dígito</span> = <span class="tok-number">0</span>;; dígito++) { + <span class="tok-keyword">let</span> <span class="tok-definition">nuevoCódigo</span> = código + dígito; <span class="tok-keyword">try</span> { - <span class="tok-keyword">await</span> withTimeout(joinWifi(networkID, newCode), <span class="tok-number">50</span>); - <span class="tok-keyword">return</span> newCode; - } <span class="tok-keyword">catch</span> (<span class="tok-definition">failure</span>) { - <span class="tok-keyword">if</span> (failure == <span class="tok-string">"Timed out"</span>) { - code = newCode; + <span class="tok-keyword">await</span> withTimeout(joinWifi(identificadorDeRed, nuevoCódigo), <span class="tok-number">50</span>); + <span class="tok-keyword">return</span> nuevoCódigo; + } <span class="tok-keyword">catch</span> (<span class="tok-definition">fallo</span>) { + <span class="tok-keyword">if</span> (fallo == <span class="tok-string">"Se agotó el tiempo"</span>) { + código = nuevoCódigo; <span class="tok-keyword">break</span>; - } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (digit == <span class="tok-number">9</span>) { - <span class="tok-keyword">throw</span> failure; + } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (dígito == <span class="tok-number">9</span>) { + <span class="tok-keyword">throw</span> fallo; } } } } }</pre> -<p><a class="p_ident" id="p-+4mFixoWg7" href="#p-+4mFixoWg7" tabindex="-1" role="presentation"></a>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).</p> +<p><a class="p_ident" id="p-+4mFixoWg7" href="#p-+4mFixoWg7" tabindex="-1" role="presentation"></a>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).</p> -<p><a class="p_ident" id="p-MHiEBPHcq0" href="#p-MHiEBPHcq0" tabindex="-1" role="presentation"></a>Una función <code>async</code> está marcada con la palabra <code>async</code> antes de la palabra clave <code>function</code>. Los métodos también pueden ser marcados como <code>async</code> escribiendo <code>async</code> 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.</p> +<p><a class="p_ident" id="p-MHiEBPHcq0" href="#p-MHiEBPHcq0" tabindex="-1" role="presentation"></a>Una función <code>async</code> está marcada con la palabra <code>async</code> antes de la palabra clave <code>function</code>. Los métodos también se pueden marcar como <code>async</code> escribiendo <code>async</code> 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.</p> <p><a class="p_ident" id="p-GXfnmGh9zs" href="#p-GXfnmGh9zs" tabindex="-1" role="presentation"></a>Dentro de una función <code>async</code>, la palabra <code>await</code> 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 <code>await</code>.</p> -<p><a class="p_ident" id="p-o9TSZxwXki" href="#p-o9TSZxwXki" tabindex="-1" role="presentation"></a>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 <em>congelada</em> en cualquier punto que tenga un <code>await</code>, y puede continuar más tarde.</p> +<p><a class="p_ident" id="p-o9TSZxwXki" href="#p-o9TSZxwXki" tabindex="-1" role="presentation"></a>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 <em>congelada</em> en cualquier punto que tenga un <code>await</code>, y continuar más tarde.</p> -<p><a class="p_ident" id="p-YwhF0rS3qb" href="#p-YwhF0rS3qb" tabindex="-1" role="presentation"></a>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 <code>async</code> suelen ser más agradables de escribir que encadenar llamadas <code>then</code>.</p> +<p><a class="p_ident" id="p-mZrEY5AcpL" href="#p-mZrEY5AcpL" tabindex="-1" role="presentation"></a>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 <code>async</code> suelen ser más agradables de escribir que encadenar llamadas a <code>then</code>.</p> <h2 id="generator"><a class="h_ident" id="h-LGLwBM7N7Q" href="#h-LGLwBM7N7Q" tabindex="-1" role="presentation"></a>Generadores</h2> -<p><a class="p_ident" id="p-vucanJZjN2" href="#p-vucanJZjN2" tabindex="-1" role="presentation"></a>Esta capacidad de pausar y luego reanudar funciones no es exclusiva de las funciones <code>async</code>. JavaScript también tiene una característica llamada <em>generador</em> functions. Son similares, pero sin las promesas.</p> +<p><a class="p_ident" id="p-vucanJZjN2" href="#p-vucanJZjN2" tabindex="-1" role="presentation"></a>Esta capacidad de pausar y luego reanudar funciones no es exclusiva de las funciones <code>async</code>. JavaScript también tiene una característica llamada funciones generadoras (<em>generator functions</em>). Estas son parecidas a las funciones <code>async</code>, pero sin las promesas.</p> -<p><a class="p_ident" id="p-KhGB+fXocF" href="#p-KhGB+fXocF" tabindex="-1" role="presentation"></a>Cuando defines una función con <code>function*</code> (colocando un asterisco después de la palabra <code>function</code>), se convierte en un generador. Al llamar a un generador, devuelve un iterador, que ya vimos en el <a href="06_object.html">Capítulo 6</a>.</p> +<p><a class="p_ident" id="p-KhGB+fXocF" href="#p-KhGB+fXocF" tabindex="-1" role="presentation"></a>Cuando defines una función con <code>function*</code> (colocando un asterisco después de la palabra <code>function</code>), se convierte en un generador. Al llamar a un generador, este devuelve un iterador, que ya estudiamos en el <a href="06_object.html">Capítulo 6</a>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-B4ek89g871" href="#c-B4ek89g871" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span><span class="tok-keyword">*</span> <span class="tok-definition">powers</span>(<span class="tok-definition">n</span>) { - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">current</span> = n;; current *= n) { - <span class="tok-keyword">yield</span> current; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-+/YWo6RHLX" href="#c-+/YWo6RHLX" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span><span class="tok-keyword">*</span> <span class="tok-definition">potencias</span>(<span class="tok-definition">n</span>) { + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">actual</span> = n;; actual *= n) { + <span class="tok-keyword">yield</span> actual; } } -<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">power</span> <span class="tok-keyword">of</span> powers(<span class="tok-number">3</span>)) { - <span class="tok-keyword">if</span> (power > <span class="tok-number">50</span>) <span class="tok-keyword">break</span>; - console.log(power); +<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">potencia</span> <span class="tok-keyword">of</span> potencias(<span class="tok-number">3</span>)) { + <span class="tok-keyword">if</span> (potencia > <span class="tok-number">50</span>) <span class="tok-keyword">break</span>; + console.log(potencia); } <span class="tok-comment">// → 3</span> <span class="tok-comment">// → 9</span> <span class="tok-comment">// → 27</span></pre> -<p><a class="p_ident" id="p-aD9V5e562+" href="#p-aD9V5e562+" tabindex="-1" role="presentation"></a>Inicialmente, al llamar a <code>powers</code>, la función se congela desde el principio. Cada vez que llamas a <code>next</code> en el iterador, la función se ejecuta hasta que encuentra una expresión <code>yield</code>, 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.</p> +<p><a class="p_ident" id="p-aD9V5e562+" href="#p-aD9V5e562+" tabindex="-1" role="presentation"></a>Inicialmente, al llamar a <code>potencias</code>, la función se congela desde el principio. Cada vez que llamas a <code>next</code> en el iterador, la función se ejecuta hasta que encuentra una expresión <code>yield</code>, 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.</p> <p><a class="p_ident" id="p-+edzRQNWn2" href="#p-+edzRQNWn2" tabindex="-1" role="presentation"></a>Escribir iteradores a menudo es mucho más fácil cuando usas funciones generadoras. El iterador para la clase <code>Group</code> (del ejercicio en el <a href="06_object.html#group_iterator">Capítulo 6</a>) se puede escribir con este generador:</p> @@ -313,30 +320,30 @@ <h2 id="generator"><a class="h_ident" id="h-LGLwBM7N7Q" href="#h-LGLwBM7N7Q" tab <p><a class="p_ident" id="p-C276cu76si" href="#p-C276cu76si" tabindex="-1" role="presentation"></a>Tales expresiones <code>yield</code> 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 <em>local</em> y la posición donde hizo el yield.</p> -<p><a class="p_ident" id="p-+kfgMm93qQ" href="#p-+kfgMm93qQ" tabindex="-1" role="presentation"></a>Una función <code>async</code> 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 <code>await</code>.</p> +<p><a class="p_ident" id="p-+kfgMm93qQ" href="#p-+kfgMm93qQ" tabindex="-1" role="presentation"></a>Una función <code>async</code> 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 <code>await</code>), el resultado de esa promesa (el valor o la excepción generada) es el resultado de la expresión <code>await</code>.</p> -<h2><a class="h_ident" id="h-0q8YSKeCAX" href="#h-0q8YSKeCAX" tabindex="-1" role="presentation"></a>Un Proyecto de Arte de Corvidos</h2> +<h2><a class="h_ident" id="h-8PK3DJlJpe" href="#h-8PK3DJlJpe" tabindex="-1" role="presentation"></a>Un Proyecto de Arte de Córvidos</h2> <p><a class="p_ident" id="p-EIAKJkXc8j" href="#p-EIAKJkXc8j" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-7+FP7NBriA" href="#p-7+FP7NBriA" tabindex="-1" role="presentation"></a>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”.</p> +<p><a class="p_ident" id="p-yOmEX58AMX" href="#p-yOmEX58AMX" tabindex="-1" role="presentation"></a>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”.</p> -<p><a class="p_ident" id="p-yBNrkTbIgn" href="#p-yBNrkTbIgn" tabindex="-1" role="presentation"></a>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?</p> +<p><a class="p_ident" id="p-yBNrkTbIgn" href="#p-yBNrkTbIgn" tabindex="-1" role="presentation"></a>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?</p> -<p><a class="p_ident" id="p-4e69YlbBkR" href="#p-4e69YlbBkR" tabindex="-1" role="presentation"></a>Cada dispositivo en una red recibe una <em>dirección IP</em>, que otros dispositivos pueden usar para enviarle mensajes. Hablamos más sobre eso en el <a href="13_browser.html">Capítulo 13</a>. Carla nota que sus propios teléfonos reciben direcciones como <code>10.0.0.20</code> o <code>10.0.0.33</code>. 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.</p> +<p><a class="p_ident" id="p-4e69YlbBkR" href="#p-4e69YlbBkR" tabindex="-1" role="presentation"></a>Cada dispositivo en una red recibe una <em>dirección IP</em>, que otros dispositivos pueden usar para enviarle mensajes. Hablaremos más sobre eso en el <a href="13_browser.html">Capítulo 13</a>. Carla se da cuenta que sus propios teléfonos reciben direcciones como <code>10.0.0.20</code> o <code>10.0.0.33</code>. 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.</p> <p><a class="p_ident" id="p-nKIobazOIw" href="#p-nKIobazOIw" tabindex="-1" role="presentation"></a>El <a href="18_http.html">Capítulo 18</a> muestra cómo hacer solicitudes reales en redes reales. En este capítulo, usaremos una función ficticia simplificada llamada <code>request</code> 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.</p> -<p><a class="p_ident" id="p-3j7VheCqjp" href="#p-3j7VheCqjp" tabindex="-1" role="presentation"></a>Según el manual, puedes cambiar lo que se muestra en una señal SIG-5030 enviándole un mensaje con contenido como <code>{"command": "display", "data": [0, 0, 3, …]}</code>, donde <code>data</code> 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.</p> +<p><a class="p_ident" id="p-3j7VheCqjp" href="#p-3j7VheCqjp" tabindex="-1" role="presentation"></a>Según el manual, puedes cambiar lo que se muestra en una señal SIG-5030 enviándole un mensaje con contenido como <code>{"command": "display", "data": [0, 0, 3, …]}</code>, donde <code>data</code> 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.</p> <p><a class="p_ident" id="p-Id2shPCIz0" href="#p-Id2shPCIz0" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-2FC1PT/yQn" href="#c-2FC1PT/yQn" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">addr</span> = <span class="tok-number">1</span>; addr < <span class="tok-number">256</span>; addr++) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-6rc6x2fPE6" href="#c-6rc6x2fPE6" tabindex="-1" role="presentation"></a><span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">dir</span> = <span class="tok-number">1</span>; dir < <span class="tok-number">256</span>; dir++) { <span class="tok-keyword">let</span> <span class="tok-definition">data</span> = []; <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">n</span> = <span class="tok-number">0</span>; n < <span class="tok-number">1500</span>; n++) { - data.push(n < addr ? <span class="tok-number">3</span> : <span class="tok-number">0</span>); + data.push(n < dir ? <span class="tok-number">3</span> : <span class="tok-number">0</span>); } - <span class="tok-keyword">let</span> <span class="tok-definition">ip</span> = <span class="tok-string2">`10.0.0.</span>${addr}<span class="tok-string2">`</span>; + <span class="tok-keyword">let</span> <span class="tok-definition">ip</span> = <span class="tok-string2">`10.0.0.</span>${dir}<span class="tok-string2">`</span>; request(ip, {<span class="tok-definition">command</span>: <span class="tok-string">"display"</span>, <span class="tok-definition">data</span>}) .then(() => console.log(<span class="tok-string2">`Solicitud a </span>${ip}<span class="tok-string2"> aceptada`</span>)) .catch(() => {}); @@ -346,77 +353,82 @@ <h2><a class="h_ident" id="h-0q8YSKeCAX" href="#h-0q8YSKeCAX" tabindex="-1" role <p><a class="p_ident" id="p-XT02BFndLm" href="#p-XT02BFndLm" tabindex="-1" role="presentation"></a>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:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-YxTIbNKE+b" href="#c-YxTIbNKE+b" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">screenAddresses</span> = [ +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-0xZYyiiOuj" href="#c-0xZYyiiOuj" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">direccionesPantalla</span> = [ <span class="tok-string">"10.0.0.44"</span>, <span class="tok-string">"10.0.0.45"</span>, <span class="tok-string">"10.0.0.41"</span>, <span class="tok-string">"10.0.0.31"</span>, <span class="tok-string">"10.0.0.40"</span>, <span class="tok-string">"10.0.0.42"</span>, <span class="tok-string">"10.0.0.48"</span>, <span class="tok-string">"10.0.0.47"</span>, <span class="tok-string">"10.0.0.46"</span> ];</pre> -<p><a class="p_ident" id="p-t3lXdYG6kN" href="#p-t3lXdYG6kN" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-4xFvLyVJ47" href="#p-4xFvLyVJ47" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-y5Q2eMHG15" href="#p-y5Q2eMHG15" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-y5Q2eMHG15" href="#p-y5Q2eMHG15" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-y6uk8qBvhT" href="#p-y6uk8qBvhT" tabindex="-1" role="presentation"></a>Ella ha configurado la variable <code>clipImages</code> 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.</p> +<p><a class="p_ident" id="p-ikDdJwR2zo" href="#p-ikDdJwR2zo" tabindex="-1" role="presentation"></a>Ha configurado la variable <code>imágenesVídeo</code> 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.</p> -<p><a class="p_ident" id="p-KvXTg6wDLF" href="#p-KvXTg6wDLF" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-KvXTg6wDLF" href="#p-KvXTg6wDLF" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-YW17AqPsQ1" href="#p-YW17AqPsQ1" tabindex="-1" role="presentation"></a><code>Promise</code> tiene un método estático <code>all</code> 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).</p> +<p><a class="p_ident" id="p-YW17AqPsQ1" href="#p-YW17AqPsQ1" tabindex="-1" role="presentation"></a><code>Promise</code> tiene un método estático <code>all</code> 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).</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-IQcpgoIvEH" href="#c-IQcpgoIvEH" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">displayFrame</span>(<span class="tok-definition">frame</span>) { - <span class="tok-keyword">return</span> Promise.all(frame.map((<span class="tok-definition">data</span>, <span class="tok-definition">i</span>) => { - <span class="tok-keyword">return</span> request(screenAddresses[i], { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-83G/xzS9mc" href="#c-83G/xzS9mc" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">mostrarFotograma</span>(<span class="tok-definition">fotograma</span>) { + <span class="tok-keyword">return</span> Promise.all(fotograma.map((<span class="tok-definition">data</span>, <span class="tok-definition">i</span>) => { + <span class="tok-keyword">return</span> request(direccionesPantalla[i], { <span class="tok-definition">command</span>: <span class="tok-string">"display"</span>, <span class="tok-definition">data</span> }); })); }</pre> -<p><a class="p_ident" id="p-FPLMeF12gv" href="#p-FPLMeF12gv" tabindex="-1" role="presentation"></a>Esto recorre las imágenes en <code>frame</code> (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.</p> +<p><a class="p_ident" id="p-FPLMeF12gv" href="#p-FPLMeF12gv" tabindex="-1" role="presentation"></a>Esto recorre las imágenes en <code>fotograma</code> (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.</p> -<p><a class="p_ident" id="p-toeS8qCaQC" href="#p-toeS8qCaQC" tabindex="-1" role="presentation"></a>Para poder detener un video en reproducción, el proceso está envuelto en una clase. Esta clase tiene un método asíncrono <code>play</code> que devuelve una promesa que solo se resuelve cuando la reproducción se detiene de nuevo a través del método <code>stop</code>.</p> +<p><a class="p_ident" id="p-HlRvWcaLgh" href="#p-HlRvWcaLgh" tabindex="-1" role="presentation"></a>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 <code>reproducir</code> que devuelve una promesa que solo se resuelve cuando la reproducción se detiene a través del método <code>parar</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9CWs6kw1Nf" href="#c-9CWs6kw1Nf" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">wait</span>(<span class="tok-definition">time</span>) { - <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise(<span class="tok-definition">accept</span> => setTimeout(accept, time)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-jhZtnszLIr" href="#c-jhZtnszLIr" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">espera</span>(<span class="tok-definition">tiempo</span>) { + <span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise(<span class="tok-definition">aceptar</span> => setTimeout(aceptar, tiempo)); } -<span class="tok-keyword">class</span> VideoPlayer { - <span class="tok-definition">constructor</span>(<span class="tok-definition">frames</span>, <span class="tok-definition">frameTime</span>) { - <span class="tok-keyword">this</span>.frames = frames; - <span class="tok-keyword">this</span>.frameTime = frameTime; - <span class="tok-keyword">this</span>.stopped = true; +<span class="tok-keyword">class</span> ReproductorVídeo { + <span class="tok-definition">constructor</span>(<span class="tok-definition">fotogramas</span>, <span class="tok-definition">tiempoFotograma</span>) { + <span class="tok-keyword">this</span>.fotogramas = fotogramas; + <span class="tok-keyword">this</span>.tiempoFotograma = tiempoFotograma; + <span class="tok-keyword">this</span>.parado = true; } - <span class="tok-keyword">async</span> <span class="tok-definition">play</span>() { - <span class="tok-keyword">this</span>.stopped = false; - <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = <span class="tok-number">0</span>; !<span class="tok-keyword">this</span>.stopped; i++) { - <span class="tok-keyword">let</span> <span class="tok-definition">nextFrame</span> = wait(<span class="tok-keyword">this</span>.frameTime); - <span class="tok-keyword">await</span> displayFrame(<span class="tok-keyword">this</span>.frames[i % <span class="tok-keyword">this</span>.frames.length]); - <span class="tok-keyword">await</span> nextFrame; + <span class="tok-keyword">async</span> <span class="tok-definition">reproducir</span>() { + <span class="tok-keyword">this</span>.parado = false; + <span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">i</span> = <span class="tok-number">0</span>; !<span class="tok-keyword">this</span>.parado; i++) { + <span class="tok-keyword">let</span> <span class="tok-definition">siguienteFotograma</span> = espera(<span class="tok-keyword">this</span>.tiempoFotograma); + <span class="tok-keyword">await</span> mostrarFotograma(<span class="tok-keyword">this</span>.fotogramas[i % <span class="tok-keyword">this</span>.fotogramas.length]); + <span class="tok-keyword">await</span> siguienteFotograma; } } - <span class="tok-definition">stop</span>() { - <span class="tok-keyword">this</span>.stopped = true; + <span class="tok-definition">parar</span>() { + <span class="tok-keyword">this</span>.parado = true; } }</pre> -<p><a class="p_ident" id="p-wKK2/D6QRW" href="#p-wKK2/D6QRW" tabindex="-1" role="presentation"></a>La función <code>wait</code> envuelve <code>setTimeout</code> en una promesa que se resuelve después del número de milisegundos especificado. Esto es útil para controlar la velocidad de reproducción.</p> +<p><a class="p_ident" id="p-wKK2/D6QRW" href="#p-wKK2/D6QRW" tabindex="-1" role="presentation"></a>La función <code>espera</code> envuelve <code>setTimeout</code> en una promesa que se resuelve después del número de milisegundos especificado. Esto es útil para controlar la velocidad de reproducción.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-WIv+1JofkY" href="#c-WIv+1JofkY" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">video</span> = <span class="tok-keyword">new</span> VideoPlayer(clipImages, <span class="tok-number">100</span>); -video.play().catch(<span class="tok-definition">e</span> => { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-GrUGoEJqTM" href="#c-GrUGoEJqTM" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">vídeo</span> = <span class="tok-keyword">new</span> ReproductorVídeo(imágenesVídeo, <span class="tok-number">100</span>); +vídeo.reproducir().catch(<span class="tok-definition">e</span> => { console.log(<span class="tok-string">"La reproducción falló: "</span> + e); }); -setTimeout(() => video.stop(), <span class="tok-number">15000</span>);</pre> +setTimeout(() => vídeo.parar(), <span class="tok-number">15000</span>);</pre> -<p><a class="p_ident" id="p-Yx3D6CTz+N" href="#p-Yx3D6CTz+N" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-gizPvmLjFs" href="#p-gizPvmLjFs" tabindex="-1" role="presentation"></a>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.</p> <h2><a class="h_ident" id="h-iSkjGslyNf" href="#h-iSkjGslyNf" tabindex="-1" role="presentation"></a>El bucle de eventos</h2> -<p><a class="p_ident" id="p-uqFuigR5hW" href="#p-uqFuigR5hW" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-cV+8xco+RZ" href="#p-cV+8xco+RZ" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-RRuVVhfUCF" href="#p-RRuVVhfUCF" tabindex="-1" role="presentation"></a>Por lo tanto, las devoluciones de llamada no son llamadas directamente por el código que las programó. Si llamo a <code>setTimeout</code> 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ó.</p> +<p><a class="p_ident" id="p-RRuVVhfUCF" href="#p-RRuVVhfUCF" tabindex="-1" role="presentation"></a>Por lo tanto, las funciones de callback no son llamadas directamente por el código que las programó. Si llamo a <code>setTimeout</code> 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 <code>setTimeout</code>. Y cuando la función de callback retorna, el control no vuelve a la función que lo programó.</p> -<p><a class="p_ident" id="p-ekUlWC/PZ3" href="#p-ekUlWC/PZ3" tabindex="-1" role="presentation"></a>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 <code>catch</code> no estarán en la pila cuando lancen una excepción.</p> +<p><a class="p_ident" id="p-dkmkAWYURU" href="#p-dkmkAWYURU" tabindex="-1" role="presentation"></a>El comportamiento asíncrono ocurre en su propia pila de llamadas vacía.</p> + +<div class="translator-note"><p><strong>N. del T.:</strong> 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.</p> +</div> + +<p><a class="p_ident" id="p-cspiD/byXw" href="#p-cspiD/byXw" tabindex="-1" role="presentation"></a>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 <code>catch</code> no estarán en la pila cuando lancen una excepción.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-RZ/XhGQYYZ" href="#c-RZ/XhGQYYZ" tabindex="-1" role="presentation"></a><span class="tok-keyword">try</span> { setTimeout(() => { @@ -427,20 +439,20 @@ <h2><a class="h_ident" id="h-iSkjGslyNf" href="#h-iSkjGslyNf" tabindex="-1" role console.log(<span class="tok-string">"Atrapado"</span>, e); }</pre> -<p><a class="p_ident" id="p-joSObRYrFp" href="#p-joSObRYrFp" tabindex="-1" role="presentation"></a>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 <em>alrededor</em> de tu programa, llamado el <em>bucle de eventos</em>. 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.</p> +<p><a class="p_ident" id="p-joSObRYrFp" href="#p-joSObRYrFp" tabindex="-1" role="presentation"></a>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 <em>bucle de eventos</em>, que se ejecuta <em>alrededor</em> 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.</p> -<p><a class="p_ident" id="p-5ZkjsY7Yeu" href="#p-5ZkjsY7Yeu" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-UiF6wXkNgm" href="#p-UiF6wXkNgm" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Avc3WZx6ck" href="#c-Avc3WZx6ck" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">start</span> = Date.now(); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-6aSENtec1Q" href="#c-6aSENtec1Q" tabindex="-1" role="presentation"></a><span class="tok-keyword">let</span> <span class="tok-definition">comienzo</span> = Date.now(); setTimeout(() => { - console.log(<span class="tok-string">"El tiempo de espera se ejecutó en"</span>, Date.now() - start); + console.log(<span class="tok-string">"El tiempo de espera se ejecutó en"</span>, Date.now() - comienzo); }, <span class="tok-number">20</span>); -<span class="tok-keyword">while</span> (Date.now() < start + <span class="tok-number">50</span>) {} -console.log(<span class="tok-string">"Tiempo perdido hasta"</span>, Date.now() - start); +<span class="tok-keyword">while</span> (Date.now() < comienzo + <span class="tok-number">50</span>) {} +console.log(<span class="tok-string">"Tiempo perdido hasta"</span>, Date.now() - comienzo); <span class="tok-comment">// → Tiempo perdido hasta 50</span> <span class="tok-comment">// → El tiempo de espera se ejecutó en 55</span></pre> -<p><a class="p_ident" id="p-rhhrRkqin4" href="#p-rhhrRkqin4" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-rhhrRkqin4" href="#p-rhhrRkqin4" tabindex="-1" role="presentation"></a>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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-CpYlSjFfYU" href="#c-CpYlSjFfYU" tabindex="-1" role="presentation"></a>Promise.resolve(<span class="tok-string">"Hecho"</span>).then(console.log); console.log(<span class="tok-string">"¡Yo primero!"</span>); @@ -449,59 +461,61 @@ <h2><a class="h_ident" id="h-iSkjGslyNf" href="#h-iSkjGslyNf" tabindex="-1" role <p><a class="p_ident" id="p-qwnR1hGJw8" href="#p-qwnR1hGJw8" tabindex="-1" role="presentation"></a>En capítulos posteriores veremos varios tipos de eventos que se ejecutan en el bucle de eventos.</p> -<h2><a class="h_ident" id="h-5F87Tj7sc7" href="#h-5F87Tj7sc7" tabindex="-1" role="presentation"></a>Errores asincrónicos</h2> +<h2><a class="h_ident" id="h-SvhK10Vq/a" href="#h-SvhK10Vq/a" tabindex="-1" role="presentation"></a>Errores asíncronos</h2> -<p><a class="p_ident" id="p-DJqLCQdudP" href="#p-DJqLCQdudP" tabindex="-1" role="presentation"></a>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 <em>brechas</em> en su ejecución durante las cuales otro código puede correr.</p> +<p><a class="p_ident" id="p-DJqLCQdudP" href="#p-DJqLCQdudP" tabindex="-1" role="presentation"></a>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 <em>brechas</em> en su ejecución durante las cuales otro código puede correr.</p> -<p><a class="p_ident" id="p-1YB4rmos4T" href="#p-1YB4rmos4T" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-RqcPRXTj8I" href="#p-RqcPRXTj8I" tabindex="-1" role="presentation"></a>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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-SNIwqfk7T3" href="#c-SNIwqfk7T3" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">fileSizes</span>(<span class="tok-definition">files</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ZmNSWd6V9T" href="#c-ZmNSWd6V9T" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">tamañosArchivos</span>(<span class="tok-definition">archivos</span>) { <span class="tok-keyword">let</span> <span class="tok-definition">lista</span> = <span class="tok-string">""</span>; - <span class="tok-keyword">await</span> Promise.all(files.map(<span class="tok-keyword">async</span> <span class="tok-definition">fileName</span> => { - lista += fileName + <span class="tok-string">": "</span> + - (<span class="tok-keyword">await</span> textFile(fileName)).length + <span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>; + <span class="tok-keyword">await</span> Promise.all(archivos.map(<span class="tok-keyword">async</span> <span class="tok-definition">nombreArchivo</span> => { + lista += nombreArchivo + <span class="tok-string">": "</span> + + (<span class="tok-keyword">await</span> archivoTexto(nombreArchivo)).length + <span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>; })); <span class="tok-keyword">return</span> lista; }</pre> -<p><a class="p_ident" id="p-HiAwDFFOUL" href="#p-HiAwDFFOUL" tabindex="-1" role="presentation"></a>La parte <code>async fileName =></code> muestra cómo también se pueden hacer arrow functions <code>async</code> colocando la palabra <code>async</code> delante de ellas.</p> +<p><a class="p_ident" id="p-HiAwDFFOUL" href="#p-HiAwDFFOUL" tabindex="-1" role="presentation"></a>La parte <code>async nombreArchivo =></code> muestra cómo también se pueden hacer arrow functions <code>async</code> (funciones flecha asíncronas) colocando la palabra <code>async</code> delante de ellas.</p> -<p><a class="p_ident" id="p-pPIiTEafT4" href="#p-pPIiTEafT4" tabindex="-1" role="presentation"></a>El código no parece ser sospechoso de inmediato... mapea la función flecha <code>async</code> sobre el arreglo de nombres, creando un arreglo de promesas, y luego usa <code>Promise.all</code> para esperar a todas ellas antes de devolver la lista que construyen.</p> +<p><a class="p_ident" id="p-pPIiTEafT4" href="#p-pPIiTEafT4" tabindex="-1" role="presentation"></a>El código no parece sospechoso de inmediato... mapea la función flecha <code>async</code> sobre el array de nombres, creando un array de promesas, y luego usa <code>Promise.all</code> para esperar a todas ellas antes de devolver la lista que construyen.</p> -<p><a class="p_ident" id="p-exvEa8QEiP" href="#p-exvEa8QEiP" tabindex="-1" role="presentation"></a>Pero está totalmente roto. Siempre devolverá solo una línea de salida, enumerando el archivo que tardó más en leer.</p> +<p><a class="p_ident" id="p-PrXuYzRu5U" href="#p-PrXuYzRu5U" tabindex="-1" role="presentation"></a>Sin embargo, el programa está totalmente roto. Siempre devolverá solo una línea de salida, enumerando el archivo que tardó más en leer.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-KqsJmHqBh2" href="#c-KqsJmHqBh2" tabindex="-1" role="presentation"></a>fileSizes([<span class="tok-string">"plans.txt"</span>, <span class="tok-string">"shopping_list.txt"</span>]) +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Y4YY7I2xCH" href="#c-Y4YY7I2xCH" tabindex="-1" role="presentation"></a>tamañosArchivos([<span class="tok-string">"planes.txt"</span>, <span class="tok-string">"lista_compra.txt"</span>]) .then(console.log);</pre> <p><a class="p_ident" id="p-s9Q+INoyNl" href="#p-s9Q+INoyNl" tabindex="-1" role="presentation"></a>¿Puedes descubrir por qué?</p> <p><a class="p_ident" id="p-GBG4on0Rh9" href="#p-GBG4on0Rh9" tabindex="-1" role="presentation"></a>El problema radica en el operador <code>+=</code>, que toma el valor <em>actual</em> de <code>lista</code> en el momento en que comienza a ejecutarse la instrucción y luego, cuando el <code>await</code> termina, establece el enlace <code>lista</code> como ese valor más la cadena agregada.</p> -<p><a class="p_ident" id="p-gKQGh439Yc" href="#p-gKQGh439Yc" tabindex="-1" role="presentation"></a>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 <code>map</code> se ejecuta antes de que se agregue cualquier cosa a la lista, por lo que cada uno de los operadores <code>+=</code> comienza desde una cadena vacía y termina, cuando termina su recuperación de almacenamiento, estableciendo <code>lista</code> en el resultado de agregar su línea a la cadena vacía.</p> +<p><a class="p_ident" id="p-gKQGh439Yc" href="#p-gKQGh439Yc" tabindex="-1" role="presentation"></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 <code>map</code> se ejecuta antes de que se agregue cualquier cosa a la lista, por lo que cada uno de los operadores <code>+=</code> comienza desde una cadena vacía y acaba, cuando recupera la información del almacenamiento, estableciendo <code>lista</code> en el resultado de agregar su línea a la cadena vacía.</p> -<p><a class="p_ident" id="p-MBjCCi/2ci" href="#p-MBjCCi/2ci" tabindex="-1" role="presentation"></a>Esto podría haberse evitado fácilmente devolviendo las líneas de las promesas mapeadas y llamando a <code>join</code> en el resultado de <code>Promise.all</code>, en lugar de construir la lista cambiando un enlace. Como suele ser, calcular nuevos valores es menos propenso a errores que cambiar valores existentes.</p> +<p><a class="p_ident" id="p-MBjCCi/2ci" href="#p-MBjCCi/2ci" tabindex="-1" role="presentation"></a>Esto podría haberse evitado fácilmente devolviendo las líneas de las promesas mapeadas y llamando a <code>join</code> en el resultado de <code>Promise.all</code>, en lugar de construir la lista cambiando una variable. Como de costumbre, calcular nuevos valores es menos propenso a errores que cambiar valores existentes.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9cdVzSKas5" href="#c-9cdVzSKas5" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">fileSizes</span>(<span class="tok-definition">files</span>) { - <span class="tok-keyword">let</span> <span class="tok-definition">líneas</span> = files.map(<span class="tok-keyword">async</span> <span class="tok-definition">fileName</span> => { - <span class="tok-keyword">return</span> fileName + <span class="tok-string">": "</span> + - (<span class="tok-keyword">await</span> textFile(fileName)).length; +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-lJOKU1AENO" href="#c-lJOKU1AENO" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">tamañosArchivos</span>(<span class="tok-definition">archivos</span>) { + <span class="tok-keyword">let</span> <span class="tok-definition">líneas</span> = archivos.map(<span class="tok-keyword">async</span> <span class="tok-definition">nombreArchivo</span> => { + <span class="tok-keyword">return</span> nombreArchivo + <span class="tok-string">": "</span> + + (<span class="tok-keyword">await</span> archivoTexto(nombreArchivo)).length; }); <span class="tok-keyword">return</span> (<span class="tok-keyword">await</span> Promise.all(líneas)).join(<span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>); }</pre> -<p><a class="p_ident" id="p-D5OZ7eAJk7" href="#p-D5OZ7eAJk7" tabindex="-1" role="presentation"></a>Errores como este son fáciles de cometer, especialmente al usar <code>await</code>, y debes ser consciente de dónde ocurren las brechas en tu código. Una ventaja de la asincronía <em>explícita</em> de JavaScript (ya sea a través de devoluciones de llamada, promesas o <code>await</code>) es que identificar estas brechas es relativamente fácil.</p> +<p><a class="p_ident" id="p-D5OZ7eAJk7" href="#p-D5OZ7eAJk7" tabindex="-1" role="presentation"></a>Errores como este son fáciles de cometer, especialmente al usar <code>await</code>, y debes ser consciente de dónde ocurren las brechas en tu código. Una ventaja de la asincronía <em>explícita</em> de JavaScript (ya sea a través de callbacks, promesas o <code>await</code>) es que identificar estas brechas es relativamente fácil.</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-/tyI2XiFVY" href="#p-/tyI2XiFVY" tabindex="-1" role="presentation"></a>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 <code>async</code>, que te permiten escribir un programa asíncrono como si fuera sincrónico.</p> +<p><a class="p_ident" id="p-cNHsSzr2ax" href="#p-cNHsSzr2ax" tabindex="-1" role="presentation"></a>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.</p> + +<p><a class="p_ident" id="p-/tyI2XiFVY" href="#p-/tyI2XiFVY" tabindex="-1" role="presentation"></a>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 <code>async</code>, que te permiten escribir un programa asíncrono como si fuera sincrónico.</p> <h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2> <h3><a class="i_ident" id="i-+uVmJAkWrn" href="#i-+uVmJAkWrn" tabindex="-1" role="presentation"></a>Momentos de tranquilidad</h3> -<p><a class="p_ident" id="p-6ZL6bHpqZH" href="#p-6ZL6bHpqZH" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-C4ZGbkY1j7" href="#p-C4ZGbkY1j7" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-0yy81tIz2h" href="#p-0yy81tIz2h" tabindex="-1" role="presentation"></a>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 <code>Date.now()</code>).</p> +<p><a class="p_ident" id="p-UQByW5u5se" href="#p-UQByW5u5se" tabindex="-1" role="presentation"></a>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 <code>Date.now()</code>).</p> <pre class="snippet" data-language="null" ><a class="c_ident" id="c-oYpYSTh/9y" href="#c-oYpYSTh/9y" tabindex="-1" role="presentation"></a>1695709940692 1695701068331 @@ -511,11 +525,11 @@ <h3><a class="i_ident" id="i-+uVmJAkWrn" href="#i-+uVmJAkWrn" tabindex="-1" role <p><a class="p_ident" id="p-nq7jr1mWcA" href="#p-nq7jr1mWcA" tabindex="-1" role="presentation"></a>La función <code>activityGraph</code>, proporcionada por el sandbox, resume dicha tabla en una cadena.</p> -<p><a class="p_ident" id="p-4EjrIsWVmx" href="#p-4EjrIsWVmx" tabindex="-1" role="presentation"></a>Utiliza la función <code>textFile</code> definida anteriormente, que al recibir un nombre de archivo devuelve una promesa que se resuelve en el contenido del archivo. Recuerda que <code>new Date(marcaDeTiempo)</code> crea un objeto <code>Date</code> para ese momento, que tiene métodos <code>getDay</code> y <code>getHours</code> que devuelven el día de la semana y la hora del día.</p> +<p><a class="p_ident" id="p-4EjrIsWVmx" href="#p-4EjrIsWVmx" tabindex="-1" role="presentation"></a>Utiliza la función <code>textFile</code> ( o <code>archivoTexto</code>) definida anteriormente, que al recibir un nombre de archivo devuelve una promesa que se resuelve en el contenido del archivo. Recuerda que <code>new Date(marcaDeTiempo)</code> crea un objeto <code>Date</code> para ese momento, que tiene métodos <code>getDay</code> y <code>getHours</code> que devuelven el día de la semana y la hora del día.</p> -<p><a class="p_ident" id="p-oVU7j47Kad" href="#p-oVU7j47Kad" tabindex="-1" role="presentation"></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 (<code>"\n"</code>).</p> +<p><a class="p_ident" id="p-oVU7j47Kad" href="#p-oVU7j47Kad" tabindex="-1" role="presentation"></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 (<code>"\n"</code>).</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-lwkjHlEwNf" href="#c-lwkjHlEwNf" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">day</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-AjUhoPZKxz" href="#c-AjUhoPZKxz" tabindex="-1" role="presentation"></a><span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">día</span>) { <span class="tok-keyword">let</span> <span class="tok-definition">logFileList</span> = <span class="tok-keyword">await</span> textFile(<span class="tok-string">"camera_logs.txt"</span>); <span class="tok-comment">// Tu código aquí</span> } @@ -525,59 +539,59 @@ <h3><a class="i_ident" id="i-+uVmJAkWrn" href="#i-+uVmJAkWrn" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-jZ7OykMDrT" href="#p-jZ7OykMDrT" tabindex="-1" role="presentation"></a>Necesitarás convertir el contenido de estos archivos en un array. La forma más fácil de hacerlo es utilizando el método <code>split</code> en la cadena producida por <code>textFile</code>. 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 <code>new Date</code>.</p> +<p><a class="p_ident" id="p-jZ7OykMDrT" href="#p-jZ7OykMDrT" tabindex="-1" role="presentation"></a>Necesitarás convertir el contenido de estos archivos en un array. La forma más fácil de hacerlo es utilizando el método <code>split</code> en la cadena producida por <code>textFile</code> ( o <code>archivoTexto</code>). 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 <code>new Date</code>.</p> -<p><a class="p_ident" id="p-9fBqjU8ju+" href="#p-9fBqjU8ju+" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-9fBqjU8ju+" href="#p-9fBqjU8ju+" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-iW4mBbLVDT" href="#p-iW4mBbLVDT" tabindex="-1" role="presentation"></a>Asegúrate de usar <code>await</code> en el resultado de las funciones asíncronas antes de hacer cualquier cosa con él, o terminarás con una <code>Promise</code> donde esperabas un string.</p> +<p><a class="p_ident" id="p-igRmA9qYWA" href="#p-igRmA9qYWA" tabindex="-1" role="presentation"></a>Asegúrate de usar <code>await</code> en el resultado de las funciones asíncronas antes de hacer cualquier cosa con él, o terminarás con una <code>Promise</code> donde esperabas tener un string.</p> -<p><a class="p_ident" id="p-MYC7CGnZ1D" href="#p-MYC7CGnZ1D" tabindex="-1" role="presentation"></a>hinting}}</p> +</div></details> <h3><a class="i_ident" id="i-rAhebsaSY2" href="#i-rAhebsaSY2" tabindex="-1" role="presentation"></a>Promesas Reales</h3> <p><a class="p_ident" id="p-vfx72TWCka" href="#p-vfx72TWCka" tabindex="-1" role="presentation"></a>Reescribe la función del ejercicio anterior sin <code>async</code>/<code>await</code>, utilizando métodos simples de <code>Promise</code>.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-3oGelkObvZ" href="#c-3oGelkObvZ" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">día</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-W6rAArkArS" href="#c-W6rAArkArS" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">día</span>) { <span class="tok-comment">// Tu código aquí</span> } activityTable(<span class="tok-number">6</span>) - .then(<span class="tok-definition">tabla</span> => console.log(gráficoActividad(tabla)));</pre> + .then(<span class="tok-definition">tabla</span> => console.log(activityGraph(tabla)));</pre> -<p><a class="p_ident" id="p-BGph0/UdzK" href="#p-BGph0/UdzK" tabindex="-1" role="presentation"></a>En este estilo, usar <code>Promise.all</code> será más conveniente que intentar modelar un bucle sobre los archivos de registro. En la función <code>async</code>, simplemente usar <code>await</code> 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?</p> +<p><a class="p_ident" id="p-BGph0/UdzK" href="#p-BGph0/UdzK" tabindex="-1" role="presentation"></a>En este estilo, usar <code>Promise.all</code> será más conveniente que intentar modelar un bucle sobre los archivos de registro. En la función <code>async</code>, simplemente usar <code>await</code> 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?</p> -<p><a class="p_ident" id="p-EBStIx8OmH" href="#p-EBStIx8OmH" tabindex="-1" role="presentation"></a>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 <code>Promise</code> que retorna tu función?</p> +<p><a class="p_ident" id="p-EBStIx8OmH" href="#p-EBStIx8OmH" tabindex="-1" role="presentation"></a>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 <code>Promise</code> que retorna tu función?</p> <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-aw5MLt4jxJ" href="#p-aw5MLt4jxJ" tabindex="-1" role="presentation"></a>El enfoque más directo para escribir esta función es usar una cadena de llamadas <code>then</code>. La primera promesa se produce al leer la lista de archivos de registro. El primer callback puede dividir esta lista y mapear <code>textFile</code> sobre ella para obtener una matriz de promesas para pasar a <code>Promise.all</code>. Puede devolver el objeto devuelto por <code>Promise.all</code>, para que lo que sea que eso devuelva se convierta en el resultado del valor de retorno de este primer <code>then</code>.</p> +<p><a class="p_ident" id="p-aw5MLt4jxJ" href="#p-aw5MLt4jxJ" tabindex="-1" role="presentation"></a>El enfoque más directo para escribir esta función es usar una cadena de llamadas <code>then</code>. La primera promesa se produce al leer la lista de archivos de registro. El primer callback puede dividir esta lista y mapear <code>textFile</code> sobre ella para obtener un array de promesas para pasar a <code>Promise.all</code>. Puede devolver el objeto devuelto por <code>Promise.all</code>, para que lo que sea que eso devuelva se convierta en el resultado del valor de retorno de este primer <code>then</code>.</p> -<p><a class="p_ident" id="p-XLfKUPx5h8" href="#p-XLfKUPx5h8" tabindex="-1" role="presentation"></a>Ahora tenemos una promesa que devuelve un array de archivos de registro. Podemos llamar a <code>then</code> nuevamente en eso, y poner la lógica de conteo de marcas de tiempo allí. Algo así:</p> +<p><a class="p_ident" id="p-XLfKUPx5h8" href="#p-XLfKUPx5h8" tabindex="-1" role="presentation"></a>Ahora tenemos una promesa que devuelve un array de archivos de registro. Podemos llamar a <code>then</code> nuevamente en eso, y poner la lógica de recuento de marcas de tiempo allí. Algo así:</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Ri43SdEaNh" href="#c-Ri43SdEaNh" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">día</span>) { - <span class="tok-keyword">return</span> textoArchivo(<span class="tok-string">"registros_camara.txt"</span>).then(<span class="tok-definition">archivos</span> => { - <span class="tok-keyword">return</span> Promise.all(archivos.split(<span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>).map(textoArchivo)); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-XVz2i5dZDr" href="#c-XVz2i5dZDr" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">día</span>) { + <span class="tok-keyword">return</span> archivoTexto(<span class="tok-string">"camera_logs.txt"</span>).then(<span class="tok-definition">archivos</span> => { + <span class="tok-keyword">return</span> Promise.all(archivos.split(<span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>).map(archivoTexto)); }).then(<span class="tok-definition">logs</span> => { <span class="tok-comment">// analizar...</span> }); }</pre> -<p><a class="p_ident" id="p-nemG+o6551" href="#p-nemG+o6551" tabindex="-1" role="presentation"></a>O podrías, para una programación aún mejor, poner el análisis de cada archivo dentro de <code>Promise.all</code>, para que ese trabajo pueda comenzar para el primer archivo que regresa del disco, incluso antes de que los otros archivos regresen.</p> +<p><a class="p_ident" id="p-AKUrRuWxiy" href="#p-AKUrRuWxiy" tabindex="-1" role="presentation"></a>O podrías, para una programación del trabajo aún mejor, poner el análisis de cada archivo dentro de <code>Promise.all</code>, para que ese trabajo pueda comenzar con el primer archivo que se reciba del disco, incluso antes de que lleguen los otros archivos.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-c8ZiPZOaXj" href="#c-c8ZiPZOaXj" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">día</span>) { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-M7pmLIzx0/" href="#c-M7pmLIzx0/" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">activityTable</span>(<span class="tok-definition">día</span>) { <span class="tok-keyword">let</span> <span class="tok-definition">tabla</span> = []; <span class="tok-comment">// inicializar...</span> - <span class="tok-keyword">return</span> textoArchivo(<span class="tok-string">"registros_camara.txt"</span>).then(<span class="tok-definition">archivos</span> => { + <span class="tok-keyword">return</span> archivoTexto(<span class="tok-string">"registros_camara.txt"</span>).then(<span class="tok-definition">archivos</span> => { <span class="tok-keyword">return</span> Promise.all(archivos.split(<span class="tok-string">"</span><span class="tok-string2">\n</span><span class="tok-string">"</span>).map(<span class="tok-definition">nombre</span> => { - <span class="tok-keyword">return</span> textoArchivo(nombre).then(<span class="tok-definition">log</span> => { + <span class="tok-keyword">return</span> archivoTexto(nombre).then(<span class="tok-definition">log</span> => { <span class="tok-comment">// analizar...</span> }); })); }).then(() => tabla); }</pre> -<p><a class="p_ident" id="p-ZHUmUZs29f" href="#p-ZHUmUZs29f" tabindex="-1" role="presentation"></a>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 <code>await</code> hará que el proceso sea completamente lineal: espera a que se cargue cada archivo antes de continuar. <code>Promise.all</code> 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.</p> +<p><a class="p_ident" id="p-AoWOB0oS8h" href="#p-AoWOB0oS8h" tabindex="-1" role="presentation"></a>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 <code>await</code> hará que el proceso sea completamente lineal: espera a que se cargue cada archivo antes de continuar. <code>Promise.all</code> 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.</p> -<p><a class="p_ident" id="p-Ld+4EByL4v" href="#p-Ld+4EByL4v" tabindex="-1" role="presentation"></a>Cuando un archivo en la lista no existe, la promesa devuelta por <code>textFile</code> será rechazada. Debido a que <code>Promise.all</code> 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 <code>then</code> también será una promesa rechazada. Esto hace que la promesa devuelta por <code>then</code> falle, por lo que la devolución de llamada dada al segundo <code>then</code> ni siquiera se llama, y se devuelve una promesa rechazada desde la función.</p> +<p><a class="p_ident" id="p-Ld+4EByL4v" href="#p-Ld+4EByL4v" tabindex="-1" role="presentation"></a>Cuando un archivo en la lista no existe, la promesa devuelta por <code>archivoTexto</code> será rechazada. Debido a que <code>Promise.all</code> se rechaza si alguna de las promesas que se le pasan falla, el valor de retorno de la callback dada al primer <code>then</code> también será una promesa rechazada. Esto hace que la promesa devuelta por <code>then</code> falle, por lo que la callback dada al segundo <code>then</code> ni siquiera se llama, y se devuelve una promesa rechazada desde la función.</p> </div></details> @@ -585,7 +599,7 @@ <h3><a class="i_ident" id="i-pfc5Y5gAWn" href="#i-pfc5Y5gAWn" tabindex="-1" role <p><a class="p_ident" id="p-M8QzvSw1av" href="#p-M8QzvSw1av" tabindex="-1" role="presentation"></a>Como vimos, dado un array de promesas, <code>Promise.all</code> 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 <code>all</code> también falla, con la razón de fallo de la promesa que falló.</p> -<p><a class="p_ident" id="p-7xgUT0fq6f" href="#p-7xgUT0fq6f" tabindex="-1" role="presentation"></a>Implementa algo similar tú mismo como una función regular llamada <code>Promise_all</code>.</p> +<p><a class="p_ident" id="p-jbVLP1YRLs" href="#p-jbVLP1YRLs" tabindex="-1" role="presentation"></a>Implementa algo similar tú mismo como una función normal llamada <code>Promise_all</code>.</p> <p><a class="p_ident" id="p-JLqRagv2ZP" href="#p-JLqRagv2ZP" tabindex="-1" role="presentation"></a>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.</p> @@ -619,15 +633,13 @@ <h3><a class="i_ident" id="i-pfc5Y5gAWn" href="#i-pfc5Y5gAWn" tabindex="-1" role <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-jsnP1W7uln" href="#p-jsnP1W7uln" tabindex="-1" role="presentation"></a>La función pasada al constructor <code>Promise</code> tendrá que llamar a <code>then</code> 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.</p> +<p><a class="p_ident" id="p-jsnP1W7uln" href="#p-jsnP1W7uln" tabindex="-1" role="presentation"></a>La función pasada al constructor <code>Promise</code> tendrá que llamar a <code>then</code> 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.</p> <p><a class="p_ident" id="p-pv4cygIqmH" href="#p-pv4cygIqmH" tabindex="-1" role="presentation"></a>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).</p> <p><a class="p_ident" id="p-RfvboDBLxx" href="#p-RfvboDBLxx" tabindex="-1" role="presentation"></a>Manejar el fallo requiere un poco de pensamiento pero resulta ser extremadamente simple. Simplemente pasa la función <code>reject</code> de la promesa contenedora a cada una de las promesas en el array como un controlador <code>catch</code> o como un segundo argumento para <code>then</code> para que un fallo en una de ellas desencadene el rechazo de toda la promesa contenedora.</p> -<p><a class="p_ident" id="p-nfQ6A5rdkZ" href="#p-nfQ6A5rdkZ" tabindex="-1" role="presentation"></a>pista</p> - -</div></details> +<p><a class="p_ident" id="p-2jmj7l5rSw" href="#p-2jmj7l5rSw" tabindex="-1" role="presentation"></a>}}</p> </div></details><nav><a href="10_modules.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="12_language.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button> </nav> 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 @@ <h1>Proyecto: Un Lenguaje de Programación</h1> <p><a class="p_ident" id="p-NCKe5tEBSN" href="#p-NCKe5tEBSN" tabindex="-1" role="presentation"></a>El evaluador, que determina el significado de expresiones en un lenguaje de programación, es solo otro programa.</p> -<footer>Hal Abelson y Gerald Sussman, <cite>Estructura e Interpretación de Programas de Computadora</cite></footer> +<footer>Hal Abelson y Gerald Sussman, <cite>Estructura e Interpretación de Programas Informáticos</cite></footer> </blockquote><figure class="chapter framed"><img src="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"></figure> <p><a class="p_ident" id="p-xcNW3HLt2k" href="#p-xcNW3HLt2k" tabindex="-1" role="presentation"></a>Crear tu propio lenguaje de programación es sorprendentemente fácil (si no apuntas muy alto) y muy esclarecedor.</p> -<p><a class="p_ident" id="p-mZbmmA9jPL" href="#p-mZbmmA9jPL" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-mZbmmA9jPL" href="#p-mZbmmA9jPL" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-AY1eTWKK2r" href="#p-AY1eTWKK2r" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-AY1eTWKK2r" href="#p-AY1eTWKK2r" tabindex="-1" role="presentation"></a>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.</p> <h2 id="parsing"><a class="h_ident" id="h-Ri8DEsFixT" href="#h-Ri8DEsFixT" tabindex="-1" role="presentation"></a>Análisis Sintáctico</h2> -<p><a class="p_ident" id="p-LWCHgQ8jiK" href="#p-LWCHgQ8jiK" tabindex="-1" role="presentation"></a>La parte más inmediatamente visible de un lenguaje de programación es su <em>sintaxis</em>, o notación. Un <em>analizador sintáctico</em> 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.</p> +<p><a class="p_ident" id="p-LWCHgQ8jiK" href="#p-LWCHgQ8jiK" tabindex="-1" role="presentation"></a>La parte más inmediatamente visible de un lenguaje de programación es su <em>sintaxis</em>, o notación. Un <em>analizador sintáctico</em> (o <em>parser</em>) 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.</p> <p><a class="p_ident" id="p-ZtGU+dhqC7" href="#p-ZtGU+dhqC7" tabindex="-1" role="presentation"></a>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 <em>aplicación</em>. Las aplicaciones se utilizan para llamadas de funciones pero también para estructuras como <code>if</code> o <code>while</code>.</p> -<p><a class="p_ident" id="p-ep+0glVGES" href="#p-ep+0glVGES" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-z+EsMsMXrt" href="#p-z+EsMsMXrt" tabindex="-1" role="presentation"></a>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.</p> <p><a class="p_ident" id="p-vwvgo3jFhV" href="#p-vwvgo3jFhV" tabindex="-1" role="presentation"></a>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.</p> @@ -41,7 +41,7 @@ <h2 id="parsing"><a class="h_ident" id="h-Ri8DEsFixT" href="#h-Ri8DEsFixT" tabin print("grande"), print("pequeño")))</pre> -<p><a class="p_ident" id="p-C/tUY61w0R" href="#p-C/tUY61w0R" tabindex="-1" role="presentation"></a>La uniformidad del lenguaje Egg significa que las cosas que son operadores en JavaScript (como <code>></code>) 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 <code>do</code> para representar la realización de múltiples tareas en secuencia.</p> +<p><a class="p_ident" id="p-M92I6tmX6O" href="#p-M92I6tmX6O" tabindex="-1" role="presentation"></a>Como queremos tener la ya mencionada uniformidad en el lenguaje Egg, resulta que, cosas que son operadores en JavaScript (como <code>></code>), 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 <code>do</code> para representar la realización de múltiples tareas en secuencia.</p> <p><a class="p_ident" id="p-U5mB4GWXLX" href="#p-U5mB4GWXLX" tabindex="-1" role="presentation"></a>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 <code>type</code> que indica el tipo de expresión que es y otras propiedades para describir su contenido.</p> @@ -58,15 +58,17 @@ <h2 id="parsing"><a class="h_ident" id="h-Ri8DEsFixT" href="#h-Ri8DEsFixT" tabin ] }</pre> -<p><a class="p_ident" id="p-xa+b6R6Gzl" href="#p-xa+b6R6Gzl" tabindex="-1" role="presentation"></a>Esta estructura de datos se llama un <em>árbol de sintaxis</em>. 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.</p><figure><img src="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."></figure> +<p><a class="p_ident" id="p-xa+b6R6Gzl" href="#p-xa+b6R6Gzl" tabindex="-1" role="presentation"></a>Esta estructura de datos se llama un <em>árbol sintáctico</em>. 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.</p><figure><img src="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."></figure> -<p><a class="p_ident" id="p-oWghglriRc" href="#p-oWghglriRc" tabindex="-1" role="presentation"></a>Contrasta esto con el analizador que escribimos para el formato de archivo de configuración en el <a href="09_regexp.html#ini">Capítulo 9</a>, 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.</p> +<p><a class="p_ident" id="p-oWghglriRc" href="#p-oWghglriRc" tabindex="-1" role="presentation"></a>Contrasta esto con el analizador que escribimos para el formato de archivo de configuración en el <a href="09_regexp.html#ini">Capítulo 9</a>, 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.</p> <p><a class="p_ident" id="p-6Mz6uGItWm" href="#p-6Mz6uGItWm" tabindex="-1" role="presentation"></a>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 <em>contienen</em> otras expresiones.</p> <p><a class="p_ident" id="p-cXp43eu5PY" href="#p-cXp43eu5PY" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-+kbXV3aRjp" href="#p-+kbXV3aRjp" tabindex="-1" role="presentation"></a>Definimos una función <code>parseExpression</code>, 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:</p> +<p><a class="p_ident" id="p-LXrIAR63Rl" href="#p-LXrIAR63Rl" tabindex="-1" role="presentation"></a>Definimos una función <code>parseExpression</code>, 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.</p> + +<p><a class="p_ident" id="p-VBW6YCLGAq" href="#p-VBW6YCLGAq" tabindex="-1" role="presentation"></a>Esta es la primera parte del analizador sintáctico:</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Kq2My8rR4Z" href="#c-Kq2My8rR4Z" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">parseExpression</span>(<span class="tok-definition">program</span>) { program = skipSpace(program); @@ -90,11 +92,11 @@ <h2 id="parsing"><a class="h_ident" id="h-Ri8DEsFixT" href="#h-Ri8DEsFixT" tabin <span class="tok-keyword">return</span> string.slice(first); }</pre> -<p><a class="p_ident" id="p-69spf6/XVL" href="#p-69spf6/XVL" tabindex="-1" role="presentation"></a>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 <code>skipSpace</code>.</p> +<p><a class="p_ident" id="p-FrwOEHj7ij" href="#p-FrwOEHj7ij" tabindex="-1" role="presentation"></a>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 <code>skipSpace</code>.</p> <p><a class="p_ident" id="p-G2iiBlqqe/" href="#p-G2iiBlqqe/" tabindex="-1" role="presentation"></a>Después de omitir cualquier espacio inicial, <code>parseExpression</code> 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 <code>SyntaxError</code> aquí. Esta es una clase de excepción definida por el estándar, al igual que <code>Error</code>, pero más específica.</p> -<p><a class="p_ident" id="p-EgqyXKPX43" href="#p-EgqyXKPX43" tabindex="-1" role="presentation"></a>Luego cortamos la parte que coincidió de la cadena del programa y la pasamos, junto con el objeto de la expresión, a <code>parseApply</code>, que verifica si la expresión es una aplicación. Si lo es, analiza una lista de argumentos entre paréntesis.</p> +<p><a class="p_ident" id="p-EgqyXKPX43" href="#p-EgqyXKPX43" tabindex="-1" role="presentation"></a>Luego cortamos la parte que coincidió de la cadena del programa y pasamos el resto, junto con el objeto de la expresión, a <code>parseApply</code>, que verifica si la expresión es una aplicación. Si lo es, analiza una lista de argumentos entre paréntesis.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-NbmLO+XtaQ" href="#c-NbmLO+XtaQ" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">parseApply</span>(<span class="tok-definition">expr</span>, <span class="tok-definition">program</span>) { program = skipSpace(program); @@ -117,13 +119,13 @@ <h2 id="parsing"><a class="h_ident" id="h-Ri8DEsFixT" href="#h-Ri8DEsFixT" tabin <span class="tok-keyword">return</span> parseApply(expr, program.slice(<span class="tok-number">1</span>)); }</pre> -<p><a class="p_ident" id="p-P+rEG3RwON" href="#p-P+rEG3RwON" tabindex="-1" role="presentation"></a>Si el próximo carácter en el programa no es un paréntesis de apertura, esto no es una aplicación y <code>parseApply</code> devuelve la expresión que se le dio.</p> +<p><a class="p_ident" id="p-P+rEG3RwON" href="#p-P+rEG3RwON" tabindex="-1" role="presentation"></a>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 <code>parseApply</code> devuelve la expresión que se le dio.</p> -<p><a class="p_ident" id="p-gyKfAj64fa" href="#p-gyKfAj64fa" tabindex="-1" role="presentation"></a>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 <code>parseExpression</code> para analizar cada argumento hasta encontrar un paréntesis de cierre. La recursión es indirecta, a través de <code>parseApply</code> y <code>parseExpression</code> llamándose mutuamente.</p> +<p><a class="p_ident" id="p-gyKfAj64fa" href="#p-gyKfAj64fa" tabindex="-1" role="presentation"></a>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 <code>parseExpression</code> para analizar cada argumento hasta encontrar un paréntesis de cierre. La recursión es indirecta, realizada a través de <code>parseApply</code> y <code>parseExpression</code> llamándose mutuamente.</p> <p><a class="p_ident" id="p-lwTBl+PY0V" href="#p-lwTBl+PY0V" tabindex="-1" role="presentation"></a>Dado que una expresión de aplicación puede a su vez ser aplicada (como en <code>multiplicador(2)(1)</code>), <code>parseApply</code> debe, después de analizar una aplicación, llamarse a sí misma nuevamente para verificar si sigue otro par de paréntesis.</p> -<p><a class="p_ident" id="p-89g4z+3rO6" href="#p-89g4z+3rO6" tabindex="-1" role="presentation"></a>Esto es todo lo que necesitamos para analizar Egg. Lo envolvemos en una conveniente <code>parse</code> 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.</p> +<p><a class="p_ident" id="p-89g4z+3rO6" href="#p-89g4z+3rO6" tabindex="-1" role="presentation"></a>Esto es todo lo que necesitamos para analizar el lenguaje Egg. Lo envolvemos en una conveniente función <code>parse</code> 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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-PRcHuKWwJ9" href="#c-PRcHuKWwJ9" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">parse</span>(<span class="tok-definition">program</span>) { <span class="tok-keyword">let</span> {expr, rest} = parseExpression(program); @@ -139,13 +141,13 @@ <h2 id="parsing"><a class="h_ident" id="h-Ri8DEsFixT" href="#h-Ri8DEsFixT" tabin <span class="tok-comment">// args: [{type: "word", name: "a"},</span> <span class="tok-comment">// {type: "value", value: 10}]}</span></pre> -<p><a class="p_ident" id="p-l7ELJeshJs" href="#p-l7ELJeshJs" tabindex="-1" role="presentation"></a>¡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.</p> +<p><a class="p_ident" id="p-qKTv0ZtDnp" href="#p-qKTv0ZtDnp" tabindex="-1" role="presentation"></a>¡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.</p> <h2><a class="h_ident" id="h-lI3Fc6r+GN" href="#h-lI3Fc6r+GN" tabindex="-1" role="presentation"></a>El evaluador</h2> -<p><a class="p_ident" id="p-dhdWhScWHm" href="#p-dhdWhScWHm" tabindex="-1" role="presentation"></a>¿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.</p> +<p><a class="p_ident" id="p-W7cZLo9h6k" href="#p-W7cZLo9h6k" tabindex="-1" role="presentation"></a>¿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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-w34EbEGm2M" href="#c-w34EbEGm2M" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">specialForms</span> = Object.create(<span class="tok-keyword">null</span>); +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-bWmvf0SxWN" href="#c-bWmvf0SxWN" tabindex="-1" role="presentation"></a><span class="tok-keyword">const</span> <span class="tok-definition">specialForms</span> = Object.create(<span class="tok-keyword">null</span>); <span class="tok-keyword">function</span> <span class="tok-definition">evaluate</span>(<span class="tok-definition">expr</span>, <span class="tok-definition">scope</span>) { <span class="tok-keyword">if</span> (expr.type == <span class="tok-string">"value"</span>) { @@ -155,7 +157,7 @@ <h2><a class="h_ident" id="h-lI3Fc6r+GN" href="#h-lI3Fc6r+GN" tabindex="-1" role <span class="tok-keyword">return</span> scope[expr.name]; } <span class="tok-keyword">else</span> { <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> ReferenceError( - <span class="tok-string2">`Vinculación indefinida: </span>${expr.name}<span class="tok-string2">`</span>); + <span class="tok-string2">`Asociación indefinida: </span>${expr.name}<span class="tok-string2">`</span>); } } <span class="tok-keyword">else</span> <span class="tok-keyword">if</span> (expr.type == <span class="tok-string">"apply"</span>) { <span class="tok-keyword">let</span> {operator, args} = expr; @@ -173,13 +175,13 @@ <h2><a class="h_ident" id="h-lI3Fc6r+GN" href="#h-lI3Fc6r+GN" tabindex="-1" role } }</pre> -<p><a class="p_ident" id="p-HWNpAazEel" href="#p-HWNpAazEel" tabindex="-1" role="presentation"></a>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 <code>100</code> 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.</p> +<p><a class="p_ident" id="p-CO2DO5EE0G" href="#p-CO2DO5EE0G" tabindex="-1" role="presentation"></a>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 <code>100</code> 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.</p> <p><a class="p_ident" id="p-NYz/C0Evfv" href="#p-NYz/C0Evfv" tabindex="-1" role="presentation"></a>Las aplicaciones son más complicadas. Si son una forma especial, como <code>if</code>, 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.</p> <p><a class="p_ident" id="p-/VWFz5tzb5" href="#p-/VWFz5tzb5" tabindex="-1" role="presentation"></a>Usamos valores de función JavaScript simples para representar los valores de función de Egg. Volveremos a esto <a href="12_language.html#egg_fun">más tarde</a>, cuando se defina la forma especial llamada <code>fun</code>.</p> -<p><a class="p_ident" id="p-yBQx2/sKQH" href="#p-yBQx2/sKQH" tabindex="-1" role="presentation"></a>La estructura recursiva de <code>evaluate</code> 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.</p> +<p><a class="p_ident" id="p-yBQx2/sKQH" href="#p-yBQx2/sKQH" tabindex="-1" role="presentation"></a>La estructura recursiva de <code>evaluate</code> 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.</p> <p><a class="p_ident" id="p-YL/SkpZx0H" href="#p-YL/SkpZx0H" tabindex="-1" role="presentation"></a>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.</p> @@ -197,15 +199,15 @@ <h2><a class="h_ident" id="h-okJU2Tz5zr" href="#h-okJU2Tz5zr" tabindex="-1" role } };</pre> -<p><a class="p_ident" id="p-WeZ6L3damh" href="#p-WeZ6L3damh" tabindex="-1" role="presentation"></a>La construcción <code>if</code> de Egg espera exactamente tres argumentos. Evaluará el primero, y si el resultado no es el valor <code>false</code>, evaluará el segundo. De lo contrario, se evaluará el tercero. Esta forma <code>if</code> se asemeja más al operador ternario <code>?:</code> de JavaScript que al <code>if</code> de JavaScript. Es una expresión, no una declaración, y produce un valor, concretamente, el resultado del segundo o tercer argumento.</p> +<p><a class="p_ident" id="p-WeZ6L3damh" href="#p-WeZ6L3damh" tabindex="-1" role="presentation"></a>La construcción <code>if</code> de Egg espera exactamente tres argumentos. Evaluará el primero y, si el resultado no es el valor <code>false</code>, evaluará el segundo. De lo contrario, se evaluará el tercero. Esta forma <code>if</code> se asemeja más al operador ternario <code>?:</code> de JavaScript que al <code>if</code> de JavaScript. Es una expresión, no una declaración, y produce un valor, concretamente, el resultado del segundo o tercer argumento.</p> <p><a class="p_ident" id="p-zLB8PMONce" href="#p-zLB8PMONce" tabindex="-1" role="presentation"></a>Egg también difiere de JavaScript en cómo maneja el valor de condición para <code>if</code>. No tratará cosas como cero o la cadena vacía como falso, solo el valor preciso <code>false</code>.</p> -<p><a class="p_ident" id="p-q4X/kmv6pB" href="#p-q4X/kmv6pB" tabindex="-1" role="presentation"></a>La razón por la que necesitamos representar <code>if</code> 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 <code>if</code> debe evaluar solo <em>uno</em> de sus segundos o terceros argumentos, dependiendo del valor del primero.</p> +<p><a class="p_ident" id="p-q4X/kmv6pB" href="#p-q4X/kmv6pB" tabindex="-1" role="presentation"></a>La razón por la que necesitamos representar <code>if</code> 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 <code>if</code> debe evaluar solo <em>uno</em> de entre su segundo y tercer argumentos, dependiendo del valor del primero.</p> <p><a class="p_ident" id="p-ThCmPNDU78" href="#p-ThCmPNDU78" tabindex="-1" role="presentation"></a>La forma <code>while</code> es similar.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-icmi3itpHq" href="#c-icmi3itpHq" tabindex="-1" role="presentation"></a>specialForms.while = (<span class="tok-definition">args</span>, <span class="tok-definition">scope</span>) => { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-AMAXY0KvU2" href="#c-AMAXY0KvU2" tabindex="-1" role="presentation"></a>specialForms.while = (<span class="tok-definition">args</span>, <span class="tok-definition">scope</span>) => { <span class="tok-keyword">if</span> (args.length != <span class="tok-number">2</span>) { <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> SyntaxError(<span class="tok-string">"Número incorrecto de argumentos para while"</span>); } @@ -214,7 +216,7 @@ <h2><a class="h_ident" id="h-okJU2Tz5zr" href="#h-okJU2Tz5zr" tabindex="-1" role } <span class="tok-comment">// Dado que undefined no existe en Egg, devolvemos false,</span> - <span class="tok-comment">// por falta de un resultado significativo.</span> + <span class="tok-comment">// para la falta de un resultado con sentido.</span> <span class="tok-keyword">return</span> false; };</pre> @@ -228,7 +230,7 @@ <h2><a class="h_ident" id="h-okJU2Tz5zr" href="#h-okJU2Tz5zr" tabindex="-1" role <span class="tok-keyword">return</span> valor; };</pre> -<p><a class="p_ident" id="p-p3K5iHWfUQ" href="#p-p3K5iHWfUQ" tabindex="-1" role="presentation"></a>Para poder crear vinculaciones y darles nuevos valores, también creamos una forma llamada <code>define</code>. 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 <code>define</code>, al igual que todo, es una expresión, debe devolver un valor. Haremos que devuelva el valor que se asignó (como el operador <code>=</code> de JavaScript).</p> +<p><a class="p_ident" id="p-p3K5iHWfUQ" href="#p-p3K5iHWfUQ" tabindex="-1" role="presentation"></a>Para poder crear asociaciones y darles nuevos valores, también creamos una forma llamada <code>define</code>. 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 <code>define</code>, al igual que todo, es una expresión, debe devolver un valor. Haremos que devuelva el valor que se asignó (como el operador <code>=</code> de JavaScript).</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-9inONoYidA" href="#c-9inONoYidA" tabindex="-1" role="presentation"></a>specialForms.define = (<span class="tok-definition">args</span>, <span class="tok-definition">scope</span>) => { <span class="tok-keyword">if</span> (args.length != <span class="tok-number">2</span> || args[<span class="tok-number">0</span>].type != <span class="tok-string">"word"</span>) { @@ -241,7 +243,7 @@ <h2><a class="h_ident" id="h-okJU2Tz5zr" href="#h-okJU2Tz5zr" tabindex="-1" role <h2><a class="h_ident" id="h-QDYVZFuV/L" href="#h-QDYVZFuV/L" tabindex="-1" role="presentation"></a>El entorno</h2> -<p><a class="p_ident" id="p-t3Y1Pmfab7" href="#p-t3Y1Pmfab7" tabindex="-1" role="presentation"></a>El scope aceptado por <code>evaluate</code> 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.</p> +<p><a class="p_ident" id="p-t3Y1Pmfab7" href="#p-t3Y1Pmfab7" tabindex="-1" role="presentation"></a>El scope aceptado por <code>evaluate</code> 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.</p> <p><a class="p_ident" id="p-b3b8HuKUtZ" href="#p-b3b8HuKUtZ" tabindex="-1" role="presentation"></a>Para poder usar la construcción <code>if</code> 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 <code>true</code> y <code>false</code> y los usamos.</p> @@ -275,7 +277,7 @@ <h2><a class="h_ident" id="h-QDYVZFuV/L" href="#h-QDYVZFuV/L" tabindex="-1" role <span class="tok-keyword">return</span> evaluate(parse(program), Object.create(topScope)); }</pre> -<p><a class="p_ident" id="p-QlhCpeZ1rM" href="#p-QlhCpeZ1rM" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-I7W/GUVT3g" href="#p-I7W/GUVT3g" tabindex="-1" role="presentation"></a>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.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-uW/XtfVXMZ" href="#c-uW/XtfVXMZ" tabindex="-1" role="presentation"></a>run(<span class="tok-string2">`</span> <span class="tok-string2">do(define(total, 0),</span> @@ -287,22 +289,20 @@ <h2><a class="h_ident" id="h-QDYVZFuV/L" href="#h-QDYVZFuV/L" tabindex="-1" role <span class="tok-string2">`</span>); <span class="tok-comment">// → 55</span></pre> -<p><a class="p_ident" id="p-xNJMDZFJNP" href="#p-xNJMDZFJNP" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-xNJMDZFJNP" href="#p-xNJMDZFJNP" tabindex="-1" role="presentation"></a>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.</p> <h2 id="egg_fun"><a class="h_ident" id="h-H0l5He7QIh" href="#h-H0l5He7QIh" tabindex="-1" role="presentation"></a>Funciones</h2> -<p><a class="p_ident" id="p-pxhz3ZokU1" href="#p-pxhz3ZokU1" tabindex="-1" role="presentation"></a>Un lenguaje de programación sin funciones es un pobre lenguaje de programación.</p> - -<p><a class="p_ident" id="p-Knv8+HvQ29" href="#p-Knv8+HvQ29" tabindex="-1" role="presentation"></a>Afortunadamente, no es difícil agregar una construcción <code>fun</code>, 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.</p> +<p><a class="p_ident" id="p-wS9RQVhTKH" href="#p-wS9RQVhTKH" tabindex="-1" role="presentation"></a>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 <code>fun</code>, 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.</p> -<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-nLlUjn7C0q" href="#c-nLlUjn7C0q" tabindex="-1" role="presentation"></a>specialForms.fun = (<span class="tok-definition">args</span>, <span class="tok-definition">scope</span>) => { +<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-RNbUSCylI0" href="#c-RNbUSCylI0" tabindex="-1" role="presentation"></a>specialForms.fun = (<span class="tok-definition">args</span>, <span class="tok-definition">scope</span>) => { <span class="tok-keyword">if</span> (!args.length) { <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> SyntaxError(<span class="tok-string">"Las funciones necesitan un cuerpo"</span>); } <span class="tok-keyword">let</span> <span class="tok-definition">body</span> = args[args.length - <span class="tok-number">1</span>]; <span class="tok-keyword">let</span> <span class="tok-definition">params</span> = args.slice(<span class="tok-number">0</span>, args.length - <span class="tok-number">1</span>).map(<span class="tok-definition">expr</span> => { <span class="tok-keyword">if</span> (expr.type != <span class="tok-string">"word"</span>) { - <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> SyntaxError(<span class="tok-string">"Los nombres de los parámetros deben ser palabras"</span>); + <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> SyntaxError(<span class="tok-string">"Los nombres de los parámetros deben ser de tipo word"</span>); } <span class="tok-keyword">return</span> expr.name; }); @@ -340,21 +340,23 @@ <h2><a class="h_ident" id="h-QZp+1BKgLP" href="#h-QZp+1BKgLP" tabindex="-1" role <p><a class="p_ident" id="p-pYpmnQkmEE" href="#p-pYpmnQkmEE" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-TqdaU5OIYq" href="#p-TqdaU5OIYq" tabindex="-1" role="presentation"></a><em>La compilación</em> 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.</p> +<p><a class="p_ident" id="p-xxZ2PhzKFw" href="#p-xxZ2PhzKFw" tabindex="-1" role="presentation"></a><em>La compilación</em> 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.</p> <p><a class="p_ident" id="p-MlvKWh44Ts" href="#p-MlvKWh44Ts" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-aM2KGsJ5pX" href="#p-aM2KGsJ5pX" tabindex="-1" role="presentation"></a>Sería posible escribir una estrategia de evaluación alternativa para Egg, una que primero convierte el programa a un programa JavaScript, usa <code>Function</code> 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.</p> +<p><a class="p_ident" id="p-aM2KGsJ5pX" href="#p-aM2KGsJ5pX" tabindex="-1" role="presentation"></a>Sería posible escribir una estrategia de evaluación alternativa para Egg, una que primero convierte el programa a un programa JavaScript, usa <code>Function</code> 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.</p> <p><a class="p_ident" id="p-41d+3GRIQp" href="#p-41d+3GRIQp" tabindex="-1" role="presentation"></a>Si te interesa este tema y estás dispuesto a dedicar tiempo a ello, te animo a intentar implementar ese compilador como ejercicio.</p> <h2><a class="h_ident" id="h-cAD+YSu/Uu" href="#h-cAD+YSu/Uu" tabindex="-1" role="presentation"></a>Haciendo trampa</h2> -<p><a class="p_ident" id="p-VP1siqwk/B" href="#p-VP1siqwk/B" tabindex="-1" role="presentation"></a>Cuando definimos <code>if</code> y <code>while</code>, probablemente notaste que eran envoltorios más o menos triviales alrededor del propio <code>if</code> y <code>while</code> 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, <em>sí</em> hay situaciones donde escribir pequeños lenguajes ayuda a realizar trabajos reales.</p> +<p><a class="p_ident" id="p-7L8Oaq7ns/" href="#p-7L8Oaq7ns/" tabindex="-1" role="presentation"></a>Cuando hemos definido <code>if</code> y <code>while</code>, probablemente has notado que eran envoltorios más o menos triviales alrededor de los propios <code>if</code> y <code>while</code> 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í.</p> + +<p><a class="p_ident" id="p-y5ZbBecJZh" href="#p-y5ZbBecJZh" tabindex="-1" role="presentation"></a>Aunque el lenguaje de juguete de este capítulo no hace nada que no se pudiera hacer mejor en JavaScript, <em>sí</em> hay situaciones donde escribir pequeños lenguajes ayuda a sacar adelante trabajo de verdad.</p> <p><a class="p_ident" id="p-kbfQXnrr8R" href="#p-kbfQXnrr8R" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-UmyA5toIkN" href="#p-UmyA5toIkN" tabindex="-1" role="presentation"></a>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.</p> +<p><a class="p_ident" id="p-UmyA5toIkN" href="#p-UmyA5toIkN" tabindex="-1" role="presentation"></a>O imagina que estás construyendo un programa que permite crear rápidamente analizadores sintácticos (o <em>parsers</em>) 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.</p> <pre class="snippet" data-language="null" ><a class="c_ident" id="c-ZLsfR3tk19" href="#c-ZLsfR3tk19" tabindex="-1" role="presentation"></a>expr = número | cadena | nombre | aplicación @@ -398,7 +400,7 @@ <h3><a class="i_ident" id="i-uQzJv9I1Z6" href="#i-uQzJv9I1Z6" tabindex="-1" role <p><a class="p_ident" id="p-V6CA9sjtEY" href="#p-V6CA9sjtEY" tabindex="-1" role="presentation"></a>La forma más sencilla de hacer esto es representar los arrays de Egg con arrays de JavaScript.</p> -<p><a class="p_ident" id="p-gKO9oQjczI" href="#p-gKO9oQjczI" tabindex="-1" role="presentation"></a>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 <code>array</code> puede ser <em>muy</em> simple.</p> +<p><a class="p_ident" id="p-gKO9oQjczI" href="#p-gKO9oQjczI" tabindex="-1" role="presentation"></a>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 <code>array</code> puede ser <em>muy</em> simple.</p> </div></details> @@ -406,7 +408,7 @@ <h3><a class="i_ident" id="i-KU8YojAGul" href="#i-KU8YojAGul" tabindex="-1" role <p><a class="p_ident" id="p-fppmSSGuOO" href="#p-fppmSSGuOO" tabindex="-1" role="presentation"></a>La forma en que hemos definido <code>fun</code> 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.</p> -<p><a class="p_ident" id="p-CGUDr6fRr0" href="#p-CGUDr6fRr0" tabindex="-1" role="presentation"></a>El siguiente programa ilustra esto: la función <code>f</code> devuelve una función que suma su argumento al argumento de <code>f</code>, lo que significa que necesita acceder al ámbito local dentro de <code>f</code> para poder usar la vinculación <code>a</code>.</p> +<p><a class="p_ident" id="p-22mEw2O+20" href="#p-22mEw2O+20" tabindex="-1" role="presentation"></a>El siguiente programa ilustra esto: la función <code>f</code> devuelve una función que suma su argumento al argumento de <code>f</code>, lo que significa que necesita acceder al ámbito local dentro de <code>f</code> para poder usar la asociación <code>a</code>.</p> <pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-zJ2x7sbWRv" href="#c-zJ2x7sbWRv" tabindex="-1" role="presentation"></a>run(<span class="tok-string2">`</span> <span class="tok-string2">do(define(f, fun(a, fun(b, +(a, b)))),</span> @@ -414,19 +416,19 @@ <h3><a class="i_ident" id="i-KU8YojAGul" href="#i-KU8YojAGul" tabindex="-1" role <span class="tok-string2">`</span>); <span class="tok-comment">// → 9</span></pre> -<p><a class="p_ident" id="p-8Tn4o3XoK3" href="#p-8Tn4o3XoK3" tabindex="-1" role="presentation"></a>Vuelve a la definición del formulario <code>fun</code> y explica qué mecanismo hace que esto funcione.</p> +<p><a class="p_ident" id="p-8Tn4o3XoK3" href="#p-8Tn4o3XoK3" tabindex="-1" role="presentation"></a>Vuelve a la definición de la forma <code>fun</code> y explica qué mecanismo hace que esto funcione.</p> <details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text"> -<p><a class="p_ident" id="p-+PSytzGvu7" href="#p-+PSytzGvu7" tabindex="-1" role="presentation"></a>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 <code>fun</code> tiene acceso al argumento <code>scope</code> dado a su función contenedora y lo utiliza para crear el ámbito local de la función cuando se llama.</p> +<p><a class="p_ident" id="p-+PSytzGvu7" href="#p-+PSytzGvu7" tabindex="-1" role="presentation"></a>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 <code>fun</code> tiene acceso al argumento <code>scope</code> dado a su función contenedora y lo utiliza para crear el ámbito local de la función cuando se llama.</p> -<p><a class="p_ident" id="p-ZUZpVdQzvJ" href="#p-ZUZpVdQzvJ" tabindex="-1" role="presentation"></a>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).</p> +<p><a class="p_ident" id="p-ZUZpVdQzvJ" href="#p-ZUZpVdQzvJ" tabindex="-1" role="presentation"></a>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).</p> </div></details> <h3><a class="i_ident" id="i-BU3lBD6Cl5" href="#i-BU3lBD6Cl5" tabindex="-1" role="presentation"></a>Comentarios</h3> -<p><a class="p_ident" id="p-O8y2A3uI5s" href="#p-O8y2A3uI5s" tabindex="-1" role="presentation"></a>Sería bueno si pudiéramos escribir comentarios en Egg. Por ejemplo, siempre que encontremos un signo de almohadilla (<code>#</code>), podríamos tratar el resto de la línea como un comentario y ignorarlo, similar a <code>//</code> en JavaScript.</p> +<p><a class="p_ident" id="p-lEsaOoP0eC" href="#p-lEsaOoP0eC" tabindex="-1" role="presentation"></a>Sería bueno si pudiéramos escribir comentarios en Egg. Por ejemplo, siempre que encontremos un signo de almohadilla (<code>#</code>), podríamos tratar el resto de la línea como un comentario e ignorarlo, como con <code>//</code> en JavaScript.</p> <p><a class="p_ident" id="p-ctRQbLAhSs" href="#p-ctRQbLAhSs" tabindex="-1" role="presentation"></a>No tenemos que hacer grandes cambios en el analizador para admitir esto. Simplemente podemos cambiar <code>skipSpace</code> para omitir comentarios como si fueran espacios en blanco de manera que todos los puntos donde se llama a <code>skipSpace</code> ahora también omitirán comentarios. Realiza este cambio.</p> @@ -449,17 +451,17 @@ <h3><a class="i_ident" id="i-BU3lBD6Cl5" href="#i-BU3lBD6Cl5" tabindex="-1" role <p><a class="p_ident" id="p-4pQ2RIUDPS" href="#p-4pQ2RIUDPS" tabindex="-1" role="presentation"></a>Asegúrate de que tu solución maneje múltiples comentarios seguidos, con posiblemente espacios en blanco entre ellos o después de ellos.</p> -<p><a class="p_ident" id="p-QQa8lWmHXT" href="#p-QQa8lWmHXT" tabindex="-1" role="presentation"></a>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 <code>exec</code> o <code>match</code> y observa la longitud del primer elemento en la matriz devuelta (la coincidencia completa) para averiguar cuántos caracteres cortar.</p> +<p><a class="p_ident" id="p-/7TlNe3+Wg" href="#p-/7TlNe3+Wg" tabindex="-1" role="presentation"></a>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 <code>exec</code> o <code>match</code> y observa la longitud del primer elemento en la matriz devuelta (la coincidencia completa) para averiguar cuántos caracteres cortar.</p> </div></details> <h3><a class="i_ident" id="i-TJ4IOq9YIj" href="#i-TJ4IOq9YIj" tabindex="-1" role="presentation"></a>Corrigiendo el ámbito</h3> -<p><a class="p_ident" id="p-OLjh9e6hGE" href="#p-OLjh9e6hGE" tabindex="-1" role="presentation"></a>Actualmente, la única forma de asignar un enlace un valor es <code>define</code>. Esta construcción actúa como una forma tanto de definir nuevos enlaces como de dar un nuevo valor a los existentes.</p> +<p><a class="p_ident" id="p-OLjh9e6hGE" href="#p-OLjh9e6hGE" tabindex="-1" role="presentation"></a>Actualmente, la única forma de asignar un valor a una asociación es usar <code>define</code>. Esta construcción actúa como una forma tanto de definir nuevos enlaces como de dar un nuevo valor a los existentes.</p> <p><a class="p_ident" id="p-ssqoqtZ2fc" href="#p-ssqoqtZ2fc" tabindex="-1" role="presentation"></a>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.</p> -<p><a class="p_ident" id="p-bmER/dL8WQ" href="#p-bmER/dL8WQ" tabindex="-1" role="presentation"></a>Agrega una forma especial <code>set</code>, similar a <code>define</code>, 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 <code>ReferenceError</code> (otro tipo de error estándar).</p> +<p><a class="p_ident" id="p-bmER/dL8WQ" href="#p-bmER/dL8WQ" tabindex="-1" role="presentation"></a>Agrega una forma especial <code>set</code>, similar a <code>define</code>, 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 <code>ReferenceError</code> (otro tipo de error estándar).</p> <p><a class="p_ident" id="p-sX9Cu8BPn/" href="#p-sX9Cu8BPn/" tabindex="-1" role="presentation"></a>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 <code>Object.<wbr>getPrototypeOf</code>, la cual devuelve el prototipo de un objeto. También recuerda que puedes utilizar <code>Object.hasOwn</code> para verificar si un objeto dado tiene una propiedad.</p> 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> 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> 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>> 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"]}