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 (`
`). +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 (`
`). {{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 ``) 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 `
`) 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

Uno
@@ -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} -The
in the
-
.
El
en el
+
.
`) o encabezados (`
`) o encabezados (`
``` @@ -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 en línea, -como un bloque, y -no del todo. +Este texto se muestra en línea, + este como un bloque y +este no se muestra. ``` {{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} -
+
Texto bonito
``` {{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; } -Ahora el texto fuerte es cursiva y gris.
+Ahora el texto fuerte está escrito en cursiva y de color gris.
``` {{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 ` ``` @@ -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}Arrastra la barra para cambiar su anchura:
Toca esta página
``` @@ -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} - + ``` @@ -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} -Nombre:
-Edad:
- +Nombre:
+Edad:
+ @@ -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 ``` @@ -627,7 +627,7 @@ Podemos usar un patrón ligeramente diferente si queremos espaciar las respuesta ```{lang: html} ``` ## 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 `