JavaScript (Semana 4)
1. ¿Qué es JavaScript?
JavaScript se creó inicialmente para "dar vida a las páginas web".
Los programas en este idioma se llaman scripts . Se pueden escribir
directamente en el HTML de una página web y ejecutarse automáticamente a
medida que se carga la página.
Los scripts se proporcionan y ejecutan como texto sin formato. No necesitan
preparación especial o compilación para ejecutarse.
En este aspecto, JavaScript es muy diferente a otro lenguaje llamado Java .
¿Por qué se llama Java Script?
Cuando se creó JavaScript, inicialmente tenía otro nombre: "LiveScript". Pero
Java era muy popular en ese momento, por lo que se decidió que ayudaría
posicionar un nuevo lenguaje como un "hermano menor" de Java.
Pero a medida que evolucionó, JavaScript se convirtió en un lenguaje
completamente independiente con su propia especificación
llamada ECMAScript , y ahora no tiene ninguna relación con Java.
Hoy en día, JavaScript puede ejecutarse no solo en el navegador, sino también
en el servidor, o en cualquier dispositivo que tenga un programa especial
llamado motor JavaScript .
El navegador tiene un motor integrado a veces llamado "máquina virtual
JavaScript".
Es bueno recordar los términos anteriores porque se utilizan en artículos para
desarrolladores en Internet. Los usaremos también. Por ejemplo, si "una función
X es compatible con V8", probablemente funcione en Chrome y Opera.
¿Cómo funcionan los motores?
Los motores son complicados. Pero lo básico es sencillo.
1. El motor (incrustado si es un navegador) lee ("analiza") el script.
2. Luego convierte ("compila") el script al lenguaje de máquina.
3. Y luego el código de la máquina se ejecuta bastante rápido.
El motor aplica optimizaciones en cada paso del proceso. Incluso observa el
script compilado mientras se ejecuta, analiza los datos que fluyen a través de él
y optimiza aún más el código de la máquina en función de ese conocimiento.
¿Qué puede hacer JavaScript en el navegador?
JavaScript moderno es un lenguaje de programación "seguro". No proporciona
acceso de bajo nivel a la memoria o la CPU, ya que fue creado inicialmente para
navegadores que no lo requieren.
Las capacidades de JavaScript dependen en gran medida del entorno en el que
se ejecuta. Por ejemplo, [Link] admite funciones que permiten a JavaScript
leer / escribir archivos arbitrarios, realizar solicitudes de red, etc.
JavaScript en el navegador puede hacer todo lo relacionado con la
manipulación de la página web, la interacción con el usuario y el servidor web.
Por ejemplo, JavaScript en el navegador puede:
Agregue nuevo HTML a la página, cambie el contenido existente,
modifique los estilos.
Reaccione a las acciones del usuario, ejecute clics del mouse,
movimientos del puntero, pulsaciones de teclas.
Envíe solicitudes a través de la red a servidores remotos, descargue y
cargue archivos (las denominadas tecnologías AJAX y COMET ).
Obtenga y configure cookies, haga preguntas al visitante, muestre
mensajes.
Recuerde los datos del lado del cliente ("almacenamiento local").
¿Qué NO PUEDE hacer JavaScript en el navegador?
Las capacidades de JavaScript en el navegador están limitadas por el bien de la
seguridad del usuario. El objetivo es evitar que una página web malintencionada
acceda a información privada o dañe los datos del usuario.
Ejemplos de tales restricciones incluyen:
JavaScript en una página web no puede leer / escribir archivos arbitrarios
en el disco duro, copiarlos o ejecutar programas. No tiene acceso directo a las
funciones del sistema operativo.
Los navegadores modernos le permiten trabajar con archivos, pero el acceso es
limitado y solo se proporciona si el usuario realiza ciertas acciones, como
"soltar" un archivo en una ventana del navegador o seleccionarlo mediante
una <input>etiqueta.
Hay formas de interactuar con la cámara / micrófono y otros dispositivos, pero
requieren el permiso explícito del usuario. Por lo tanto, una página habilitada
para JavaScript no puede habilitar furtivamente una cámara web, observar los
alrededores y enviar la información a la NSA .
Las diferentes pestañas / ventanas generalmente no se conocen entre
sí. A veces lo hacen, por ejemplo, cuando una ventana usa JavaScript para abrir
la otra. Pero incluso en este caso, es posible que JavaScript de una página no
acceda a la otra si provienen de diferentes sitios (de un dominio, protocolo o
puerto diferente).
Esto se denomina "Política del mismo origen". Para solucionarlo, ambas
páginas deben aceptar el intercambio de datos y contener un código JavaScript
especial que lo maneje. Cubriremos eso en el tutorial.
Esta limitación es, nuevamente, para la seguridad del usuario. Una página desde
la [Link] un usuario ha abierto no debe poder acceder a
otra pestaña del navegador con la URL [Link] robar información
desde allí.
JavaScript puede comunicarse fácilmente a través de la red con el
servidor de donde proviene la página actual. Pero su capacidad para recibir
datos de otros sitios / dominios está paralizada. Aunque es posible, requiere un
acuerdo explícito (expresado en encabezados HTTP) desde el lado remoto. Una
vez más, esa es una limitación de seguridad.
Estos límites no existen si JavaScript se utiliza fuera del navegador, por ejemplo,
en un servidor. Los navegadores modernos también permiten complementos /
extensiones que pueden solicitar permisos extendidos.
¿Qué hace que JavaScript sea único?
Hay al menos tres cosas buenas sobre JavaScript:
Integración completa con HTML / CSS.
Las cosas simples se hacen simplemente.
Compatible con los principales navegadores y habilitado de forma
predeterminada.
JavaScript es la única tecnología de navegador que combina estas tres cosas.
Eso es lo que hace que JavaScript sea único. Por eso es la herramienta más
extendida para crear interfaces de navegador.
Dicho esto, JavaScript también permite crear servidores, aplicaciones móviles,
etc.
Idiomas "sobre" JavaScript
La sintaxis de JavaScript no se adapta a las necesidades de todos. Diferentes
personas quieren diferentes características.
Eso es de esperar, porque los proyectos y los requisitos son diferentes para
todos.
Recientemente apareció una plétora de nuevos lenguajes, que
se transpilan (convierten) a JavaScript antes de que se ejecuten en el navegador.
Las herramientas modernas hacen que la transpilación sea muy rápida y
transparente, lo que permite a los desarrolladores codificar en otro idioma y
convertirlo automáticamente "bajo el capó".
Ejemplos de tales lenguajes:
CoffeeScript es un "azúcar sintáctico" para JavaScript. Introduce una
sintaxis más corta, lo que nos permite escribir código más claro y preciso. Por lo
general, a los desarrolladores de Ruby les gusta.
TypeScript se concentra en agregar "tipificación de datos estricta" para
simplificar el desarrollo y el soporte de sistemas complejos. Está desarrollado
por Microsoft.
Flow también agrega la escritura de datos, pero de una manera
diferente. Desarrollado por Facebook.
Dart es un lenguaje independiente que tiene su propio motor que se
ejecuta en entornos que no son de navegador (como aplicaciones móviles),
pero que también se puede transpilar a JavaScript. Desarrollado por Google.
Brython es un transpilador de Python a JavaScript que permite escribir
aplicaciones en Python puro sin JavaScript.
Hay mas. Por supuesto, incluso si usamos uno de los lenguajes transpilados,
también deberíamos saber JavaScript para comprender realmente lo que
estamos haciendo.
Resumen
JavaScript se creó inicialmente como un lenguaje solo para navegador,
pero ahora también se usa en muchos otros entornos.
Hoy en día, JavaScript tiene una posición única como el lenguaje de
navegador más ampliamente adoptado con integración completa con HTML /
CSS.
Hay muchos lenguajes que se "transpilan" a JavaScript y proporcionan
ciertas características. Se recomienda echarles un vistazo, al menos brevemente,
después de dominar JavaScript.
2. Consola de desarrollador
El código es propenso a errores. Es muy probable que cometa errores ... Oh, ¿de
qué estoy hablando? Usted está absolutamente va a cometer errores, al menos
si eres un humano y no un robot .
Pero en el navegador, los usuarios no ven errores de forma
predeterminada. Entonces, si algo sale mal en el script, no veremos qué está
roto y no podremos arreglarlo.
Para ver los errores y obtener mucha más información útil sobre los scripts, se
han integrado "herramientas de desarrollo" en los navegadores.
La mayoría de los desarrolladores se inclinan por Chrome o Firefox para el
desarrollo porque esos navegadores tienen las mejores herramientas de
desarrollo. Otros navegadores también proporcionan herramientas de
desarrollo, a veces con funciones especiales, pero normalmente se ponen al día
con Chrome o Firefox. Por lo tanto, la mayoría de los desarrolladores tienen un
navegador "favorito" y cambian a otros si el problema es específico del
navegador.
Las herramientas de desarrollo son potentes; tienen muchas características. Para
comenzar, aprenderemos cómo abrirlos, ver errores y ejecutar comandos
JavaScript.
Google Chrome
Abra la página [Link] .
Hay un error en el código JavaScript. Está oculto a los ojos de un visitante
habitual, así que abramos las herramientas de desarrollo para verlo.
Presione F12 o, si está en Mac, entonces . Cmd+Opt+J
Las herramientas de desarrollador se abrirán en la pestaña Consola de forma
predeterminada.
Se parece a esto:
El aspecto exacto de las herramientas para desarrolladores depende de su
versión de Chrome. Cambia de vez en cuando, pero debería ser similar.
Aquí podemos ver el mensaje de error de color rojo. En este caso, el
script contiene un comando "lalala" desconocido.
A la derecha, hay un enlace en el que se puede hacer clic a la
fuente [Link]:12con el número de línea donde se produjo el error.
Debajo del mensaje de error, hay un >símbolo azul . Marca una "línea de
comandos" donde podemos escribir comandos JavaScript. Presione Enter para
ejecutarlos.
Ahora podemos ver errores, y eso es suficiente para empezar. Regresaremos a
las herramientas para desarrolladores más adelante y cubriremos la depuración
con más profundidad en el capítulo Depuración en Chrome .
Entrada multilínea
Por lo general, cuando colocamos una línea de código en la consola y luego
presionamos Enter , se ejecuta.
Para insertar varias líneas, presione . De esta forma, se pueden ingresar
fragmentos largos de código JavaScript. Shift+Enter
Resumen
Las herramientas de desarrollo nos permiten ver errores, ejecutar
comandos, examinar variables y mucho más.
Se pueden abrir con la F12 mayoría de los navegadores en
Windows. Chrome para Mac necesita , Safari: (primero debe
habilitarlo). Cmd+Opt+JCmd+Opt+C
3. La etiqueta "script"
Los programas JavaScript se pueden insertar en cualquier parte de un
documento HTML con la ayuda de la <script>etiqueta.
La <script>etiqueta contiene código JavaScript que se ejecuta
automáticamente cuando el navegador procesa la etiqueta.
Marcado moderno
La <script>etiqueta tiene algunos atributos que rara vez se usan hoy en día,
pero que aún se pueden encontrar en el código antiguo:
El typeatributo:<script type=…>
El antiguo estándar HTML, HTML4, requería que un script tuviera una
extensión type. Normalmente lo era type="text/javascript". Ya no
es necesario. Además, el estándar HTML moderno cambió totalmente el
significado de este atributo. Ahora, se puede utilizar para módulos
JavaScript. Pero ese es un tema avanzado, hablaremos de módulos en
otra parte del tutorial.
El languageatributo:<script language=…>
Este atributo estaba destinado a mostrar el idioma del guión. Este
atributo ya no tiene sentido porque JavaScript es el idioma
predeterminado. No es necesario utilizarlo.
Comentarios antes y después de los guiones.
En libros y guías realmente antiguos, puede encontrar comentarios
dentro de <script>etiquetas, como esta:
<script type="text/javascript"><!--
...
//--></script>
Este truco no se usa en JavaScript moderno. Estos comentarios ocultan el
código JavaScript de los navegadores antiguos que no sabían cómo
procesar la <script>etiqueta. Dado que los navegadores lanzados en los
últimos 15 años no tienen este problema, este tipo de comentario puede
ayudarlo a identificar el código realmente antiguo.
Guiones externos
Si tenemos mucho código JavaScript, podemos ponerlo en un archivo separado.
Los archivos de secuencia de comandos se adjuntan a HTML con el srcatributo:
<script src="/path/to/[Link]"></script>
Aquí, /path/to/[Link] una ruta absoluta al script desde la raíz del
sitio. También se puede proporcionar una ruta relativa desde la página
actual. Por ejemplo, src="[Link]"significaría un archivo "[Link]"en la
carpeta actual.
También podemos proporcionar una URL completa. Por ejemplo:
<script
src="[Link]
[Link]"></script>
Para adjuntar varios scripts, use varias etiquetas:
<script src="/js/[Link]"></script>
<script src="/js/[Link]"></script>
…
Tenga en cuenta:
Como regla general, solo los scripts más simples se colocan en HTML. Los más
complejos residen en archivos separados.
El beneficio de un archivo separado es que el navegador lo descargará y lo
almacenará en su caché .
Otras páginas que hacen referencia al mismo script lo tomarán de la caché en
lugar de descargarlo, por lo que el archivo se descarga solo una vez.
Eso reduce el tráfico y acelera las páginas.
Si srcestá configurado, se ignora el contenido del script.
Una sola <script>etiqueta no puede contener tanto el srcatributo como el
código.
Esto no funcionará:
<script src="[Link]">
alert(1); // the content is ignored, because src is set
</script>
Debemos elegir entre un código externo <script src="…">o uno
regular <script>.
El ejemplo anterior se puede dividir en dos scripts para que funcione:
<script src="[Link]"></script>
<script>
alert(1);
</script>
Resumen
Podemos usar una <script>etiqueta para agregar código JavaScript a
una página.
Los atributos typey languageno son obligatorios.
Se puede insertar un script en un archivo externo <script
src="path/to/[Link]"></script>.
Hay mucho más que aprender sobre los scripts del navegador y su interacción
con la página web. Pero tengamos en cuenta que esta parte del tutorial está
dedicada al lenguaje JavaScript, por lo que no debemos distraernos con
implementaciones específicas del navegador. Usaremos el navegador como una
forma de ejecutar JavaScript, que es muy conveniente para la lectura en línea,
pero solo uno de muchos.
4. Estructura de código
Lo primero que estudiaremos son los componentes básicos del código.
Declaraciones
Las declaraciones son construcciones de sintaxis y comandos que realizan
acciones.
Ya hemos visto una declaración, alert('Hello, world!')que muestra el
mensaje "¡Hola, mundo!".
Podemos tener tantas declaraciones en nuestro código como queramos. Las
declaraciones se pueden separar con punto y coma.
Por ejemplo, aquí dividimos "Hola mundo" en dos alertas:
alert('Hello'); alert('World');
Por lo general, las declaraciones se escriben en líneas separadas para que el
código sea más legible:
alert('Hello');
alert('World');
Punto y coma
Se puede omitir un punto y coma en la mayoría de los casos cuando existe un
salto de línea.
Esto también funcionaría:
alert('Hello')
alert('World')
Aquí, JavaScript interpreta el salto de línea como un punto y coma
"implícito". Esto se denomina inserción automática de punto y coma .
En la mayoría de los casos, una nueva línea implica un punto y coma. ¡Pero "en
la mayoría de los casos" no significa "siempre"!
Hay casos en los que una nueva línea no significa un punto y coma. Por
ejemplo:
alert(3 +
+ 2);
El código se genera 6porque JavaScript no inserta punto y coma aquí. Es
intuitivamente obvio que si la línea termina con un signo más "+", entonces es
una "expresión incompleta", por lo que no se requiere el punto y coma. Y en
este caso, funciona según lo previsto.
Pero hay situaciones en las que JavaScript "falla" al asumir un punto y coma
donde realmente se necesita.
Los errores que ocurren en tales casos son bastante difíciles de encontrar y
corregir.
Un ejemplo de error
Si tiene curiosidad por ver un ejemplo concreto de tal error, consulte este
código:
[1, 2].forEach(alert)
No es necesario pensar en el significado de los
corchetes []y forEachtodavía. Los estudiaremos más tarde. Por ahora,
recuerde el resultado del código: se muestra 1entonces 2.
Ahora, agreguemos un alertantes del código y no terminemos con un punto y
coma:
alert("There will be an error")
[1, 2].forEach(alert)
Ahora, si ejecutamos el código, ¡solo alertse muestra el primero y luego
tenemos un error!
Pero todo vuelve a estar bien si agregamos un punto y coma después de alert:
alert("All fine now");
[1, 2].forEach(alert)
Ahora tenemos el mensaje "Todo bien ahora" seguido de 1y 2.
El error en la variante sin punto y coma se produce porque JavaScript no asume
un punto y coma antes de los corchetes [...].
Entonces, debido a que el punto y coma no se inserta automáticamente, el
código del primer ejemplo se trata como una sola declaración. Así es como lo
ve el motor:
alert("There will be an error")[1, 2].forEach(alert)
Pero deberían ser dos declaraciones separadas, no una. Tal fusión en este caso
es simplemente incorrecta, de ahí el error. Esto puede suceder en otras
situaciones.
Recomendamos poner punto y coma entre las declaraciones incluso si están
separadas por líneas nuevas. Esta regla es ampliamente adoptada por la
comunidad. Observemos una vez más: es posible omitir el punto y coma la
mayor parte del tiempo. Pero es más seguro, especialmente para un
principiante, usarlos.
Comentarios
A medida que pasa el tiempo, los programas se vuelven cada vez más
complejos. Es necesario agregar comentarios que describan qué hace el código
y por qué.
Los comentarios se pueden colocar en cualquier lugar de un guión. No afectan
su ejecución porque el motor simplemente los ignora.
Los comentarios de una línea comienzan con dos caracteres de barra
diagonal //.
El resto de la línea es un comentario. Puede ocupar una línea completa propia o
seguir una declaración.
Como aquí:
// This comment occupies a line of its own
alert('Hello');
alert('World'); // This comment follows the statement
Los comentarios de varias líneas comienzan con una barra diagonal y un
asterisco /*y terminan con un asterisco y una barra inclinada */.
Me gusta esto:
/* An example with two messages.
This is a multiline comment.
*/
alert('Hello');
alert('World');
El contenido de los comentarios se ignora, por lo que si ponemos código
dentro /* … */, no se ejecutará.
A veces puede resultar útil deshabilitar temporalmente una parte del código:
/* Commenting out the code
alert('Hello');
*/
alert('World');
¡Usa accesos rapidos del teclado!
En la mayoría de los editores, una línea de código se puede comentar
presionando la tecla de acceso rápido para un comentario de una sola línea y
algo como - para comentarios de varias líneas (seleccione un fragmento de
código y presione la tecla de acceso rápido). Para Mac, intente en lugar de y
en lugar de . Ctrl+/Ctrl+Shift+/CmdCtrlOptionShift
¡Los comentarios anidados no son compatibles!
Puede que no haya /*...*/dentro de otro /*...*/.
Dicho código morirá con un error:
/*
/* nested comment ?!? */
*/
alert( 'World' );
Por favor, no dudes en comentar tu código.
Los comentarios aumentan la huella general del código, pero eso no es un
problema en absoluto. Hay muchas herramientas que minimizan el código antes
de publicarlo en un servidor de producción. Quitan los comentarios para que no
aparezcan en los guiones de trabajo. Por tanto, los comentarios no tienen
ningún efecto negativo sobre la producción.
El modo moderno, "uso estricto"
Durante mucho tiempo, JavaScript evolucionó sin problemas de
compatibilidad. Se agregaron nuevas funciones al idioma, mientras que la
funcionalidad anterior no cambió.
Eso tenía la ventaja de no romper el código existente. Pero la desventaja era
que cualquier error o una decisión imperfecta de los creadores de JavaScript se
quedaba estancada en el lenguaje para siempre.
Este fue el caso hasta 2009 cuando apareció ECMAScript 5 (ES5). Añadió nuevas
funciones al lenguaje y modificó algunas de las existentes. Para que el código
antiguo siga funcionando, la mayoría de estas modificaciones están
desactivadas de forma predeterminada. Es necesario que les permita
explícitamente de una directiva especial: "use strict".
"Usar estricto"
La directiva se parece a una cadena: "use strict"o 'use strict'. Cuando se
encuentra en la parte superior de un guión, todo el guión funciona de manera
“moderna”.
Por ejemplo:
"use strict";
// this code works the modern way
...
Muy pronto aprenderemos funciones (una forma de agrupar comandos), así que
observemos de antemano que "use strict"se pueden poner al principio de
una función. Hacer eso habilita el modo estricto solo en esa función. Pero
generalmente la gente lo usa para todo el guión.
Asegúrese de que "use estricto" esté en la parte superior
Asegúrese de que "use strict"esté en la parte superior de sus scripts; de lo
contrario, es posible que el modo estricto no esté habilitado.
El modo estricto no está habilitado aquí:
alert("some code");
// "use strict" below is ignored--it must be at the top
"use strict";
// strict mode is not activated
Solo pueden aparecer comentarios arriba "use strict".
No hay forma de cancelar use strict
No existe una directiva como la "no use strict"que revierte el motor a su
antiguo comportamiento.
Una vez que ingresamos al modo estricto, no hay vuelta atrás.
Consola del navegador
Cuando usa una consola de desarrollador para ejecutar código, tenga en cuenta
que no lo hace use strictde forma predeterminada.
A veces, cuando use strictmarca la diferencia, obtendrá resultados
incorrectos.
Entonces, ¿cómo en realidad use stricten la consola?
Primero, puede intentar presionar para ingresar varias líneas y colocarlas en la
parte superior, así: Shift+Enter use strict
'use strict'; <Shift+Enter for a newline>
// ...your code
<Enter to run>
Funciona en la mayoría de los navegadores, a saber, Firefox y Chrome.
Si no es así, por ejemplo, en un navegador antiguo, hay una forma fea pero
confiable de asegurarse use strict. Ponlo dentro de este tipo de envoltorio:
(function() {
'use strict';
// ...your code here...
})()
¿Deberíamos "usar estricto"?
La pregunta puede parecer obvia, pero no lo es.
Se podría recomendar comenzar los guiones con "use strict"… ¿Pero sabes
qué es genial?
El JavaScript moderno admite "clases" y "módulos", estructuras de lenguaje
avanzadas (seguramente llegaremos a ellas), que se habilitan use
strictautomáticamente. Por lo tanto, no necesitamos agregar la "use
strict"directiva, si las usamos.
Entonces, por ahora "use strict";es un invitado bienvenido en la parte
superior de sus guiones. Más tarde, cuando su código esté en clases y módulos,
puede omitirlo.
A partir de ahora, tenemos que conocerlo use stricten general.
Variables
La mayoría de las veces, una aplicación JavaScript necesita trabajar con
información. A continuación se muestran dos ejemplos:
1. Una tienda en línea: la información puede incluir productos que se
venden y un carrito de compras.
2. Una aplicación de chat: la información puede incluir usuarios, mensajes y
mucho más.
Las variables se utilizan para almacenar esta información.
Una variable
Una variable es un "almacenamiento con nombre" para datos. Podemos usar
variables para almacenar golosinas, visitantes y otros datos.
Para crear una variable en JavaScript, use la letpalabra clave.
La siguiente declaración crea (en otras palabras: declara ) una variable con el
nombre "mensaje":
let message;
Ahora, podemos poner algunos datos en él usando el operador de asignación =:
let message;
message = 'Hello'; // store the string
La cadena ahora se guarda en el área de memoria asociada con la
variable. Podemos acceder a él usando el nombre de la variable:
let message;
message = 'Hello!';
alert(message); // shows the variable content
Para ser concisos, podemos combinar la declaración de variable y la asignación
en una sola línea:
let message = 'Hello!'; // define the variable and assign the
value
alert(message); // Hello!
También podemos declarar múltiples variables en una línea:
let user = 'John', age = 25, message = 'Hello';
Puede parecer más corto, pero no lo recomendamos. En aras de una mejor
legibilidad, utilice una sola línea por variable.
La variante multilínea es un poco más larga, pero más fácil de leer:
let user = 'John';
let age = 25;
let message = 'Hello';
Algunas personas también definen múltiples variables en este estilo de líneas
múltiples:
let user = 'John',
age = 25,
message = 'Hello';
... O incluso en el estilo de "coma primero":
let user = 'John'
, age = 25
, message = 'Hello';
Técnicamente, todas estas variantes hacen lo mismo. Entonces, es una cuestión
de gusto y estética personal.
var en vez de let
En scripts más antiguos, también puede encontrar otra palabra clave:
en varlugar de let:
var message = 'Hello';
La varpalabra clave es casi la misma que let. También declara una variable,
pero de una manera ligeramente diferente, de la "vieja escuela".
Hay diferencias sutiles entre lety var, pero todavía no nos importan. Los
cubriremos en detalle en el capítulo La vieja "var" .
Una analogía de la vida real
Podemos captar fácilmente el concepto de "variable" si lo imaginamos como
una "caja" de datos, con una etiqueta con un nombre exclusivo.
Por ejemplo, la variable messagese puede imaginar como un cuadro
etiquetado "message"con el valor "Hello!"en él:
Podemos poner cualquier valor en la casilla.
También podemos cambiarlo tantas veces como queramos:
let message;
message = 'Hello!';
message = 'World!'; // value changed
alert(message);
Cuando se cambia el valor, los datos antiguos se eliminan de la variable:
También podemos declarar dos variables y copiar datos de una a otra.
let hello = 'Hello world!';
let message;
// copy 'Hello world' from hello into message
message = hello;
// now two variables hold the same data
alert(hello); // Hello world!
alert(message); // Hello world!
Declarar dos veces provoca un error
Una variable debe declararse solo una vez.
Una declaración repetida de la misma variable es un error:
let message = "This";
// repeated 'let' leads to an error
let message = "That"; // SyntaxError: 'message' has already been
declared
Por lo tanto, deberíamos declarar una variable una vez y luego referirnos a ella
sin ella let.
Lenguajes funcionales
Es interesante notar que existen lenguajes de programación funcionales ,
como Scala o Erlang, que prohíben cambiar los valores de las variables.
En dichos lenguajes, una vez que el valor se almacena "en la caja", está ahí para
siempre. Si necesitamos almacenar algo más, el lenguaje nos obliga a crear una
nueva caja (declarar una nueva variable). No podemos reutilizar el anterior.
Aunque pueda parecer un poco extraño a primera vista, estos lenguajes son
bastante capaces de desarrollarse seriamente. Más que eso, hay áreas como los
cálculos paralelos donde esta limitación confiere ciertos beneficios. Se
recomienda estudiar ese idioma (incluso si no planea usarlo pronto) para
ampliar la mente.
Denominación variable
Hay dos limitaciones en los nombres de variables en JavaScript:
1. El nombre debe contener solo letras, dígitos o los símbolos $y _.
2. El primer carácter no debe ser un dígito.
Ejemplos de nombres válidos:
let userName;
let test123;
Cuando el nombre contiene varias palabras, se usa comúnmente camelCase . Es
decir: las palabras van uno tras otro, cada palabra excepto la primera partida
con una letra mayúscula: myVeryLongName.
Lo que es interesante: el signo de dólar '$'y el guión bajo '_'también se
pueden usar en los nombres. Son símbolos regulares, como letras, sin ningún
significado especial.
Estos nombres son válidos:
let $ = 1; // declared a variable with the name "$"
let _ = 2; // and now a variable with the name "_"
alert($ + _); // 3
Ejemplos de nombres de variables incorrectos:
let 1a; // cannot start with a digit
let my-name; // hyphens '-' aren't allowed in the name
El caso importa
Las variables nombradas appley AppLEson dos variables diferentes.
Se permiten letras no latinas, pero no se recomiendan
Es posible utilizar cualquier idioma, incluidas letras cirílicas o incluso jeroglíficos,
como este:
let имя = '...';
let 我 = '...';
Técnicamente, no hay ningún error aquí. Estos nombres están permitidos, pero
existe una convención internacional para usar el inglés en los nombres de
variables. Incluso si estamos escribiendo un guión pequeño, es posible que
tenga una larga vida por delante. Es posible que las personas de otros países
necesiten leerlo en algún momento.
Nombres reservados
Existe una lista de palabras reservadas , que no se pueden utilizar como
nombres de variables porque son utilizadas por el propio idioma.
Por ejemplo: let, class, return, y functionestán reservados.
El siguiente código da un error de sintaxis:
let let = 5; // can't name a variable "let", error!
let return = 5; // also can't name it "return", error!
Una tarea sin use strict
Normalmente, necesitamos definir una variable antes de usarla. Pero en los
viejos tiempos, era técnicamente posible crear una variable mediante una mera
asignación del valor sin usar let. Esto todavía funciona ahora si no
ponemos use strictnuestros scripts para mantener la compatibilidad con los
scripts antiguos.
// note: no "use strict" in this example
num = 5; // the variable "num" is created if it didn't exist
alert(num); // 5
Esta es una mala práctica y causaría un error en modo estricto:
"use strict";
num = 5; // error: num is not defined
Constantes
Para declarar una variable constante (que no cambia), use en constlugar
de let:
const myBirthday = '18.04.1982';
Las variables declaradas mediante constse denominan “constantes”. No se
pueden reasignar. Un intento de hacerlo provocaría un error:
const myBirthday = '18.04.1982';
myBirthday = '01.01.2001'; // error, can't reassign the
constant!
Cuando un programador está seguro de que una variable nunca cambiará,
puede declararla con constpara garantizar y comunicar claramente ese hecho a
todos.
Constantes mayúsculas
Existe una práctica generalizada de utilizar constantes como alias para valores
difíciles de recordar que se conocen antes de la ejecución.
Estas constantes se nombran con letras mayúsculas y guiones bajos.
Por ejemplo, creemos constantes para los colores en el llamado formato "web"
(hexadecimal):
const COLOR_RED = "#F00";
const COLOR_GREEN = "#0F0";
const COLOR_BLUE = "#00F";
const COLOR_ORANGE = "#FF7F00";
// ...when we need to pick a color
let color = COLOR_ORANGE;
alert(color); // #FF7F00
Beneficios:
COLOR_ORANGEes mucho más fácil de recordar que "#FF7F00".
Es mucho más fácil escribir mal "#FF7F00"que COLOR_ORANGE.
Al leer el código, COLOR_ORANGEes mucho más significativo que #FF7F00.
¿Cuándo deberíamos usar mayúsculas para una constante y cuándo deberíamos
nombrarla normalmente? Dejémoslo claro.
Ser una "constante" simplemente significa que el valor de una variable nunca
cambia. Pero hay constantes que se conocen antes de la ejecución (como un
valor hexadecimal para rojo) y hay constantes que se calculan en tiempo de
ejecución, durante la ejecución, pero que no cambian después de su asignación
inicial.
Por ejemplo:
const pageLoadTime = /* time taken by a webpage to load */;
El valor de pageLoadTimeno se conoce antes de la carga de la página, por lo
que se denomina normalmente. Pero sigue siendo una constante porque no
cambia después de la asignación.
En otras palabras, las constantes con nombre en mayúsculas solo se utilizan
como alias para valores "codificados".
Nombra las cosas bien
Hablando de variables, hay una cosa más extremadamente importante.
El nombre de una variable debe tener un significado claro y obvio, que describa
los datos que almacena.
La denominación de variables es una de las habilidades más importantes y
complejas de la programación. Un vistazo rápido a los nombres de las variables
puede revelar qué código fue escrito por un principiante versus un
desarrollador experimentado.
En un proyecto real, la mayor parte del tiempo se dedica a modificar y ampliar
una base de código existente en lugar de escribir algo completamente separado
desde cero. Cuando volvemos a algún código después de hacer otra cosa por
un tiempo, es mucho más fácil encontrar información que esté bien
etiquetada. O, en otras palabras, cuando las variables tienen buen nombre.
Dedique tiempo a pensar en el nombre correcto para una variable antes de
declararla. Hacerlo le compensará generosamente.
Algunas reglas que conviene seguir son:
Utilice nombres legibles por humanos como userNameo shoppingCart.
Manténgase alejado de las abreviaturas o nombres cortos como a, b, c, a
menos que realmente sepa lo que está haciendo.
Haga nombres lo más descriptivos y concisos. Ejemplos de malos
nombres son datay value. Tales nombres no dicen nada. Solo está bien usarlos
si el contexto del código hace que sea excepcionalmente obvio a qué datos o
valor hace referencia la variable.
Acuerde los términos dentro de su equipo y en su propia mente. Si un
visitante del sitio se llama "usuario", entonces debemos nombrar las variables
relacionadas currentUsero en newUserlugar
de currentVisitoro newManInTown.
¿Suena simple? De hecho lo es, pero crear nombres de variables descriptivos y
concisos en la práctica no lo es. Ve a por ello.
¿Reutilizar o crear?
Y la última nota. Hay algunos programadores perezosos que, en lugar de
declarar nuevas variables, tienden a reutilizar las existentes.
Como resultado, sus variables son como cajas en las que la gente arroja
diferentes cosas sin cambiar sus pegatinas. ¿Qué hay dentro de la caja
ahora? ¿Quién sabe? Tenemos que acercarnos y comprobarlo.
Estos programadores ahorran un poco en la declaración de variables pero
pierden diez veces más en la depuración.
Una variable adicional es buena, no mala.
Los navegadores y minificadores de JavaScript modernos optimizan el código lo
suficientemente bien, por lo que no crearán problemas de rendimiento. El uso
de diferentes variables para diferentes valores puede incluso ayudar al motor a
optimizar su código.
Resumen
Podemos declarar variables para almacenar datos mediante el uso de
los var, leto constpalabras clave.
let - es una declaración de variable moderna.
var- es una declaración de variable de la vieja escuela. Normalmente no
lo usamos en absoluto, pero cubriremos las diferencias sutiles leten el
capítulo La vieja "var" , por si las necesita.
const- es como let, pero el valor de la variable no se puede cambiar.
Las variables deben nombrarse de manera que nos permitan comprender
fácilmente su contenido.
5. Tipos de datos
Un valor en JavaScript es siempre de un tipo determinado. Por ejemplo, una
cadena o un número.
Hay ocho tipos de datos básicos en JavaScript. Aquí los cubriremos en general y
en los próximos capítulos hablaremos de cada uno de ellos en detalle.
Podemos poner cualquier tipo en una variable. Por ejemplo, una variable puede
ser en un momento una cadena y luego almacenar un número:
// no error
let message = "hello";
message = 123456;
Los lenguajes de programación que permiten tales cosas, como JavaScript, se
denominan "tipados dinámicamente", lo que significa que existen tipos de
datos, pero las variables no están vinculadas a ninguno de ellos.
Número
let n = 123;
n = 12.345;
El tipo de número representa números enteros y de coma flotante.
Hay muchas operaciones para números, por ejemplo, multiplicación *,
división /, suma +, resta -, etc.
Además de los números regulares, no son los llamados “valores numéricos
especiales”, que también pertenecen a este tipo de datos: Infinity, -
Infinityy NaN.
Infinityrepresenta el infinito matemático ∞. Es un valor especial que es
mayor que cualquier número.
Podemos obtenerlo como resultado de la división por cero:
alert( 1 / 0 ); // Infinity
O simplemente haga referencia a él directamente:
alert( Infinity ); // Infinity
NaNrepresenta un error de cálculo. Es el resultado de una operación
matemática incorrecta o indefinida, por ejemplo:
alert( "not a number" / 2 ); // NaN, such division is erroneous
NaNes pegajoso. Cualquier otra operación sobre NaNdevoluciones NaN:
alert( "not a number" / 2 + 5 ); // NaN
Entonces, si hay un NaNlugar en una expresión matemática, se propaga al
resultado completo.
Las operaciones matemáticas son seguras
Hacer matemáticas es "seguro" en JavaScript. Podemos hacer cualquier cosa:
dividir por cero, tratar cadenas no numéricas como números, etc.
El guión nunca se detendrá con un error fatal ("morir"). En el peor de los casos,
obtendremos NaNel resultado.
Los valores numéricos especiales pertenecen formalmente al tipo "número". Por
supuesto, no son números en el sentido común de esta palabra.
Veremos más sobre cómo trabajar con números en el capítulo Números .
Empezando
En JavaScript, el tipo "número" no puede representar valores enteros mayores
que (eso es ) o menores que para los negativos. Es una limitación técnica
provocada por su representación interna.(253-1)9007199254740991-(253-1)
Para la mayoría de los propósitos, eso es suficiente, pero a veces necesitamos
números realmente grandes, por ejemplo, para criptografía o marcas de tiempo
con precisión de microsegundos.
BigInt type se agregó recientemente al lenguaje para representar números
enteros de longitud arbitraria.
Un BigIntvalor se crea agregando nal final de un número entero:
// the "n" at the end means it's a BigInt
const bigInt = 1234567890123456789012345678901234567890n;
Como los BigIntnúmeros rara vez se necesitan, no los cubrimos aquí, pero les
dedicamos un capítulo separado BigInt . Léalo cuando necesite números tan
grandes.
Problemas de compatibilidad
En este momento, BigIntse admite en Firefox / Chrome / Edge, pero no en
Safari / IE.
Cuerda
Una cadena en JavaScript debe estar entre comillas.
let str = "Hello";
let str2 = 'Single quotes are ok too';
let phrase = `can embed another ${str}`;
En JavaScript, hay 3 tipos de citas.
1. Las comillas dobles: "Hello".
2. Las comillas simples: 'Hello'.
3. Acentos abiertos: `Hello`.
Las comillas simples y dobles son comillas "simples". Prácticamente no hay
diferencia entre ellos en JavaScript.
Las comillas inversas son comillas de "funcionalidad ampliada". Nos permiten
incrustar variables y expresiones en una cadena envolviéndolas ${…}, por
ejemplo:
let name = "John";
// embed a variable
alert( `Hello, ${name}!` ); // Hello, John!
// embed an expression
alert( `the result is ${1 + 2}` ); // the result is 3
La expresión dentro ${…}se evalúa y el resultado se convierte en parte de la
cadena. Podemos poner cualquier cosa allí: una variable como nameo una
expresión aritmética como 1 + 2o algo más complejo.
Tenga en cuenta que esto solo se puede hacer con comillas invertidas. ¡Otras
citas no tienen esta función de incrustación!
alert( "the result is ${1 + 2}" ); // the result is ${1 + 2}
(double quotes do nothing)
Cubriremos las cadenas más a fondo en el capítulo Cuerdas .
No hay ningún tipo de carácter .
En algunos idiomas, hay un tipo de "carácter" especial para un solo carácter. Por
ejemplo, en el lenguaje C y en Java se llama "char".
En JavaScript, no existe tal tipo. Sólo hay un tipo de: string. Una cadena puede
constar de cero caracteres (estar vacía), un carácter o muchos de ellos.
Booleano (tipo lógico)
El tipo booleano tiene solo dos valores: truey false.
Este tipo se usa comúnmente para almacenar valores de sí / no: truesignifica
"sí, correcto" y falsesignifica "no, incorrecto".
Por ejemplo:
let nameFieldChecked = true; // yes, name field is checked
let ageFieldChecked = false; // no, age field is not checked
Los valores booleanos también se obtienen como resultado de comparaciones:
let isGreater = 4 > 1;
alert( isGreater ); // true (the comparison result is "yes")
Cubriremos los valores booleanos con más profundidad en el
capítulo Operadores lógicos .
El valor "nulo"
El nullvalor especial no pertenece a ninguno de los tipos descritos
anteriormente.
Forma un tipo separado propio que contiene solo el nullvalor:
let age = null;
En JavaScript, nullno es una "referencia a un objeto inexistente" o un "puntero
nulo" como en algunos otros idiomas.
Es solo un valor especial que representa "nada", "vacío" o "valor desconocido".
El código anterior establece que agese desconoce.
El valor "indefinido"
El valor especial undefinedtambién se distingue. Hace un tipo propio,
como null.
El significado de undefinedes "valor no asignado".
Si se declara una variable, pero no se asigna, entonces su valor es undefined:
let age;
alert(age); // shows "undefined"
Técnicamente, es posible asignar explícitamente undefineda una variable:
let age = 100;
// change the value to undefined
age = undefined;
alert(age); // "undefined"
… Pero no recomendamos hacer eso. Normalmente, se utiliza nullpara asignar
un valor "vacío" o "desconocido" a una variable, mientras que undefinedse
reserva como valor inicial predeterminado para cosas no asignadas.
Objetos y símbolos
El objecttipo es especial.
Todos los demás tipos se denominan "primitivos" porque sus valores pueden
contener solo una cosa (ya sea una cadena, un número o lo que sea). Por el
contrario, los objetos se utilizan para almacenar colecciones de datos y
entidades más complejas.
Siendo así de importantes, los objetos merecen un tratamiento especial. Nos
ocuparemos de ellos más adelante en el capítulo Objetos , después de que
aprendamos más sobre las primitivas.
El symboltipo se utiliza para crear identificadores únicos para objetos. Tenemos
que mencionarlo aquí en aras de la integridad, pero también posponer los
detalles hasta que sepamos los objetos.
El tipo de operador
El typeofoperador devuelve el tipo de argumento. Es útil cuando queremos
procesar valores de diferentes tipos de manera diferente o simplemente
queremos hacer una verificación rápida.
Admite dos formas de sintaxis:
1. Como operador: typeof x.
2. En función: typeof(x).
En otras palabras, funciona con paréntesis o sin ellos. El resultado es el mismo.
La llamada a typeof xdevuelve una cadena con el nombre del tipo:
typeof undefined // "undefined"
typeof 0 // "number"
typeof 10n // "bigint"
typeof true // "boolean"
typeof "foo" // "string"
typeof Symbol("id") // "symbol"
typeof Math // "object" (1)
typeof null // "object" (2)
typeof alert // "function" (3)
Las últimas tres líneas pueden necesitar una explicación adicional:
1. Mathes un objeto integrado que proporciona operaciones
matemáticas. Lo aprenderemos en el capítulo Números . Aquí, sirve solo como
un ejemplo de un objeto.
2. El resultado de typeof nulles "object". Ese es un error
de typeofcomportamiento reconocido oficialmente , que proviene de los
primeros días de JavaScript y se mantiene por
compatibilidad. Definitivamente, nullno es un objeto. Es un valor especial con
un tipo separado propio.
3. El resultado de typeof alertes "function", porque alertes una
función. Estudiaremos las funciones en los siguientes capítulos, donde también
veremos que no hay un tipo de "función" especial en JavaScript. Las funciones
pertenecen al tipo de objeto. Pero los typeoftrata de manera diferente,
regresando "function". Eso también proviene de los primeros días de
JavaScript. Técnicamente, tal comportamiento no es correcto, pero puede ser
conveniente en la práctica.
Resumen
Hay 8 tipos de datos básicos en JavaScript.
numberpara números de cualquier tipo: entero o de punto flotante, los
enteros están limitados por .±(253-1)
bigint es para números enteros de longitud arbitraria.
stringpara cuerdas. Una cadena puede tener cero o más caracteres, no
hay un tipo de carácter único independiente.
booleanpara true/ false.
nullpara valores desconocidos: un tipo independiente que tiene un solo
valor null.
undefinedpara valores no asignados: un tipo independiente que tiene
un solo valor undefined.
object para estructuras de datos más complejas.
symbol para identificadores únicos.
El typeofoperador nos permite ver qué tipo está almacenado en una variable.
Dos formas: typeof xo typeof(x).
Devuelve una cadena con el nombre del tipo, como "string".
Para nulldevoluciones "object": este es un error en el idioma, en
realidad no es un objeto.
6. Interacción: alerta, aviso, confirmación
Como vamos a estar utilizando el navegador como nuestro entorno de
demostración, vamos a ver un par de funciones para interactuar con el
usuario: alert, prompty confirm.
alerta
Este ya lo hemos visto. Muestra un mensaje y espera a que el usuario presione
“OK”.
Por ejemplo:
alert("Hello");
La mini ventana con el mensaje se llama ventana modal . La palabra “modal”
significa que el visitante no puede interactuar con el resto de la página,
presionar otros botones, etc., hasta que no se haya ocupado de la ventana. En
este caso, hasta que presionen "Aceptar".
rápido
La función promptacepta dos argumentos:
result = prompt(title, [default]);
Muestra una ventana modal con un mensaje de texto, un campo de entrada
para el visitante y los botones Aceptar / Cancelar.
title
El texto que se mostrará al visitante.
default
Un segundo parámetro opcional, el valor inicial para el campo de
entrada.
Los corchetes en la sintaxis [...]
Los corchetes defaulten la sintaxis anterior indican que el parámetro es
opcional, no obligatorio.
El visitante puede escribir algo en el campo de entrada del mensaje y presionar
OK. Luego obtenemos ese texto en formato result. O pueden cancelar la
entrada presionando Cancelar o presionando la Esc tecla, luego
obtenemos nullcomo result.
La llamada a promptdevuelve el texto del campo de entrada o nullsi se canceló
la entrada.
Por ejemplo:
let age = prompt('How old are you?', 100);
alert(`You are ${age} years old!`); // You are 100 years old!
En IE: proporcione siempre un default
El segundo parámetro es opcional, pero si no lo proporcionamos, Internet
Explorer insertará el texto "undefined"en el mensaje.
Ejecute este código en Internet Explorer para ver:
let test = prompt("Test");
Por lo tanto, para que las indicaciones se vean bien en IE, recomendamos
proporcionar siempre el segundo argumento:
let test = prompt("Test", ''); // <-- for IE
confirmar
La sintaxis:
result = confirm(question);
La función confirmmuestra una ventana modal con questionay dos botones:
Aceptar y Cancelar.
El resultado es truesi se presiona OK y en falsecaso contrario.
Por ejemplo:
let isBoss = confirm("Are you the boss?");
alert( isBoss ); // true if OK is pressed
Resumen
Cubrimos 3 funciones específicas del navegador para interactuar con los
visitantes:
alert
muestra un mensaje.
prompt
muestra un mensaje pidiendo al usuario que ingrese texto. Devuelve el
texto o, si el botón Cancelar o Esc se hace clic en, null.
confirm
muestra un mensaje y espera a que el usuario presione “Aceptar” o
“Cancelar”. Vuelve truepara Aceptar y falseCancelar / Esc .
Todos estos métodos son modales: pausan la ejecución del script y no permiten
que el visitante interactúe con el resto de la página hasta que se cierre la
ventana.
Hay dos limitaciones compartidas por todos los métodos anteriores:
1. La ubicación exacta de la ventana modal la determina el navegador. Por
lo general, está en el centro.
2. El aspecto exacto de la ventana también depende del navegador. No
podemos modificarlo.
Ese es el precio de la sencillez. Hay otras formas de mostrar ventanas más
agradables y una interacción más rica con el visitante, pero si las “campanas y
silbidos” no importan mucho, estos métodos funcionan bien.
7. Conversiones de tipo
La mayoría de las veces, los operadores y las funciones convierten
automáticamente los valores que se les asignan al tipo correcto.
Por ejemplo, alertconvierte automáticamente cualquier valor en una cadena
para mostrarlo. Las operaciones matemáticas convierten valores en números.
También hay casos en los que necesitamos convertir explícitamente un valor al
tipo esperado.
Sin hablar de objetos todavía
En este capítulo, no cubriremos objetos. Por ahora solo hablaremos de
primitivas.
Más adelante, después de aprender acerca de los objetos, en el
capítulo Conversión de objeto a primitivo veremos cómo encajan los objetos.
Conversión de cadenas
La conversión de cadenas ocurre cuando necesitamos la forma de cadena de un
valor.
Por ejemplo, lo alert(value)hace para mostrar el valor.
También podemos llamar a la String(value)función para convertir un valor en
una cadena:
let value = true;
alert(typeof value); // boolean
value = String(value); // now value is a string "true"
alert(typeof value); // string
La conversión de cadenas es más obvia. A se falseconvierte "false",
se nullconvierte "null", etc.
Conversión numérica
La conversión numérica ocurre en funciones y expresiones matemáticas
automáticamente.
Por ejemplo, cuando la división /se aplica a no números:
alert( "6" / "2" ); // 3, strings are converted to numbers
Podemos usar la Number(value)función para convertir explícitamente valuea
en un número:
let str = "123";
alert(typeof str); // string
let num = Number(str); // becomes a number 123
alert(typeof num); // number
La conversión explícita generalmente se requiere cuando leemos un valor de
una fuente basada en cadenas como un formulario de texto, pero esperamos
que se ingrese un número.
Si la cadena no es un número válido, el resultado de dicha conversión
es NaN. Por ejemplo:
let age = Number("an arbitrary string instead of a number");
alert(age); // NaN, conversion failed
Reglas de conversión numérica:
Valor Se convierte en ...
undefined NaN
null 0
true and fals
1 y 0
e
Se eliminan los espacios en blanco del principio y del
final. Si la cadena restante está vacía, el resultado es 0. De
string
lo contrario, el número se "lee" de la cadena. Un error
da NaN.
Ejemplos:
alert( Number(" 123 ") ); // 123
alert( Number("123z") ); // NaN (error reading a number at
"z")
alert( Number(true) ); // 1
alert( Number(false) ); // 0
Tenga en cuenta que nully se undefinedcomporta de manera diferente aquí:
se nullconvierte en cero mientras se undefinedconvierte en NaN.
La mayoría de los operadores matemáticos también realizan dicha conversión,
lo veremos en el próximo capítulo.
Conversión booleana
La conversión booleana es la más simple.
Sucede en operaciones lógicas (más adelante veremos pruebas de condición y
otras cosas similares) pero también se puede realizar explícitamente con una
llamada a Boolean(value).
La regla de conversión:
Los valores que son intuitivamente “vacío”, como 0, una cadena
vacía, null, undefined, y NaN, se hacen false.
Se vuelven otros valores true.
Por ejemplo:
alert( Boolean(1) ); // true
alert( Boolean(0) ); // false
alert( Boolean("hello") ); // true
alert( Boolean("") ); // false
Tenga en cuenta: la cadena con cero "0"estrue
Algunos lenguajes (concretamente PHP) tratan "0"como false. Pero en
JavaScript, una cadena no vacía es siempre true.
alert( Boolean("0") ); // true
alert( Boolean(" ") ); // spaces, also true (any non-empty
string is true)
Resumen
Las tres conversiones de tipo más utilizadas son cadena, numeración y
booleana.
String Conversion- Ocurre cuando mostramos algo. Se puede realizar
con String(value). La conversión a cadena suele ser obvia para valores
primitivos.
Numeric Conversion- Ocurre en operaciones matemáticas. Se puede realizar
con Number(value).
La conversión sigue las reglas:
Valor Se convierte en ...
undefined NaN
null 0
true / fals
1 / 0
e
La cadena se lee "tal cual", los espacios en blanco de ambos lados se
string
ignoran. Una cadena vacía se convierte en 0. Un error da NaN.
Boolean Conversion- Ocurre en operaciones lógicas. Se puede realizar
con Boolean(value).
Sigue las reglas:
Valor Se convierte en ...
0, null, undefined, NaN,"" false
cualquier otro valor true
La mayoría de estas reglas son fáciles de entender y memorizar. Las notables
excepciones en las que las personas suelen cometer errores son:
undefinedes NaNcomo un número, no 0.
"0"y las cadenas de solo espacio como " "son verdaderas como
booleanas.
Los objetos no están cubiertos aquí. Volveremos a ellos más adelante en el
capítulo Conversión de objeto a primitivo que se dedica exclusivamente a
objetos después de que aprendamos cosas más básicas sobre JavaScript.
8. Operadores básicos, matemáticas
Conocemos a muchos operadores de la escuela. Son cosas como sumas +,
multiplicaciones *, restas -, etc.
En este capítulo, comenzaremos con operadores simples, luego nos
concentraremos en aspectos específicos de JavaScript, que no se tratan en la
aritmética escolar.
Términos: "unario", "binario", "operando"
Antes de continuar, comprendamos alguna terminología común.
Un operando : es a lo que se aplican los operadores. Por ejemplo, en la
multiplicación de 5 * 2hay dos operandos: el operando izquierdo es 5y el
operando derecho es 2. A veces, la gente llama a estos "argumentos" en lugar
de "operandos".
Un operador es unario si tiene un solo operando. Por ejemplo, la
negación unaria -invierte el signo de un número:
let x = 1;
x = -x;
alert( x ); // -1, unary negation was applied
Un operador es binario si tiene dos operandos. El mismo signo negativo
también existe en forma binaria:
let x = 1, y = 3;
alert( y - x ); // 2, binary minus subtracts values
Formalmente, en los ejemplos anteriores tenemos dos operadores diferentes
que comparten el mismo símbolo: el operador de negación, un operador unario
que invierte el signo, y el operador de resta, un operador binario que resta un
número de otro.
Matemáticas
Se admiten las siguientes operaciones matemáticas:
Además +,
la resta -,
multiplicación *,
división /,
Resto %,
Exponenciación **.
Los cuatro primeros son sencillos, mientras que %y **necesitan unas palabras
sobre ellos.
Recordatorio %
El operador restante %, a pesar de su apariencia, no está relacionado con los
porcentajes.
El resultado de a % bes el resto de la división entera de apor b.
Por ejemplo:
alert( 5 % 2 ); // 1, a remainder of 5 divided by 2
alert( 8 % 3 ); // 2, a remainder of 8 divided by 3
Exponenciación **
El operador de exponenciación se a ** bmultiplica apor sí mismo b.
Por ejemplo:
alert( 2 ** 2 ); // 4 (2 multiplied by itself 2 times)
alert( 2 ** 3 ); // 8 (2 * 2 * 2, 3 times)
alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2, 4 times)
Matemáticamente, la potenciación se define también para números no
enteros. Por ejemplo, una raíz cuadrada es una exponenciación por 1/2:
alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square
root)
alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic
root)
Concatenación de cadenas con binario +
Conozcamos las características de los operadores de JavaScript que van más allá
de la aritmética escolar.
Normalmente, el operador más +suma números.
Pero, si el binario +se aplica a cadenas, las fusiona (las concatena):
let s = "my" + "string";
alert(s); // mystring
Tenga en cuenta que si alguno de los operandos es una cadena, el otro también
se convierte en una cadena.
Por ejemplo:
alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"
Mira, no importa si el primer operando es una cadena o el segundo.
Aquí hay un ejemplo más complejo:
alert(2 + 2 + '1' ); // "41" and not "221"
Aquí, los operadores trabajan uno tras otro. El primero +suma dos números, por
lo que devuelve 4, luego el siguiente le +agrega la cadena 1, por lo que es
como 4 + '1' = 41.
El binario +es el único operador que admite cadenas de esa manera. Otros
operadores aritméticos funcionan solo con números y siempre convierten sus
operandos en números.
Aquí está la demostración para restar y dividir:
alert( 6 - '2' ); // 4, converts '2' to a number
alert( '6' / '2' ); // 3, converts both operands to numbers
Conversión numérica, unaria +
El plus +existe en dos formas: la forma binaria que usamos anteriormente y la
forma unaria.
El unario más o, en otras palabras, el operador más +aplicado a un solo valor, no
hace nada a los números. Pero si el operando no es un número, el unario más lo
convierte en un número.
Por ejemplo:
// No effect on numbers
let x = 1;
alert( +x ); // 1
let y = -2;
alert( +y ); // -2
// Converts non-numbers
alert( +true ); // 1
alert( +"" ); // 0
Realmente hace lo mismo que Number(...), pero es más corto.
La necesidad de convertir cadenas en números surge con mucha frecuencia. Por
ejemplo, si obtenemos valores de campos de formulario HTML, generalmente
son cadenas. ¿Y si queremos sumarlos?
El binario más los agregaría como cadenas:
let apples = "2";
let oranges = "3";
alert( apples + oranges ); // "23", the binary plus concatenates
strings
Si queremos tratarlos como números, necesitamos convertirlos y luego
sumarlos:
let apples = "2";
let oranges = "3";
// both values converted to numbers before the binary plus
alert( +apples + +oranges ); // 5
// the longer variant
// alert( Number(apples) + Number(oranges) ); // 5
Desde el punto de vista de un matemático, la abundancia de ventajas puede
parecer extraña. Pero desde el punto de vista de un programador, no hay nada
especial: las ventajas unarias se aplican primero, convierten cadenas en números
y luego el signo más binario las suma.
¿Por qué se aplican las ventajas unarias a los valores antes que a los
binarios? Como veremos, eso se debe a su mayor precedencia .
Precedencia del operador
Si una expresión tiene más de un operador, el orden de ejecución se define por
su precedencia o, en otras palabras, el orden de prioridad predeterminado de
los operadores.
De la escuela, todos sabemos que la multiplicación en la expresión 1 + 2 *
2debe calcularse antes de la suma. Esa es exactamente la cuestión de la
precedencia. Se dice que la multiplicación tiene mayor precedencia que la suma.
Los paréntesis anulan cualquier precedencia, por lo que si no estamos
satisfechos con el orden predeterminado, podemos usarlos para cambiarlo. Por
ejemplo, escribe (1 + 2) * 2.
Hay muchos operadores en JavaScript. Cada operador tiene un número de
precedencia correspondiente. El que tiene el número más grande se ejecuta
primero. Si la precedencia es la misma, el orden de ejecución es de izquierda a
derecha.
Aquí hay un extracto de la tabla de precedencia (no necesita recordar esto, pero
tenga en cuenta que los operadores unarios son más altos que los binarios
correspondientes):
Precedencia Nombre Firmar
... ... ...
17 unario más +
17 negación unaria -
dieciséis exponenciación **
15 multiplicación *
15 división /
13 adición +
13 sustracción -
... ... ...
3 asignación =
... ... ...
Como podemos ver, el “más unario” tiene una prioridad 17mayor que la 13de
“suma” (más binario). Por eso, en la expresión "+apples + +oranges", las
ventajas unarias funcionan antes de la suma.
Asignación
Observemos que una asignación =también es un operador. Aparece en la tabla
de precedencia con la prioridad muy baja de 3.
Por eso, cuando asignamos una variable, como x = 2 * 2 + 1, los cálculos se
hacen primero y luego =se evalúa, almacenando el resultado en x.
let x = 2 * 2 + 1;
alert( x ); // 5
Asignación = devuelve un valor
El hecho de =ser un operador, no un constructo de lenguaje “mágico”, tiene una
implicación interesante.
La mayoría de los operadores en JavaScript devuelven un valor. Eso es obvio
para +y -, pero también cierto para =.
La llamada x = valueescribe el valueen x y luego lo devuelve .
Aquí hay una demostración que usa una asignación como parte de una
expresión más compleja:
let a = 1;
let b = 2;
let c = 3 - (a = b + 1);
alert( a ); // 3
alert( c ); // 0
En el ejemplo anterior, el resultado de la expresión (a = b + 1)es el valor que
se le asignó a(es decir 3). Luego se utiliza para evaluaciones adicionales.
Código gracioso, ¿no? Deberíamos entender cómo funciona, porque a veces lo
vemos en bibliotecas de JavaScript.
Aunque, por favor no escriba el código así. Estos trucos definitivamente no
hacen que el código sea más claro o legible.
Encadenamiento de asignaciones
Otra característica interesante es la capacidad de encadenar asignaciones:
let a, b, c;
a = b = c = 2 + 2;
alert( a ); // 4
alert( b ); // 4
alert( c ); // 4
Las asignaciones encadenadas se evalúan de derecha a izquierda. En primer
lugar, la expresión más a la derecha 2 + 2se evalúa y se le asigna a las variables
de la izquierda: c, by a. Al final, todas las variables comparten un solo valor.
Una vez más, por motivos de legibilidad, es mejor dividir dicho código en pocas
líneas:
c = 2 + 2;
b = c;
a = c;
Eso es más fácil de leer, especialmente cuando se escanea el código
rápidamente.
Modificar en el lugar
A menudo necesitamos aplicar un operador a una variable y almacenar el nuevo
resultado en esa misma variable.
Por ejemplo:
let n = 2;
n = n + 5;
n = n * 2;
Esta notación se puede abreviar utilizando los operadores +=y *=:
let n = 2;
n += 5; // now n = 7 (same as n = n + 5)
n *= 2; // now n = 14 (same as n = n * 2)
alert( n ); // 14
Breve “modificar y asignar-” existe operadores para todos los operadores
aritméticos y bit a bit: /=, -=, etc.
Dichos operadores tienen la misma precedencia que una asignación normal, por
lo que se ejecutan después de la mayoría de los demás cálculos:
let n = 2;
n *= 3 + 5;
alert( n ); // 16 (right part evaluated first, same as n *= 8)
Incremento / decremento
Aumentar o disminuir un número en uno es una de las operaciones numéricas
más comunes.
Entonces, hay operadores especiales para ello:
Incremento ++ aumenta una variable en 1:
let counter = 2;
counter++; // works the same as counter = counter +
1, but is shorter
alert( counter ); // 3
El decremento -- disminuye una variable en 1:
let counter = 2;
counter--; // works the same as counter = counter -
1, but is shorter
alert( counter ); // 1
Importante:
El incremento / decremento solo se puede aplicar a variables. Intentar usarlo en
un valor como 5++dará un error.
Los operadores ++y --se pueden colocar antes o después de una variable.
Cuando el operador va después de la variable, es en “forma de
sufijo”: counter++.
La “forma de prefijo” es cuando el operador va delante de la variable: +
+counter.
Ambas declaraciones hacen lo mismo: aumentar counteren 1.
¿Hay alguna diferencia? Sí, pero solo podemos verlo si usamos el valor devuelto
de ++/--.
Aclaremos. Como sabemos, todos los operadores devuelven un valor. El
incremento / decremento no es una excepción. La forma de prefijo devuelve el
nuevo valor, mientras que la forma de sufijo devuelve el valor anterior (antes de
incrementar / disminuir).
Para ver la diferencia, aquí tienes un ejemplo:
let counter = 1;
let a = ++counter; // (*)
alert(a); // 2
En la línea (*), la forma de prefijo++counter aumenta countery devuelve el
nuevo valor 2,. Entonces, los alertespectáculos 2.
Ahora, usemos la forma de sufijo:
let counter = 1;
let a = counter++; // (*) changed ++counter to counter++
alert(a); // 1
En la línea (*), el postfix forma counter++también incrementos counterpero
vuelve el viejo valor (antes de incremento). Entonces, los alertespectáculos 1.
Para resumir:
Si no se usa el resultado de incremento / decremento, no hay diferencia
en qué forma usar:
let counter = 0;
counter++;
++counter;
alert( counter ); // 2, the lines above did the same
Si queremos aumentar un valor y usar inmediatamente el resultado del
operador, necesitamos la forma de prefijo:
let counter = 0;
alert( ++counter ); // 1
Si deseamos incrementar un valor pero usamos su valor anterior,
necesitamos la forma de sufijo:
let counter = 0;
alert( counter++ ); // 0
Incremento / decremento entre otros operadores
Los operadores también ++/--se pueden usar dentro de expresiones. Su
precedencia es mayor que la de la mayoría de las otras operaciones aritméticas.
Por ejemplo:
let counter = 1;
alert( 2 * ++counter ); // 4
Comparar con:
let counter = 1;
alert( 2 * counter++ ); // 2, because counter++ returns the
"old" value
Aunque técnicamente está bien, esta notación suele hacer que el código sea
menos legible. Una línea hace varias cosas, no es bueno.
Mientras lee el código, un escaneo ocular “vertical” rápido puede fácilmente
pasar por alto algo como counter++y no será obvio que la variable aumentó.
Aconsejamos un estilo de "una línea - una acción":
let counter = 1;
alert( 2 * counter );
counter++;
Operadores bit a bit
Los operadores bit a bit tratan los argumentos como números enteros de 32
bits y trabajan en el nivel de su representación binaria.
Estos operadores no son específicos de JavaScript. Son compatibles con la
mayoría de los lenguajes de programación.
Estos operadores se usan muy raramente, cuando necesitamos jugar con
números en el nivel más bajo (bit a bit). No necesitaremos estos operadores en
el corto plazo, ya que el desarrollo web tiene poco uso de ellos, pero en algunas
áreas especiales, como la criptografía, son útiles. Puede leer
el capítulo Operadores bit a bit sobre MDN cuando surja una necesidad.
Coma
El operador de coma ,es uno de los operadores más raros e inusuales. A veces,
se usa para escribir código más corto, por lo que necesitamos saberlo para
comprender qué está pasando.
El operador de coma nos permite evaluar varias expresiones, dividiéndolas con
una coma ,. Cada uno de ellos se evalúa pero solo se devuelve el resultado del
último.
Por ejemplo:
let a = (1 + 2, 3 + 4);
alert( a ); // 7 (the result of 3 + 4)
Aquí, 1 + 2se evalúa la primera expresión y se desecha su resultado. Luego, 3
+ 4se evalúa y se devuelve como resultado.
La coma tiene una precedencia muy baja
Tenga en cuenta que el operador de coma tiene una precedencia muy baja,
inferior a =, por lo que los paréntesis son importantes en el ejemplo anterior.
Sin ellos: a = 1 + 2, 3 + 4evalúa +primero, sumando los números a = 3, 7,
luego el operador de asignación =asigna a = 3y el resto se ignora. Es como (a
= 1 + 2), 3 + 4.
¿Por qué necesitamos un operador que descarte todo excepto la última
expresión?
A veces, la gente lo usa en construcciones más complejas para poner varias
acciones en una línea.
Por ejemplo:
// three operations in one line
for (a = 1, b = 3, c = a * b; a < 10; a++) {
...
Estos trucos se utilizan en muchos marcos de JavaScript. Por eso los
mencionamos. Pero generalmente no mejoran la legibilidad del código, por lo
que debemos pensarlo bien antes de usarlos.
Comparaciones
Conocemos muchos operadores de comparación de las matemáticas.
En JavaScript están escritos así:
Mayor / menor que: a > b, a < b.
Mayor / menor que o igual: a >= b, a <= b.
Es igual a:, a == btenga en cuenta que el signo de doble
igualdad ==significa la prueba de igualdad, mientras que uno solo a =
bsignifica una asignación.
No es igual. En matemáticas, la notación es ≠, pero en JavaScript se
escribe como a != b.
En este artículo aprenderemos más sobre los diferentes tipos de comparaciones,
cómo las hace JavaScript, incluidas peculiaridades importantes.
Al final, encontrará una buena receta para evitar problemas relacionados con las
"peculiaridades de JavaScript".
Booleano es el resultado
Todos los operadores de comparación devuelven un valor booleano:
true - significa "sí", "correcto" o "la verdad".
false - significa "no", "incorrecto" o "no es la verdad".
Por ejemplo:
alert( 2 > 1 ); // true (correct)
alert( 2 == 1 ); // false (wrong)
alert( 2 != 1 ); // true (correct)
Se puede asignar un resultado de comparación a una variable, como cualquier
valor:
let result = 5 > 4; // assign the result of the comparison
alert( result ); // true
Comparación de cadenas
Para ver si una cadena es mayor que otra, JavaScript utiliza el llamado
"diccionario" o orden "lexicográfico".
En otras palabras, las cadenas se comparan letra por letra.
Por ejemplo:
alert( 'Z' > 'A' ); // true
alert( 'Glow' > 'Glee' ); // true
alert( 'Bee' > 'Be' ); // true
El algoritmo para comparar dos cadenas es simple:
1. Compare el primer carácter de ambas cadenas.
2. Si el primer carácter de la primera cadena es mayor (o menor) que el de
la otra cadena, entonces la primera cadena es mayor (o menor) que la
segunda. Hemos terminado.
3. De lo contrario, si los primeros caracteres de ambas cadenas son iguales,
compare los segundos caracteres de la misma manera.
4. Repita hasta el final de cualquiera de las cuerdas.
5. Si ambas cadenas terminan con la misma longitud, entonces son
iguales. De lo contrario, la cuerda más larga es mayor.
En los ejemplos anteriores, la comparación 'Z' > 'A'llega a un resultado en el
primer paso, mientras que las cadenas "Glow"y "Glee"se comparan carácter
por carácter:
1. Ges lo mismo que G.
2. les lo mismo que l.
3. oes mayor que e. Deténgase aquí. La primera cuerda es mayor.
No es un diccionario real, sino orden Unicode
El algoritmo de comparación proporcionado anteriormente es
aproximadamente equivalente al que se utiliza en diccionarios o guías
telefónicas, pero no es exactamente el mismo.
Por ejemplo, el caso importa. Una letra mayúscula "A"no es igual a
minúscula "a". Cual es mayor? La minúscula "a". ¿Por qué? Debido a que el
carácter en minúscula tiene un índice mayor en la tabla de codificación interna
que utiliza JavaScript (Unicode). Volveremos a los detalles específicos y las
consecuencias de esto en el capítulo Cadenas .
Comparación de diferentes tipos
Al comparar valores de diferentes tipos, JavaScript convierte los valores en
números.
Por ejemplo:
alert( '2' > 1 ); // true, string '2' becomes a number 2
alert( '01' == 1 ); // true, string '01' becomes a number 1
Para valores booleanos, se trueconvierte en 1y se falseconvierte en 0.
Por ejemplo:
alert( true == 1 ); // true
alert( false == 0 ); // true
Una consecuencia divertida
Es posible que al mismo tiempo:
Dos valores son iguales.
Uno de ellos es truebooleano y el otro es falsebooleano.
Por ejemplo:
let a = 0;
alert( Boolean(a) ); // false
let b = "0";
alert( Boolean(b) ); // true
alert(a == b); // true!
Desde el punto de vista de JavaScript, este resultado es bastante normal. Una
verificación de igualdad convierte valores usando la conversión numérica (por lo
tanto, se "0"convierte en 0), mientras que la Booleanconversión explícita usa
otro conjunto de reglas.
Igualdad estricta
Una verificación de igualdad regular ==tiene un problema. No se puede
diferenciar 0de false:
alert( 0 == false ); // true
Lo mismo sucede con una cadena vacía:
alert( '' == false ); // true
Esto sucede porque el operador de igualdad convierte operandos de diferentes
tipos en números ==. Una cadena vacía, al igual que false, se convierte en un
cero.
¿Qué hacer si nos gustaría diferenciar 0entre false?
Un operador de igualdad estricta ===comprueba la igualdad sin conversión de
tipo.
En otras palabras, si ay bson de diferentes tipos, a === bregresa
inmediatamente falsesin intentar convertirlos.
Vamos a intentarlo:
alert( 0 === false ); // false, because the types are different
También hay un operador de "no igualdad estricta" !==análogo a !=.
El operador de igualdad estricta es un poco más largo de escribir, pero hace
que sea obvio lo que está sucediendo y deja menos espacio para errores.
Comparación con nulo e indefinido
Hay un comportamiento no intuitivo cuando nullo undefinedse comparan
con otros valores.
Para un estricto control de igualdad ===
Estos valores son diferentes, porque cada uno de ellos es de un tipo
diferente.
alert( null === undefined ); // false
Para un control no estricto ==
Hay una regla especial. Estos dos son una “dulce pareja”: se igualan (en el
sentido de ==), pero no en ningún otro valor.
alert( null == undefined ); // true
Para matemáticas y otras comparaciones < > <= >=
null/undefinedse convierten en números: se nullconvierte 0, mientras
que se undefinedconvierte NaN.
Ahora veamos algunas cosas divertidas que suceden cuando aplicamos estas
reglas. Y, lo que es más importante, cómo no caer en una trampa con ellos.
Resultado extraño: nulo vs 0
Comparemos nullcon un cero:
alert( null > 0 ); // (1) false
alert( null == 0 ); // (2) false
alert( null >= 0 ); // (3) true
Matemáticamente, eso es extraño. El último resultado indica que " nulles
mayor o igual que cero", por lo que en una de las comparaciones anteriores
debe serlo true, pero ambos son falsos.
La razón es que una verificación de igualdad ==y las comparaciones > < >=
<=funcionan de manera diferente. Las comparaciones se convierten nullen un
número, tratándolo como 0. Por eso (3) null >= 0es verdadero y (1) null >
0es falso.
Por otra parte, la comprobación de igualdad ==para undefinedy nullse define
de tal manera que, sin ningún tipo de conversiones, que son iguales entre sí y
no lo hacen igual todo lo demás. Por eso (2) null == 0es falso.
Un indefinido incomparable
El valor undefinedno debe compararse con otros valores:
alert( undefined > 0 ); // false (1)
alert( undefined < 0 ); // false (2)
alert( undefined == 0 ); // false (3)
¿Por qué no le gusta tanto el cero? ¡Siempre falso!
Obtenemos estos resultados porque:
Comparaciones (1)y (2)retorno falseporque undefinedse convierte
en un valor numérico especial que devuelve para todas las
comparaciones NaNy NaNes un valor numérico especial false.
La comprobación de la igualdad
de (3)rendimientos falseporque undefinedsólo se iguala null, undefinedy
ningún otro valor.
Evitar problemas
¿Por qué repasamos estos ejemplos? ¿Debemos recordar estas peculiaridades
todo el tiempo? Bueno en realidad no. En realidad, estas cosas complicadas se
irán familiarizando gradualmente con el tiempo, pero hay una forma sólida de
evitar problemas con ellas:
Trate cualquier comparación undefined/nullexcepto la estricta
igualdad ===con un cuidado excepcional.
No utilice comparaciones >= > < <=con una variable que pueda
serlo null/undefined, a menos que esté realmente seguro de lo que está
haciendo. Si una variable puede tener estos valores, verifíquelos por separado.
Resumen
Los operadores de comparación devuelven un valor booleano.
Las cadenas se comparan letra por letra en el orden del "diccionario".
Cuando se comparan valores de diferentes tipos, se convierten en
números (con la exclusión de una estricta verificación de igualdad).
Los valores nully son undefinediguales ==entre sí y no son iguales a
ningún otro valor.
Tenga cuidado al usar comparaciones como >o <con variables que
ocasionalmente pueden ser null/undefined. Verificar
por null/undefinedseparado es una buena idea.
9. Ramificación condicional: si, '?'
A veces, necesitamos realizar diferentes acciones en función de diferentes
condiciones.
Para hacer eso, podemos usar la ifdeclaración y el operador condicional ?,
también llamado operador de "signo de interrogación".
La declaración "si"
La if(...)declaración evalúa una condición entre paréntesis y, si el resultado
es true, ejecuta un bloque de código.
Por ejemplo:
let year = prompt('In which year was ECMAScript-2015
specification published?', '');
if (year == 2015) alert( 'You are right!' );
En el ejemplo anterior, la condición es una simple verificación de igualdad
( year == 2015), pero puede ser mucho más compleja.
Si queremos ejecutar más de una declaración, tenemos que envolver nuestro
bloque de código entre llaves:
if (year == 2015) {
alert( "That's correct!" );
alert( "You're so smart!" );
}
Recomendamos envolver su bloque de código con llaves {}cada vez que use
una ifdeclaración, incluso si solo hay una declaración para ejecutar. Hacerlo
mejora la legibilidad.
Conversión booleana
La if (…)declaración evalúa la expresión entre paréntesis y convierte el
resultado en un valor booleano.
Recordemos las reglas de conversión del capítulo Conversiones de tipos :
Un número 0, una cadena vacía "", null, undefined, y NaNtodos se
convierten false. Por eso se les llama valores "falsos".
Otros valores se vuelven true, por eso se les llama “veracidad”.
Entonces, el código bajo esta condición nunca se ejecutaría:
if (0) { // 0 is falsy
...
... y dentro de esta condición, siempre:
if (1) { // 1 is truthy
...
También podemos pasar un valor booleano evaluado previamente a if, como
este:
let cond = (year == 2015); // equality evaluates to true or
false
if (cond) {
...
La cláusula "else"
La ifdeclaración puede contener un bloque "else" opcional. Se ejecuta cuando
la condición es falsa.
Por ejemplo:
let year = prompt('In which year was the ECMAScript-2015
specification published?', '');
if (year == 2015) {
alert( 'You guessed it right!' );
} else {
alert( 'How can you be so wrong?' ); // any value except 2015
Varias condiciones: "si no"
A veces, nos gustaría probar varias variantes de una condición. La else
ifcláusula nos permite hacer eso.
Por ejemplo:
let year = prompt('In which year was the ECMAScript-2015
specification published?', '');
if (year < 2015) {
alert( 'Too early...' );
} else if (year > 2015) {
alert( 'Too late' );
} else {
alert( 'Exactly!' );
En el código anterior, JavaScript comprueba primero year < 2015. Si eso es
falso, pasa a la siguiente condición year > 2015. Si eso también es falso,
muestra el último alert.
Puede haber más else ifbloques. La final elsees opcional.
Operador condicional '?'
A veces, necesitamos asignar una variable dependiendo de una condición.
Por ejemplo:
let accessAllowed;
let age = prompt('How old are you?', '');
if (age > 18) {
accessAllowed = true;
} else {
accessAllowed = false;
alert(accessAllowed);
El llamado operador "condicional" o "signo de interrogación" nos permite
hacerlo de una manera más breve y sencilla.
El operador está representado por un signo de interrogación ?. A veces se le
llama "ternario", porque el operador tiene tres operandos. En realidad, es el
único operador en JavaScript que tiene tantos.
La sintaxis es:
let result = condition ? value1 : value2;
Se conditionevalúa: si es veraz, value1se devuelve, de lo contrario - value2.
Por ejemplo:
let accessAllowed = (age > 18) ? true : false;
Técnicamente, podemos omitir los paréntesis age > 18. El operador de signo
de interrogación tiene una precedencia baja, por lo que se ejecuta después de
la comparación >.
Este ejemplo hará lo mismo que el anterior:
// the comparison operator "age > 18" executes first anyway
// (no need to wrap it into parentheses)
let accessAllowed = age > 18 ? true : false;
Pero los paréntesis hacen que el código sea más legible, por lo que
recomendamos usarlos.
Tenga en cuenta:
En el ejemplo anterior, puede evitar usar el operador de signo de interrogación
porque la comparación en sí devuelve true/false:
// the same
let accessAllowed = age > 18;
Múltiples '?'
Una secuencia de operadores de signo de interrogación ?puede devolver un
valor que depende de más de una condición.
Por ejemplo:
let age = prompt('age?', 18);
let message = (age < 3) ? 'Hi, baby!' :
(age < 18) ? 'Hello!' :
(age < 100) ? 'Greetings!' :
'What an unusual age!';
alert( message );
Al principio puede resultar difícil comprender lo que está sucediendo. Pero
después de una mirada más cercana, podemos ver que es solo una secuencia
ordinaria de pruebas:
1. El primer signo de interrogación comprueba si age < 3.
2. Si es cierto, vuelve 'Hi, baby!'. De lo contrario, continúa con la
expresión después de los dos puntos '":"', comprobando age < 18.
3. Si eso es cierto, regresa 'Hello!'. De lo contrario, continúa con la
expresión después de los siguientes dos puntos '":"', marcando age < 100.
4. Si eso es cierto, regresa 'Greetings!'. De lo contrario, continúa con la
expresión después de los últimos dos puntos '":"', regresando 'What an
unusual age!'.
Así es como se ve esto usando if..else:
if (age < 3) {
message = 'Hi, baby!';
} else if (age < 18) {
message = 'Hello!';
} else if (age < 100) {
message = 'Greetings!';
} else {
message = 'What an unusual age!';
Uso no tradicional de '?'
A veces, el signo de interrogación ?se usa como reemplazo de if:
let company = prompt('Which company created JavaScript?', '');
(company == 'Netscape') ?
alert('Right!') : alert('Wrong.');
Dependiendo de la condición company == 'Netscape', la primera o la
segunda expresión después de ?se ejecuta y muestra una alerta.
Aquí no asignamos un resultado a una variable. En su lugar, ejecutamos un
código diferente según la condición.
No se recomienda utilizar el operador de signo de interrogación de esta
manera.
La notación es más corta que la ifdeclaración equivalente , lo que atrae a
algunos programadores. Pero es menos legible.
Aquí está el mismo código que se usa ifpara comparar:
let company = prompt('Which company created JavaScript?', '');
if (company == 'Netscape') {
alert('Right!');
} else {
alert('Wrong.');
Nuestros ojos escanean el código verticalmente. Los bloques de código que
abarcan varias líneas son más fáciles de entender que un conjunto de
instrucciones largo y horizontal.
El propósito del operador de signo de interrogación ?es devolver un valor u
otro dependiendo de su condición. Úselo exactamente para
eso. Úselo ifcuando necesite ejecutar diferentes ramas de código.
10. Operadores logicos
Hay tres operadores lógicos en JavaScript: ||(O), &&(Y), !(NO).
Aunque se denominan “lógicos”, se pueden aplicar a valores de cualquier tipo,
no solo booleanos. Su resultado también puede ser de cualquier tipo.
Veamos los detalles.
|| (O)
El operador "O" se representa con dos símbolos de línea vertical:
result = a || b;
En la programación clásica, el OR lógico está destinado a manipular valores
booleanos únicamente. Si alguno de sus argumentos lo es true, regresa true;
de lo contrario, regresa false.
En JavaScript, el operador es un poco más complicado y más poderoso. Pero
primero, veamos qué sucede con los valores booleanos.
Hay cuatro combinaciones lógicas posibles:
alert( true || true ); // true
alert( false || true ); // true
alert( true || false ); // true
alert( false || false ); // false
Como podemos ver, el resultado es siempre trueexcepto en el caso en que
ambos operandos son false.
Si un operando no es booleano, se convierte en booleano para la evaluación.
Por ejemplo, el número 1se trata como true, el número 0como false:
if (1 || 0) { // works just like if( true || false )
alert( 'truthy!' );
La mayoría de las veces, OR ||se usa en una ifdeclaración para probar
si alguna de las condiciones dadas lo es true.
Por ejemplo:
let hour = 9;
if (hour < 10 || hour > 18) {
alert( 'The office is closed.' );
Podemos pasar más condiciones:
let hour = 12;
let isWeekend = true;
if (hour < 10 || hour > 18 || isWeekend) {
alert( 'The office is closed.' ); // it is the weekend
}
O "||" encuentra el primer valor de verdad
La lógica descrita anteriormente es algo clásica. Ahora, traigamos las
características "adicionales" de JavaScript.
El algoritmo extendido funciona de la siguiente manera.
Dados múltiples valores OR'ed:
result = value1 || value2 || value3;
El ||operador OR hace lo siguiente:
Evalúa operandos de izquierda a derecha.
Para cada operando, lo convierte en booleano. Si el resultado es true, se
detiene y devuelve el valor original de ese operando.
Si se han evaluado todos los operandos (es decir, todos lo fueron false),
devuelve el último operando.
Un valor se devuelve en su forma original, sin la conversión.
En otras palabras, una cadena de OR "||"devuelve el primer valor de verdad o
el último si no se encuentra ningún valor de verdad.
Por ejemplo:
alert( 1 || 0 ); // 1 (1 is truthy)
alert( null || 1 ); // 1 (1 is the first truthy value)
alert( null || 0 || 1 ); // 1 (the first truthy value)
alert( undefined || null || 0 ); // 0 (all falsy, returns the
last value)
Esto conduce a un uso interesante en comparación con un "OR puro, clásico,
solo booleano".
1. Obtener el primer valor veraz de una lista de variables o expresiones.
Por ejemplo, tenemos firstName, lastNamey nickNamelas variables, todos
opcionales.
Usemos OR ||para elegir el que tiene los datos y mostrarlo (o anonymoussi no
hay nada configurado):
let firstName = "";
let lastName = "";
let nickName = "SuperCoder";
alert( firstName || lastName || nickName || "Anonymous"); //
SuperCoder
Si todas las variables fueran falsas, Anonymousaparecería.
2. Evaluación de cortocircuito.
Otra característica del ||operador de quirófano es la evaluación de
"cortocircuito".
Significa que ||procesa sus argumentos hasta que se alcanza el primer valor de
verdad, y luego el valor se devuelve inmediatamente, sin siquiera tocar el otro
argumento.
La importancia de esta característica se vuelve obvia si un operando no es solo
un valor, sino una expresión con un efecto secundario, como una asignación de
variable o una llamada a función.
En el siguiente ejemplo, solo se imprime el segundo mensaje:
true || alert("not printed");
false || alert("printed");
En la primera línea, el ||operador OR detiene la evaluación inmediatamente
después de ver true, por lo alertque no se ejecuta.
A veces, las personas usan esta función para ejecutar comandos solo si la
condición en la parte izquierda es falsa.
&& (Y)
El operador AND se representa con dos símbolos de unión &&:
result = a && b;
En la programación clásica, Y devuelve truesi ambos operandos son verdaderos
y falseno:
alert( true && true ); // true
alert( false && true ); // false
alert( true && false ); // false
alert( false && false ); // false
Un ejemplo con if:
let hour = 12;
let minute = 30;
if (hour == 12 && minute == 30) {
alert( 'The time is 12:30' );
Al igual que con OR, se permite cualquier valor como operando de AND:
if (1 && 0) { // evaluated as true && false
alert( "won't work, because the result is falsy" );
Y "&&" encuentra el primer valor falso
Dados múltiples valores con AND:
result = value1 && value2 && value3;
El &&operador AND hace lo siguiente:
Evalúa operandos de izquierda a derecha.
Para cada operando, lo convierte en booleano. Si el resultado es false,
se detiene y devuelve el valor original de ese operando.
Si se han evaluado todos los operandos (es decir, todos eran verdaderos),
devuelve el último operando.
En otras palabras, Y devuelve el primer valor falso o el último valor si no se
encontró ninguno.
Las reglas anteriores son similares a OR. La diferencia es que AND devuelve el
primer valor falso mientras que OR devuelve el primer valor verdadero .
Ejemplos:
// if the first operand is truthy,
// AND returns the second operand:
alert( 1 && 0 ); // 0
alert( 1 && 5 ); // 5
// if the first operand is falsy,
// AND returns it. The second operand is ignored
alert( null && 5 ); // null
alert( 0 && "no matter what" ); // 0
También podemos pasar varios valores seguidos. Vea cómo se devuelve la
primera falsa:
alert( 1 && 2 && null && 3 ); // null
Cuando todos los valores son verdaderos, se devuelve el último valor:
alert( 1 && 2 && 3 ); // 3, the last one
La precedencia de AND &&es mayor que OR||
La precedencia del &&operador AND es mayor que OR ||.
Así que el código a && b || c && des esencialmente el mismo que si
las &&expresiones eran paréntesis: (a && b) || (c && d).
No reemplace ifcon || o &&
A veces, la gente usa el &&operador AND como una "forma más corta de
escribir if".
Por ejemplo:
let x = 1;
(x > 0) && alert( 'Greater than zero!' );
La acción en la parte derecha de &&se ejecutará solo si la evaluación la
alcanza. Es decir, solo si (x > 0)es cierto.
Así que básicamente tenemos un análogo para:
let x = 1;
if (x > 0) alert( 'Greater than zero!' );
Aunque la variante con &&parece más corta, ifes más obvia y tiende a ser un
poco más legible. Por lo tanto, recomendamos usar cada construcción para su
propósito: use ifsi queremos si y use &&si queremos Y.
! (NO)
El operador booleano NOT se representa con un signo de exclamación !.
La sintaxis es bastante simple:
result = !value;
El operador acepta un solo argumento y hace lo siguiente:
1. Convierte el operando a tipo booleano: true/false.
2. Devuelve el valor inverso.
Por ejemplo:
alert( !true ); // false
alert( !0 ); // true
A !!veces se usa un doble NOT para convertir un valor a tipo booleano:
alert( !!"non-empty string" ); // true
alert( !!null ); // false
Es decir, el primer NO convierte el valor a booleano y devuelve el inverso, y el
segundo NO lo invierte nuevamente. Al final, tenemos una conversión simple de
valor a booleano.
Hay una forma un poco más detallada de hacer lo mismo: una Booleanfunción
incorporada:
alert( Boolean("non-empty string") ); // true
alert( Boolean(null) ); // false
La precedencia de NOT !es la más alta de todos los operadores lógicos, por lo
que siempre se ejecuta primero, antes &&o ||.
11. Operador de fusión nulo '??'
Una adición reciente
Esta es una adición reciente al idioma. Los navegadores antiguos pueden
necesitar polyfills.
El operador de fusión nula ??proporciona una sintaxis corta para seleccionar
una primera variable "definida" de la lista.
El resultado de a ?? bes:
asi no lo es null, o undefined,
b, de lo contrario.
Entonces, x = a ?? bes un equivalente corto a:
x = (a !== null && a !== undefined) ? a : b;
He aquí un ejemplo más extenso.
Imagínese, tenemos un usuario y hay
variables firstName, lastNameo nickNamepara su nombre, apellido y
apodo. Todos ellos pueden estar indefinidos, si el usuario decide no ingresar
ningún valor.
Nos gustaría mostrar el nombre de usuario: una de estas tres variables, o
mostrar "Anónimo" si no se establece nada.
Usemos el ??operador para seleccionar el primero definido:
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
// show the first not-null/undefined value
alert(firstName ?? lastName ?? nickName ?? "Anonymous"); //
Supercoder
Comparación con ||
El ||operador OR se puede utilizar de la misma forma que ??. En realidad,
podemos reemplazar ??con ||en el código anterior y obtener el mismo
resultado, como se describió en el capítulo anterior .
La diferencia importante es que:
||devuelve el primer valor de verdad .
??devuelve el primer valor definido .
Esto es muy importante cuando nos gustaría tratar de
manera null/undefineddiferente a 0.
Por ejemplo, considere esto:
height = height ?? 100;
Se establece heighten 100si no está definido.
Vamos a compararlo con ||:
let height = 0;
alert(height || 100); // 100
alert(height ?? 100); // 0
Aquí, height || 100golosinas altura cero tan definida, mismo
que null, undefinedo cualquier otro valor Falsy. Entonces el resultado es 100.
Las height ?? 100devoluciones 100solo si heightes
exactamente nullo undefined. Entonces alertmuestra el valor de altura 0"tal
cual".
Qué comportamiento es mejor depende de un caso de uso particular. Cuando la
altura cero es un valor válido, entonces ??es preferible.
Precedencia
La precedencia del ??operador es bastante baja: 5en la tabla MDN .
Por tanto, ??se evalúa después de la mayoría de las demás operaciones, pero
antes =y ?.
Si necesitamos elegir un valor ??dentro de una expresión compleja, considere
agregar paréntesis:
let height = null;
let width = null;
// important: use parentheses
let area = (height ?? 100) * (width ?? 50);
alert(area); // 5000
De lo contrario, si omitimos los paréntesis, *tiene mayor precedencia que ??y se
ejecutaría primero.
Eso funcionaría igual que:
// probably not correct
let area = height ?? (100 * width) ?? 50;
También hay una limitación de nivel de idioma relacionada.
Por razones de seguridad, está prohibido su uso ??en conjunto con &&y ||
operadores.
El siguiente código desencadena un error de sintaxis:
let x = 1 && 2 ?? 3; // Syntax error
La limitación es sin duda discutible, pero se añadió a la especificación del
lenguaje con el fin de evitar errores de programación, ya que la gente empieza
a cambiar a ??partir de ||.
Utilice paréntesis explícitos para solucionarlo:
let x = (1 && 2) ?? 3; // Works
alert(x); // 2
Resumen
El operador de fusión nula ??proporciona una forma breve de elegir un
valor "definido" de la lista.
Se utiliza para asignar valores predeterminados a las variables:
// set height=100, if height is null or undefined
height = height ?? 100;
El operador ??tiene una precedencia muy baja, un poco más alta que ?
y =.
Está prohibido usarlo con ||o &&sin paréntesis explícitos.
12. Bucles: mientras y para
A menudo necesitamos repetir acciones.
Por ejemplo, sacar productos de una lista uno tras otro o simplemente ejecutar
el mismo código para cada número del 1 al 10.
Los bucles son una forma de repetir el mismo código varias veces.
El bucle "mientras"
El whilebucle tiene la siguiente sintaxis:
while (condition) {
// code
// so-called "loop body"
Si bien conditiones cierto, se ejecuta codedesde el cuerpo del bucle.
Por ejemplo, el bucle de abajo genera imientras i < 3:
let i = 0;
while (i < 3) { // shows 0, then 1, then 2
alert( i );
i++;
Una sola ejecución del cuerpo del bucle se denomina iteración . El ciclo del
ejemplo anterior realiza tres iteraciones.
Si i++faltara en el ejemplo anterior, el ciclo se repetiría (en teoría) para
siempre. En la práctica, el navegador proporciona formas de detener tales
bucles y, en JavaScript del lado del servidor, podemos matar el proceso.
Cualquier expresión o variable puede ser una condición de bucle, no solo
comparaciones: la condición es evaluada y convertida a booleana por while.
Por ejemplo, una forma más corta de escribir while (i != 0)es while (i):
let i = 3;
while (i) { // when i becomes 0, the condition becomes falsy,
and the loop stops
alert( i );
i--;
Los tirantes rizados no son necesarios para un cuerpo de una sola línea
Si el cuerpo del bucle tiene una sola declaración, podemos omitir las llaves {…}:
let i = 3;
while (i) alert(i--);
El ciclo "hacer ... mientras"
La verificación de condición se puede mover debajo del cuerpo del bucle
usando la do..whilesintaxis:
do {
// loop body
} while (condition);
El bucle primero ejecutará el cuerpo, luego verificará la condición y, si bien es
cierto, lo ejecutará una y otra vez.
Por ejemplo:
let i = 0;
do {
alert( i );
i++;
} while (i < 3);
Esta forma de sintaxis solo debe usarse cuando desee que el cuerpo del bucle
se ejecute al menos una vez, independientemente de que la condición sea
verdadera. Por lo general, se prefiere la otra forma: while(…) {…}.
El bucle "for"
El forciclo es más complejo, pero también es el ciclo más utilizado.
Se parece a esto:
for (begin; condition; step) {
// ... loop body ...
Aprendamos el significado de estas partes con el ejemplo. El bucle por debajo
de carreras alert(i)para ide 0hasta (pero no incluyendo) 3:
for (let i = 0; i < 3; i++) { // shows 0, then 1, then 2
alert(i);
}
Examinemos la fordeclaración parte por parte:
parte
empezar i = 0 Se ejecuta una vez al entrar en el bucle.
Comprobado antes de cada iteración del ciclo. Si es falso,
condición i < 3
el bucle se detiene.
alert(i Se ejecuta una y otra vez mientras la condición es
cuerpo
) verdadera.
paso i++ Se ejecuta después del cuerpo en cada iteración.
El algoritmo de bucle general funciona así:
Run begin
→ (if condition → run body and run step)
→ (if condition → run body and run step)
→ (if condition → run body and run step)
→ ...
Es decir, se beginejecuta una vez y luego se itera: después de
cada conditionprueba bodyy stepse ejecutan.
Si es nuevo en los bucles, podría ser útil volver al ejemplo y reproducir paso a
paso cómo se ejecutan en una hoja de papel.
Esto es exactamente lo que sucede en nuestro caso:
// for (let i = 0; i < 3; i++) alert(i)
// run begin
let i = 0
// if condition → run body and run step
if (i < 3) { alert(i); i++ }
// if condition → run body and run step
if (i < 3) { alert(i); i++ }
// if condition → run body and run step
if (i < 3) { alert(i); i++ }
// ...finish, because now i == 3
Declaración de variable en línea
Aquí, la variable "contador" ise declara directamente en el ciclo. Esto se
denomina declaración de variable "en línea". Tales variables son visibles solo
dentro del ciclo.
for (let i = 0; i < 3; i++) {
alert(i); // 0, 1, 2
alert(i); // error, no such variable
En lugar de definir una variable, podríamos usar una existente:
let i = 0;
for (i = 0; i < 3; i++) { // use an existing variable
alert(i); // 0, 1, 2
alert(i); // 3, visible, because declared outside of the loop
Saltar partes
Se forpuede omitir cualquier parte de .
Por ejemplo, podemos omitir beginsi no necesitamos hacer nada al inicio del
ciclo.
Como aquí:
let i = 0; // we have i already declared and assigned
for (; i < 3; i++) { // no need for "begin"
alert( i ); // 0, 1, 2
También podemos eliminar la steppieza:
let i = 0;
for (; i < 3;) {
alert( i++ );
Esto hace que el bucle sea idéntico a while (i < 3).
De hecho, podemos eliminar todo, creando un bucle infinito:
for (;;) {
// repeats without limits
Tenga en cuenta que los dos forpuntos ;y comas deben estar presentes. De lo
contrario, habría un error de sintaxis.
Rompiendo el bucle
Normalmente, un bucle sale cuando su condición se vuelve falsa.
Pero podemos forzar la salida en cualquier momento usando
la breakdirectiva especial .
Por ejemplo, el ciclo siguiente le pide al usuario una serie de números, y se
"rompe" cuando no se ingresa ningún número:
let sum = 0;
while (true) {
let value = +prompt("Enter a number", '');
if (!value) break; // (*)
sum += value;
alert( 'Sum: ' + sum );
La breakdirectiva se activa en la línea (*)si el usuario ingresa una línea vacía o
cancela la entrada. Detiene el ciclo inmediatamente, pasando el control a la
primera línea después del ciclo. A saber, alert.
La combinación "bucle infinito + breaksegún sea necesario" es excelente para
situaciones en las que la condición de un bucle debe comprobarse no al
principio o al final del bucle, sino en el medio o incluso en varios lugares de su
cuerpo.
Continuar con la siguiente iteración
La continuedirectiva es una "versión más ligera" de break. No detiene todo el
ciclo. En cambio, detiene la iteración actual y fuerza al ciclo a iniciar uno nuevo
(si la condición lo permite).
Podemos usarlo si hemos terminado con la iteración actual y nos gustaría pasar
a la siguiente.
El ciclo siguiente se utiliza continuepara generar solo valores impares:
for (let i = 0; i < 10; i++) {
// if true, skip the remaining part of the body
if (i % 2 == 0) continue;
alert(i); // 1, then 3, 5, 7, 9
Para valores pares de i, la continuedirectiva deja de ejecutar el cuerpo y pasa
el control a la siguiente iteración de for(con el siguiente número). Por lo
tanto, alertsolo se solicita para valores impares.
La continuedirectiva ayuda a disminuir el anidamiento
Un bucle que muestra valores impares podría verse así:
for (let i = 0; i < 10; i++) {
if (i % 2) {
alert( i );
Desde un punto de vista técnico, esto es idéntico al ejemplo
anterior. Seguramente, podemos simplemente envolver el código en
un ifbloque en lugar de usar continue.
Pero como efecto secundario, esto creó un nivel más de anidación
(la alertllamada dentro de las llaves). Si el código dentro de ifes más largo
que unas pocas líneas, eso puede disminuir la legibilidad general.
No break/continueal lado derecho de '?'
Tenga en cuenta que las construcciones de sintaxis que no son expresiones no
se pueden utilizar con el operador ternario ?. En particular, directivas como las
que break/continueno se permiten allí.
Por ejemplo, si tomamos este código:
if (i > 5) {
alert(i);
} else {
continue;
... y reescribirlo usando un signo de interrogación:
(i > 5) ? alert(i) : continue; // continue isn't allowed here
… Deja de funcionar: hay un error de sintaxis.
Esta es solo otra razón para no usar el operador de signo de interrogación en ?
lugar de if.
Etiquetas para romper / continuar
A veces necesitamos salir de varios bucles anidados a la vez.
Por ejemplo, en el código a continuación, recorremos iy jsolicitamos las
coordenadas (i, j)de (0,0)a (2,2):
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let input = prompt(`Value at coords (${i},${j})`, '');
// what if we want to exit from here to Done (below)?
alert('Done!');
Necesitamos una forma de detener el proceso si el usuario cancela la entrada.
El breakdespués ordinario inputsolo rompería el bucle interno. Eso no es
suficiente, etiquetas, ¡vengan al rescate!
Una etiqueta es un identificador con dos puntos antes de un bucle:
labelName: for (...) {
...
La break <labelName>declaración en el bucle a continuación se desglosa en la
etiqueta:
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let input = prompt(`Value at coords (${i},${j})`, '');
// if an empty string or canceled, then break out of both
loops
if (!input) break outer; // (*)
// do something with the value...
alert('Done!');
En el código anterior, break outerbusca hacia arriba la etiqueta
nombrada outery sale de ese bucle.
Entonces el control va directamente de (*)a alert('Done!').
También podemos mover la etiqueta a una línea separada:
outer:
for (let i = 0; i < 3; i++) { ... }
La continuedirectiva también se puede utilizar con una etiqueta. En este caso,
la ejecución del código salta a la siguiente iteración del ciclo etiquetado.
Las etiquetas no permiten "saltar" a ningún lado
Las etiquetas no nos permiten saltar a un lugar arbitrario en el código.
Por ejemplo, es imposible hacer esto:
break label; // doesn't jumps to the label below
label: for (...)
Una llamada a break/continuesolo es posible desde dentro de un bucle y la
etiqueta debe estar en algún lugar por encima de la directiva.
Resumen
Cubrimos 3 tipos de bucles:
while - La condición se comprueba antes de cada iteración.
do..while - La condición se comprueba después de cada iteración.
for (;;) - La condición se comprueba antes de cada iteración, hay
configuraciones adicionales disponibles.
Para hacer un bucle "infinito", generalmente while(true)se
usa la construcción. Tal bucle, como cualquier otro, se puede detener con
la breakdirectiva.
Si no queremos hacer nada en la iteración actual y nos gustaría pasar a la
siguiente, podemos usar la continuedirectiva.
break/continueetiquetas de soporte antes del bucle. Una etiqueta es la única
forma de break/continueescapar de un bucle anidado para ir a uno externo.
13. La declaración "cambiar"
Una switchdeclaración puede reemplazar varios ifcheques.
Ofrece una forma más descriptiva de comparar un valor con múltiples variantes.
La sintaxis
La switchplaca presenta uno o más casebloques y un defecto opcional.
Se parece a esto:
switch(x) {
case 'value1': // if (x === 'value1')
...
[break]
case 'value2': // if (x === 'value2')
...
[break]
default:
...
[break]
El valor de xse verifica para una igualdad estricta con el valor del
primero case(es decir, value1) luego al segundo ( value2) y así sucesivamente.
Si se encuentra la igualdad, switchcomienza a ejecutar el código
comenzando por el correspondiente case, hasta el más cercano break(o hasta
el final de switch).
Si ningún caso coincide, defaultse ejecuta el código (si existe).
Un ejemplo
Un ejemplo de switch(el código ejecutado está resaltado):
let a = 2 + 2;
switch (a) {
case 3:
alert( 'Too small' );
break;
case 4:
alert( 'Exactly!' );
break;
case 5:
alert( 'Too large' );
break;
default:
alert( "I don't know such values" );
}
Aquí se switchempieza a comparar adesde la primera casevariante que es 3. El
partido falla.
Entonces 4. Eso es una coincidencia, por lo que la ejecución comienza case
4hasta el más cercano break.
Si no hay break, la ejecución continúa con la siguiente casesin ningún control.
Un ejemplo sin break:
let a = 2 + 2;
switch (a) {
case 3:
alert( 'Too small' );
case 4:
alert( 'Exactly!' );
case 5:
alert( 'Too big' );
default:
alert( "I don't know such values" );
En el ejemplo anterior, veremos la ejecución secuencial de tres alerts:
alert( 'Exactly!' );
alert( 'Too big' );
alert( "I don't know such values" );
Cualquier expresión puede ser un switch/caseargumento
Ambos switchy casepermiten expresiones arbitrarias.
Por ejemplo:
let a = "1";
let b = 0;
switch (+a) {
case b + 1:
alert("this runs, because +a is 1, exactly equals b+1");
break;
default:
alert("this doesn't run");
Aquí +ada 1, que se compara con b + 1in case, y se ejecuta el código
correspondiente.
Agrupación de "caso"
Se casepueden agrupar varias variantes de las cuales comparten el mismo
código.
Por ejemplo, si queremos que se ejecute el mismo código case 3y case 5:
let a = 3;
switch (a) {
case 4:
alert('Right!');
break;
case 3: // (*) grouped two cases
case 5:
alert('Wrong!');
alert("Why don't you take a math class?");
break;
default:
alert('The result is strange. Really.');
Ahora ambos 3y 5muestran el mismo mensaje.
La capacidad de “agrupar” casos es un efecto secundario de
cómo switch/casefunciona sin break. Aquí la ejecución de case 3comienza
desde la línea (*)y pasa case 5, porque no hay break.
El tipo importa
Destaquemos que el control de igualdad siempre es estricto. Los valores deben
ser del mismo tipo para coincidir.
Por ejemplo, consideremos el código:
let arg = prompt("Enter a value?");
switch (arg) {
case '0':
case '1':
alert( 'One or zero' );
break;
case '2':
alert( 'Two' );
break;
case 3:
alert( 'Never executes!' );
break;
default:
alert( 'An unknown value' );
1. Porque 0, 1el primero alertcorre.
2. Para 2las segundas alertcarreras.
3. Pero para 3, el resultado de promptes una cadena "3", que no es
estrictamente igual ===al número 3. ¡Así que tenemos un código muerto case
3! La defaultvariante se ejecutará.
14. Funciones
Muy a menudo, necesitamos realizar una acción similar en muchos lugares del
guión.
Por ejemplo, necesitamos mostrar un mensaje atractivo cuando un visitante
inicia sesión, cierra sesión y tal vez en otro lugar.
Las funciones son los principales "bloques de construcción" del
programa. Permiten que el código sea llamado muchas veces sin repetición.
Ya hemos visto ejemplos de funciones incorporadas,
como alert(message), prompt(message,
default)y confirm(question). Pero también podemos crear nuestras propias
funciones.
Declaración de función
Para crear una función podemos usar una declaración de función .
Se parece a esto:
function showMessage() {
alert( 'Hello everyone!' );
La functionpalabra clave va primero, luego el nombre de la función , luego una
lista de parámetros entre paréntesis (separados por comas, vacíos en el ejemplo
anterior) y finalmente el código de la función, también llamado "el cuerpo de la
función", entre rizado tirantes.
function name(parameters) {
...body...
}
Nuestra nueva función puede ser llamada por su nombre: showMessage().
Por ejemplo:
function showMessage() {
alert( 'Hello everyone!' );
showMessage();
showMessage();
La llamada showMessage()ejecuta el código de la función. Aquí veremos el
mensaje dos veces.
Este ejemplo demuestra claramente uno de los propósitos principales de las
funciones: evitar la duplicación de código.
Si alguna vez necesitamos cambiar el mensaje o la forma en que se muestra,
basta con modificar el código en un lugar: la función que lo genera.
Variables locales
Una variable declarada dentro de una función solo es visible dentro de esa
función.
Por ejemplo:
function showMessage() {
let message = "Hello, I'm JavaScript!"; // local variable
alert( message );
showMessage(); // Hello, I'm JavaScript!
alert( message ); // <-- Error! The variable is local to the
function
Variables externas
Una función también puede acceder a una variable externa, por ejemplo:
let userName = 'John';
function showMessage() {
let message = 'Hello, ' + userName;
alert(message);
showMessage(); // Hello, John
La función tiene acceso completo a la variable externa. También puede
modificarlo.
Por ejemplo:
let userName = 'John';
function showMessage() {
userName = "Bob"; // (1) changed the outer variable
let message = 'Hello, ' + userName;
alert(message);
alert( userName ); // John before the function call
showMessage();
alert( userName ); // Bob, the value was modified by the
function
La variable externa solo se usa si no hay una local.
Si se declara una variable con el mismo nombre dentro de la
función, sombrea la exterior. Por ejemplo, en el código siguiente, la función usa
el local userName. El exterior se ignora:
let userName = 'John';
function showMessage() {
let userName = "Bob"; // declare a local variable
let message = 'Hello, ' + userName; // Bob
alert(message);
// the function will create and use its own userName
showMessage();
alert( userName ); // John, unchanged, the function did not
access the outer variable
Variables globales
Las variables declaradas fuera de cualquier función, como la
exterior userNameen el código anterior, se denominan globales .
Las variables globales son visibles desde cualquier función (a menos que sean
sombreadas por los locales).
Es una buena práctica minimizar el uso de variables globales. El código
moderno tiene pocas o ninguna global. La mayoría de las variables residen en
sus funciones. A veces, sin embargo, pueden ser útiles para almacenar datos a
nivel de proyecto.
Parámetros
Podemos pasar datos arbitrarios a funciones usando parámetros (también
llamados argumentos de función ).
En el siguiente ejemplo, la función tiene dos parámetros: fromy text.
function showMessage(from, text) { // arguments: from, text
alert(from + ': ' + text);
showMessage('Ann', 'Hello!'); // Ann: Hello! (*)
showMessage('Ann', "What's up?"); // Ann: What's up? (**)
Cuando se llama a la función en líneas (*)y (**), los valores dados se copian
en variables locales fromy text. Entonces la función los usa.
Aquí hay un ejemplo más: tenemos una variable fromy se la pasamos a la
función. Tenga en cuenta: la función cambia from, pero el cambio no se ve
afuera, porque una función siempre obtiene una copia del valor:
function showMessage(from, text) {
from = '*' + from + '*'; // make "from" look nicer
alert( from + ': ' + text );
let from = "Ann";
showMessage(from, "Hello"); // *Ann*: Hello
// the value of "from" is the same, the function modified a
local copy
alert( from ); // Ann
Valores predeterminados
Si no se proporciona un parámetro, su valor se convierte en undefined.
Por ejemplo, la función antes mencionada showMessage(from, text)se puede
llamar con un solo argumento:
showMessage("Ann");
Eso no es un error. Tal llamada saldría "Ann: undefined". No hay text, por lo
que se asume eso text === undefined.
Si queremos usar un "predeterminado" texten este caso, podemos
especificarlo después =:
function showMessage(from, text = "no text given") {
alert( from + ": " + text );
showMessage("Ann"); // Ann: no text given
Ahora, si textno se pasa el parámetro, obtendrá el valor"no text given"
Aquí "no text given"hay una cadena, pero puede ser una expresión más
compleja, que solo se evalúa y asigna si falta el parámetro. Entonces, esto
también es posible:
function showMessage(from, text = anotherFunction()) {
// anotherFunction() only executed if no text given
// its result becomes the value of text
Evaluación de parámetros predeterminados
En JavaScript, un parámetro predeterminado se evalúa cada vez que se llama a
la función sin el parámetro respectivo.
En el ejemplo anterior, anotherFunction()se llama cada vez
que showMessage()se llama sin el textparámetro.
Parámetros predeterminados alternativos
A veces tiene sentido establecer valores predeterminados para los parámetros
no en la declaración de la función, sino en una etapa posterior, durante su
ejecución.
Para comprobar si hay un parámetro omitido, podemos compararlo
con undefined:
function showMessage(text) {
if (text === undefined) {
text = 'empty message';
alert(text);
}
showMessage(); // empty message
… O podríamos usar el ||operador:
// if text parameter is omitted or "" is passed, set it to
'empty'
function showMessage(text) {
text = text || 'empty';
...
Los motores de JavaScript modernos admiten el operador de fusión nula ?? , es
mejor cuando los valores falsos, como 0, se consideran regulares:
// if there's no "count" parameter, show "unknown"
function showCount(count) {
alert(count ?? "unknown");
showCount(0); // 0
showCount(null); // unknown
showCount(); // unknown
Devolviendo un valor
Una función puede devolver un valor al código de llamada como resultado.
El ejemplo más simple sería una función que suma dos valores:
function sum(a, b) {
return a + b;
let result = sum(1, 2);
alert( result ); // 3
La directiva returnpuede estar en cualquier lugar de la función. Cuando la
ejecución lo alcanza, la función se detiene y el valor se devuelve al código de
llamada (asignado a resultarriba).
Puede haber muchas apariciones de returnen una sola función. Por ejemplo:
function checkAge(age) {
if (age >= 18) {
return true;
} else {
return confirm('Do you have permission from your parents?');
let age = prompt('How old are you?', 18);
if ( checkAge(age) ) {
alert( 'Access granted' );
} else {
alert( 'Access denied' );
Se puede utilizar returnsin valor. Eso hace que la función se cierre
inmediatamente.
Por ejemplo:
function showMovie(age) {
if ( !checkAge(age) ) {
return;
alert( "Showing you the movie" ); // (*)
// ...
En el código anterior, si checkAge(age)regresa false, showMovieno procederá
al alert.
Una función con un returnretorno vacío o sin élundefined
Si una función no devuelve un valor, es lo mismo que si devuelve undefined:
function doNothing() { /* empty */ }
alert( doNothing() === undefined ); // true
Un vacío returntambién es lo mismo que return undefined:
function doNothing() {
return;
alert( doNothing() === undefined ); // true
Nunca agregue una nueva línea entre returny el valor
Para una expresión larga return, puede ser tentador ponerla en una línea
separada, como esta:
return
(some + long + expression + or + whatever * f(a) + f(b))
Eso no funciona, porque JavaScript asume un punto y coma
después return. Eso funcionará igual que:
return;
(some + long + expression + or + whatever * f(a) + f(b))
Entonces, efectivamente se convierte en un retorno vacío.
Si queremos que la expresión devuelta se ajuste a varias líneas, debemos
comenzar en la misma línea que return. O al menos coloque el paréntesis de
apertura allí de la siguiente manera:
return (
some + long + expression
+ or +
whatever * f(a) + f(b)
Y funcionará tal como lo esperamos.
Nombrar una función
Las funciones son acciones. Entonces, su nombre suele ser un verbo. Debe ser
breve, lo más preciso posible y describir lo que hace la función, de modo que
alguien que lea el código obtenga una indicación de lo que hace la función.
Es una práctica generalizada comenzar una función con un prefijo verbal que
describe vagamente la acción. Debe haber un acuerdo dentro del equipo sobre
el significado de los prefijos.
Por ejemplo, las funciones que comienzan con "show"suelen mostrar algo.
Función que comienza con…
"get…" - devolver un valor,
"calc…" - calcular algo,
"create…" - crear algo,
"check…" - comprobar algo y devolver un booleano, etc.
Ejemplos de tales nombres:
showMessage(..) // shows a message
getAge(..) // returns the age (gets it somehow)
calcSum(..) // calculates a sum and returns the result
createForm(..) // creates a form (and usually returns it)
checkPermission(..) // checks a permission, returns true/false
Con los prefijos en su lugar, un vistazo al nombre de una función permite
comprender qué tipo de trabajo hace y qué tipo de valor devuelve.
Una función, una acción
Una función debe hacer exactamente lo que sugiere su nombre, nada más.
Dos acciones independientes generalmente merecen dos funciones, incluso si
generalmente se llaman juntas (en ese caso, podemos hacer una tercera función
que llame a esas dos).
Algunos ejemplos de incumplimiento de esta regla:
getAge- Sería malo si muestra una alertcon la edad (solo debería
obtener).
createForm - Sería malo si modifica el documento, agregando un
formulario (solo debe crearlo y regresar).
checkPermission- Sería malo si muestra el access
granted/deniedmensaje (solo debe realizar la verificación y devolver el
resultado).
Estos ejemplos asumen significados comunes de prefijos. Usted y su equipo son
libres de acordar otros significados, pero generalmente no son muy
diferentes. En cualquier caso, debe tener una comprensión firme de lo que
significa un prefijo, lo que una función con prefijo puede y no puede
hacer. Todas las funciones con el mismo prefijo deben obedecer las reglas. Y el
equipo debe compartir el conocimiento.
Nombres de funciones ultracortos
Las funciones que se utilizan con mucha frecuencia a veces tienen nombres
ultracortos.
Por ejemplo, el marco jQuery define una función
con $. La biblioteca Lodash tiene su función principal nombrada _.
Éstas son excepciones. Generalmente, los nombres de las funciones deben ser
concisos y descriptivos.
Funciones == Comentarios
Las funciones deben ser cortas y hacer exactamente una cosa. Si eso es grande,
tal vez valga la pena dividir la función en algunas funciones más pequeñas. A
veces, seguir esta regla puede no ser tan fácil, pero definitivamente es algo
bueno.
Una función separada no solo es más fácil de probar y depurar, ¡su misma
existencia es un gran comentario!
Por ejemplo, compare las dos funciones showPrimes(n)siguientes. Cada uno
genera números primos hasta n.
La primera variante usa una etiqueta:
function showPrimes(n) {
nextPrime: for (let i = 2; i < n; i++) {
for (let j = 2; j < i; j++) {
if (i % j == 0) continue nextPrime;
}
alert( i ); // a prime
La segunda variante usa una función adicional isPrime(n)para probar la
primalidad:
function showPrimes(n) {
for (let i = 2; i < n; i++) {
if (!isPrime(i)) continue;
alert(i); // a prime
function isPrime(n) {
for (let i = 2; i < n; i++) {
if ( n % i == 0) return false;
return true;
}
La segunda variante es más fácil de entender, ¿no? En lugar de la pieza de
código, vemos un nombre de la acción ( isPrime). A veces, la gente se refiere a
este código como autodescriptivo .
Por lo tanto, se pueden crear funciones incluso si no tenemos la intención de
reutilizarlas. Estructuran el código y lo hacen legible.
Resumen
Una declaración de función se ve así:
function name(parameters, delimited, by, comma) {
/* code */
Los valores que se pasan a una función como parámetros se copian en
sus variables locales.
Una función puede acceder a variables externas. Pero funciona solo de
adentro hacia afuera. El código fuera de la función no ve sus variables locales.
Una función puede devolver un valor. Si no es así, el resultado
es undefined.
Para que el código sea limpio y fácil de entender, se recomienda utilizar
principalmente variables y parámetros locales en la función, no variables
externas.
Siempre es más fácil entender una función que obtiene parámetros, trabaja con
ellos y devuelve un resultado que una función que no obtiene parámetros, pero
modifica variables externas como efecto secundario.
Denominación de funciones:
Un nombre debe describir claramente lo que hace la función. Cuando
vemos una llamada a una función en el código, un buen nombre
instantáneamente nos da una comprensión de lo que hace y devuelve.
Una función es una acción, por lo que los nombres de las funciones
suelen ser verbales.
Existen muchos prefijos de función conocida
como create…, show…, get…, check…y así sucesivamente. Úselos para insinuar
lo que hace una función.
Las funciones son los bloques de construcción principales de los scripts. Ahora
hemos cubierto los conceptos básicos, por lo que podemos comenzar a crearlos
y usarlos. Pero ese es solo el comienzo del camino. Vamos a volver a ellos
muchas veces, profundizando en sus funciones avanzadas.
Expresiones de función
En JavaScript, una función no es una "estructura de lenguaje mágico", sino un
tipo especial de valor.
La sintaxis que usamos antes se llama Declaración de función :
function sayHi() {
alert( "Hello" );
Existe otra sintaxis para crear una función que se llama Expresión de función .
Se parece a esto:
let sayHi = function() {
alert( "Hello" );
};
Aquí, la función se crea y se asigna a la variable de forma explícita, como
cualquier otro valor. No importa cómo se defina la función, es solo un valor
almacenado en la variable sayHi.
El significado de estos ejemplos de código es el mismo: "crear una función y
ponerla en la variable sayHi".
Incluso podemos imprimir ese valor usando alert:
function sayHi() {
alert( "Hello" );
alert( sayHi ); // shows the function code
Tenga en cuenta que la última línea no ejecuta la función, porque no hay
paréntesis después sayHi. Hay lenguajes de programación donde cualquier
mención del nombre de una función provoca su ejecución, pero JavaScript no es
así.
En JavaScript, una función es un valor, por lo que podemos tratarla como un
valor. El código anterior muestra su representación de cadena, que es el código
fuente.
Seguramente, una función es un valor especial, en el sentido de que podemos
llamarlo como sayHi().
Pero sigue siendo un valor. Entonces podemos trabajar con él como con otros
tipos de valores.
Podemos copiar una función a otra variable:
function sayHi() { // (1) create
alert( "Hello" );
let func = sayHi; // (2) copy
func(); // Hello // (3) run the copy (it works)!
sayHi(); // Hello // this still works too (why wouldn't
it)
Esto es lo que sucede arriba en detalle:
1. La Declaración de función (1)crea la función y la coloca en la variable
nombrada sayHi.
2. Line lo (2)copia en la variable func. Tenga en cuenta de nuevo: no hay
paréntesis después sayHi. Si lo hubiera, func = sayHi()escribiría el resultado
de la llamada sayHi() en func, no la función en sayHi sí.
3. Ahora la función se puede llamar como sayHi()y func().
Tenga en cuenta que también podríamos haber utilizado una expresión de
función para declarar sayHi, en la primera línea:
let sayHi = function() {
alert( "Hello" );
};
let func = sayHi;
// ...
Todo funcionaría igual.
¿Por qué hay un punto y coma al final?
Es posible que se pregunte por qué la expresión de función tiene un punto y
coma ;al final, pero la declaración de función no:
function sayHi() {
// ...
let sayHi = function() {
// ...
};
La respuesta es simple:
No hay necesidad de que ;al final de los bloques de código y las
estructuras sintácticas que los utilizan gustaría if { ... }, for
{ }, function f { }etc.
Una expresión de función se utiliza dentro de la instrucción:, let sayHi
= ...;como valor. No es un bloque de código, sino una
asignación. Se ;recomienda el punto y coma al final de las declaraciones, sin
importar cuál sea el valor. Entonces, el punto y coma aquí no está relacionado
con la expresión de función en sí, simplemente termina la declaración.
Funciones de devolución de llamada
Veamos más ejemplos de pasar funciones como valores y usar expresiones de
función.
Escribiremos una función ask(question, yes, no)con tres parámetros:
question
Texto de la pregunta
yes
Función a ejecutar si la respuesta es "Sí"
no
Función a ejecutar si la respuesta es "No"
La función debe preguntar al questiony, dependiendo de la respuesta del
usuario, llamar yes()o no():
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
function showOk() {
alert( "You agreed." );
function showCancel() {
alert( "You canceled the execution." );
// usage: functions showOk, showCancel are passed as arguments
to ask
ask("Do you agree?", showOk, showCancel);
En la práctica, estas funciones son bastante útiles. La principal diferencia entre
una vida real asky el ejemplo anterior es que las funciones de la vida real
utilizan formas más complejas de interactuar con el usuario que una
simple confirm. En el navegador, esta función suele dibujar una bonita ventana
de preguntas. Pero esa es otra historia.
Los argumentos showOky showCancelde askse denominan funciones de
devolución de llamada o simplemente devoluciones de llamada .
La idea es que pasamos una función y esperamos que sea "llamada de nuevo"
más tarde si es necesario. En nuestro caso, se showOkconvierte en la devolución
de llamada para la respuesta "sí" y showCancelpara la respuesta "no".
Podemos usar Expresiones de función para escribir la misma función mucho
más corta:
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
ask(
"Do you agree?",
function() { alert("You agreed."); },
function() { alert("You canceled the execution."); }
);
Aquí, las funciones se declaran dentro de la ask(...)llamada. No tienen
nombre, por lo que se denominan anónimos . Tales funciones no son accesibles
fuera de ask(porque no están asignadas a variables), pero eso es lo que
queremos aquí.
Dicho código aparece en nuestros scripts de forma muy natural, en el espíritu
de JavaScript.
Una función es un valor que representa una "acción"
Los valores regulares como cadenas o números representan los datos .
Una función puede percibirse como una acción .
Podemos pasarlo entre variables y ejecutarlo cuando queramos.
Expresión de función frente a declaración de función
Formulemos las diferencias clave entre declaraciones de funciones y
expresiones.
Primero, la sintaxis: cómo diferenciarlos en el código.
Declaración de función: una función, declarada como una declaración
separada, en el flujo de código principal.
// Function Declaration
function sum(a, b) {
return a + b;
Expresión de función: una función, creada dentro de una expresión o
dentro de otra construcción de sintaxis. Aquí, la función se crea en el lado
derecho de la "expresión de asignación" =:
// Function Expression
let sum = function(a, b) {
return a + b;
};
La diferencia más sutil es cuando el motor JavaScript crea una función.
Una expresión de función se crea cuando la ejecución la alcanza y solo se puede
utilizar a partir de ese momento.
Una vez que el flujo de ejecución pasa al lado derecho de la asignación let
sum = function…, aquí vamos, se crea la función y se puede usar (asignar,
llamar, etc.) a partir de ahora.
Las declaraciones de funciones son diferentes.
Una declaración de función se puede llamar antes de que se defina.
Por ejemplo, una Declaración de función global es visible en todo el script, sin
importar dónde se encuentre.
Eso se debe a algoritmos internos. Cuando JavaScript se prepara para ejecutar el
script, primero busca declaraciones de funciones globales en él y crea las
funciones. Podemos pensar en ello como una "etapa de inicialización".
Y después de que se procesan todas las Declaraciones de funciones, se ejecuta
el código. Entonces tiene acceso a estas funciones.
Por ejemplo, esto funciona:
sayHi("John"); // Hello, John
function sayHi(name) {
alert( `Hello, ${name}` );
La Declaración de función sayHise crea cuando JavaScript se está preparando
para iniciar el script y está visible en todas partes.
... Si fuera una expresión de función, entonces no funcionaría:
sayHi("John"); // error!
let sayHi = function(name) { // (*) no magic any more
alert( `Hello, ${name}` );
};
Las expresiones de función se crean cuando la ejecución las alcanza. Eso
sucedería solo en la línea (*). Demasiado tarde.
Otra característica especial de las declaraciones de funciones es su alcance de
bloque.
En modo estricto, cuando una declaración de función está dentro de un bloque
de código, es visible en todas partes dentro de ese bloque. Pero no fuera de
ella.
Por ejemplo, imaginemos que necesitamos declarar una
función welcome()dependiendo de la agevariable que obtengamos durante el
tiempo de ejecución. Y luego planeamos usarlo algún tiempo después.
Si usamos la Declaración de función, no funcionará según lo previsto:
let age = prompt("What is your age?", 18);
// conditionally declare a function
if (age < 18) {
function welcome() {
alert("Hello!");
} else {
function welcome() {
alert("Greetings!");
// ...use it later
welcome(); // Error: welcome is not defined
Eso es porque una Declaración de función solo es visible dentro del bloque de
código en el que reside.
Aquí hay otro ejemplo:
let age = 16; // take 16 as an example
if (age < 18) {
welcome(); // \ (runs)
// |
function welcome() { // |
alert("Hello!"); // | Function Declaration is
available
} // | everywhere in the block where
it's declared
// |
welcome(); // / (runs)
} else {
function welcome() {
alert("Greetings!");
// Here we're out of curly braces,
// so we can not see Function Declarations made inside of them.
welcome(); // Error: welcome is not defined
¿Qué podemos hacer para hacer welcomevisible el exterior if?
El enfoque correcto sería usar una expresión de función y asignarla welcomea la
variable que se declara fuera ify tiene la visibilidad adecuada.
Este código funciona según lo previsto:
let age = prompt("What is your age?", 18);
let welcome;
if (age < 18) {
welcome = function() {
alert("Hello!");
};
} else {
welcome = function() {
alert("Greetings!");
};
welcome(); // ok now
O podríamos simplificarlo aún más usando un operador de signo de
interrogación ?:
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
function() { alert("Hello!"); } :
function() { alert("Greetings!"); };
welcome(); // ok now
¿Cuándo elegir la declaración de función frente a la expresión de función?
Como regla general, cuando necesitamos declarar una función, lo primero que
debemos considerar es la sintaxis de Declaración de funciones. Da más libertad
sobre cómo organizar nuestro código, porque podemos llamar a tales funciones
antes de que se declaren.
Eso también es mejor para la legibilidad, ya que es más fácil buscar function
f(…) {…}en el código que let f = function(…) {…};. Las declaraciones de
funciones son más "llamativas".
… Pero si una declaración de función no nos conviene por alguna razón, o
necesitamos una declaración condicional (acabamos de ver un ejemplo),
entonces se debe usar la expresión de función.
Resumen
Las funciones son valores. Se pueden asignar, copiar o declarar en
cualquier lugar del código.
Si la función se declara como una declaración separada en el flujo de
código principal, se denomina "Declaración de función".
Si la función se crea como parte de una expresión, se denomina
"Expresión de función".
Las declaraciones de función se procesan antes de que se ejecute el
bloque de código. Son visibles en todas partes del bloque.
Las expresiones de función se crean cuando el flujo de ejecución las
alcanza.
En la mayoría de los casos, cuando necesitamos declarar una función, es
preferible una Declaración de función, porque es visible antes que la declaración
en sí. Eso nos da más flexibilidad en la organización del código y, por lo general,
es más legible.
Por lo tanto, debemos usar una expresión de función solo cuando una
declaración de función no sea adecuada para la tarea. Hemos visto un par de
ejemplos de eso en este capítulo y veremos más en el futuro.
15. Funciones de flecha, conceptos básicos
Existe otra sintaxis muy simple y concisa para crear funciones, que a menudo es
mejor que las expresiones de función.
Se llama "funciones de flecha", porque se ve así:
let func = (arg1, arg2, ...argN) => expression
... Esto crea una función funcque acepta argumentos arg1..argN, luego evalúa
el expressiondel lado derecho con su uso y devuelve su resultado.
En otras palabras, es la versión más corta de:
let func = function(arg1, arg2, ...argN) {
return expression;
};
Veamos un ejemplo concreto:
let sum = (a, b) => a + b;
/* This arrow function is a shorter form of:
let sum = function(a, b) {
return a + b;
};
*/
alert( sum(1, 2) ); // 3
Como puede, ver (a, b) => a + bsignifica una función que acepta dos
argumentos llamados ay b. Tras la ejecución, evalúa la expresión a + by
devuelve el resultado.
Si solo tenemos un argumento, entonces los paréntesis alrededor de los
parámetros se pueden omitir, haciéndolo aún más corto.
Por ejemplo:
let double = n => n * 2;
// roughly the same as: let double = function(n) { return n *
2 }
alert( double(3) ); // 6
Si no hay argumentos, los paréntesis estarán vacíos (pero deberían estar
presentes):
let sayHi = () => alert("Hello!");
sayHi();
Las funciones de flecha se pueden utilizar de la misma forma que las
expresiones de función.
Por ejemplo, para crear dinámicamente una función:
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
() => alert('Hello') :
() => alert("Greetings!");
welcome();
Las funciones de las flechas pueden parecer desconocidas y poco legibles al
principio, pero eso cambia rápidamente a medida que los ojos se acostumbran
a la estructura.
Son muy convenientes para acciones simples de una línea, cuando somos
demasiado perezosos para escribir muchas palabras.
Funciones de flecha multilínea
Los ejemplos anteriores tomaron argumentos de la izquierda =>y evaluaron la
expresión del lado derecho con ellos.
A veces necesitamos algo un poco más complejo, como múltiples expresiones o
declaraciones. También es posible, pero debemos encerrarlos entre
llaves. Luego usa un normal returndentro de ellos.
Me gusta esto:
let sum = (a, b) => { // the curly brace opens a multiline
function
let result = a + b;
return result; // if we use curly braces, then we need an
explicit "return"
};
alert( sum(1, 2) ); // 3
Más por venir
Aquí elogiamos las funciones de flecha por su brevedad. ¡Pero eso no es todo!
Las funciones de flecha tienen otras características interesantes.
Para estudiarlos en profundidad, primero necesitamos conocer algunos otros
aspectos de JavaScript, por lo que volveremos a las funciones de flecha más
adelante en el capítulo Funciones de flecha revisadas .
Por ahora, ya podemos usar funciones de flecha para acciones de una línea y
devoluciones de llamada.
Resumen
Las funciones de flecha son útiles para una sola línea. Vienen en dos sabores:
1. Sin llaves: (...args) => expression- el lado derecho es una
expresión: la función la evalúa y devuelve el resultado.
2. Con llaves: (...args) => { body }- los corchetes nos permiten
escribir múltiples declaraciones dentro de la función, pero necesitamos una
explícita returnpara devolver algo.
16. Estilo de codificación
Nuestro código debe ser lo más limpio y fácil de leer posible.
Ese es en realidad el arte de la programación: tomar una tarea compleja y
codificarla de una manera correcta y legible por humanos. Un buen estilo de
código ayuda mucho en eso.
Sintaxis
Aquí hay una hoja de trucos con algunas reglas sugeridas (consulte a
continuación para obtener más detalles):
Ahora analicemos las reglas y las razones para ellas en detalle.
No hay reglas de "debes"
Aquí nada está escrito en piedra. Son preferencias de estilo, no dogmas
religiosos.
Tirantes rizados
En la mayoría de los proyectos de JavaScript, las llaves se escriben en estilo
"egipcio" con la llave de apertura en la misma línea que la palabra clave
correspondiente, no en una nueva línea. También debe haber un espacio antes
del corchete de apertura, como este:
if (condition) {
// do this
// ...and that
// ...and that
Una construcción de una sola línea, como if (condition) doSomething(), es
un caso de borde importante. ¿Deberíamos usar aparatos ortopédicos?
Aquí están las variantes anotadas para que pueda juzgar su legibilidad por
usted mismo:
1. 😠 Los principiantes a veces hacen eso. ¡Malo! No se necesitan tirantes
rizados:
if (n < 0) {alert(`Power ${n} is not supported`);}
2. 😠 Dividir en una línea separada sin tirantes. Nunca hagas eso, es fácil
cometer un error al agregar nuevas líneas:
3. if (n < 0)
alert(`Power ${n} is not supported`);
4. 😏 Una línea sin tirantes - aceptable, si es corta:
if (n < 0) alert(`Power ${n} is not supported`);
5. 😃 La mejor variante:
6. if (n < 0) {
7. alert(`Power ${n} is not supported`);
Para un código muy breve, se permite una línea, por ejemplo if (cond)
return null. Pero un bloque de código (la última variante) suele ser más
legible.
Longitud de la línea
A nadie le gusta leer una larga línea horizontal de código. Es una buena práctica
dividirlos.
Por ejemplo:
// backtick quotes ` allow to split the string into multiple
lines
let str = `
ECMA International's TC39 is a group of JavaScript developers,
implementers, academics, and more, collaborating with the
community
to maintain and evolve the definition of JavaScript.
`;
Y, para ifdeclaraciones:
if (
id === 123 &&
moonPhase === 'Waning Gibbous' &&
zodiacSign === 'Libra'
) {
letTheSorceryBegin();
La longitud máxima de la línea debe acordarse a nivel de equipo. Suele tener 80
o 120 caracteres.
Sangrías
Hay dos tipos de sangrías:
Sangrías horizontales: 2 o 4 espacios.
Se hace una sangría horizontal utilizando 2 o 4 espacios o el símbolo de
tabulación horizontal (tecla Tab ). Cuál elegir es una vieja guerra santa. Los
espacios son más habituales hoy en día.
Una ventaja de los espacios sobre las pestañas es que los espacios permiten
configuraciones de sangrías más flexibles que el símbolo de pestaña.
Por ejemplo, podemos alinear los argumentos con el corchete de apertura, así:
show(parameters,
aligned, // 5 spaces padding at the left
one,
after,
another
) {
// ...
Sangrías verticales: líneas vacías para dividir el código en bloques lógicos.
Incluso una sola función a menudo se puede dividir en bloques lógicos. En el
siguiente ejemplo, la inicialización de las variables, el bucle principal y la
devolución del resultado se dividen verticalmente:
function pow(x, n) {
let result = 1;
// <--
for (let i = 0; i < n; i++) {
result *= x;
// <--
return result;
Inserte una nueva línea adicional donde ayude a que el código sea más
legible. No debe haber más de nueve líneas de código sin sangría vertical.
Punto y coma
Debe haber un punto y coma después de cada declaración, incluso si es posible
que se salte.
Hay idiomas en los que el punto y coma es realmente opcional y rara vez se
usa. En JavaScript, sin embargo, hay casos en los que un salto de línea no se
interpreta como un punto y coma, dejando el código vulnerable a errores. Vea
más sobre eso en el capítulo Estructura del código .
Si es un programador de JavaScript experimentado, puede elegir un estilo de
código sin punto y coma como StandardJS . De lo contrario, es mejor usar
punto y coma para evitar posibles errores. La mayoría de los desarrolladores
ponen punto y coma.
Niveles de anidación
Intente evitar anidar el código a demasiados niveles de profundidad.
Por ejemplo, en el bucle, a veces es una buena idea usar la continuedirectiva
para evitar anidaciones adicionales.
Por ejemplo, en lugar de agregar un ifcondicional anidado como este:
for (let i = 0; i < 10; i++) {
if (cond) {
... // <- one more nesting level
Podemos escribir:
for (let i = 0; i < 10; i++) {
if (!cond) continue;
... // <- no extra nesting level
}
Se puede hacer algo similar con if/elsey return.
Por ejemplo, dos construcciones a continuación son idénticas.
Opción 1:
function pow(x, n) {
if (n < 0) {
alert("Negative 'n' not supported");
} else {
let result = 1;
for (let i = 0; i < n; i++) {
result *= x;
return result;
Opcion 2:
function pow(x, n) {
if (n < 0) {
alert("Negative 'n' not supported");
return;
let result = 1;
for (let i = 0; i < n; i++) {
result *= x;
return result;
El segundo es más legible porque el "caso especial" de n < 0se maneja desde
el principio. Una vez que se realiza la verificación, podemos pasar al flujo de
código "principal" sin la necesidad de un anidamiento adicional.
Colocación de funciones
Si está escribiendo varias funciones "auxiliares" y el código que las usa, hay tres
formas de organizar las funciones.
1. Declare las funciones sobre el código que las usa:
2. // function declarations
3. function createElement() {
4. ...
5. }
6.
7. function setHandler(elem) {
8. ...
9. }
10.
11. function walkAround() {
12. ...
13. }
14.
15. // the code which uses them
16. let elem = createElement();
17. setHandler(elem);
walkAround();
18. Código primero, luego funciones
19. // the code which uses the functions
20. let elem = createElement();
21. setHandler(elem);
22. walkAround();
23.
24. // --- helper functions ---
25. function createElement() {
26. ...
27. }
28.
29. function setHandler(elem) {
30. ...
31. }
32.
33. function walkAround() {
34. ...
35. Mixta: una función se declara donde se usa por primera vez.
La mayoría de las veces, se prefiere la segunda variante.
Eso es porque cuando leemos código, primero queremos saber qué hace . Si el
código va primero, queda claro desde el principio. Entonces, tal vez no
necesitemos leer las funciones en absoluto, especialmente si sus nombres son
descriptivos de lo que realmente hacen.
Guías de estilo
Una guía de estilo contiene reglas generales sobre “cómo escribir” código, por
ejemplo, qué citas usar, cuántos espacios sangrar, la longitud máxima de línea,
etc. Muchas cosas menores.
Cuando todos los miembros de un equipo usan la misma guía de estilo, el
código se ve uniforme, independientemente de qué miembro del equipo lo
haya escrito.
Por supuesto, un equipo siempre puede escribir su propia guía de estilo, pero
normalmente no es necesario. Hay muchas guías existentes para elegir.
Algunas opciones populares:
Guía de estilo de JavaScript de Google
Guía de estilo de JavaScript de Airbnb
[Link]
StandardJS
(y muchos más)
Si es un desarrollador novato, comience con la hoja de trucos al comienzo de
este capítulo. Luego, puede buscar otras guías de estilo para recoger más ideas
y decidir cuál le gusta más.
Linters automatizados
Los linters son herramientas que pueden verificar automáticamente el estilo de
su código y hacer sugerencias de mejora.
Lo mejor de ellos es que la verificación de estilo también puede encontrar
algunos errores, como errores tipográficos en los nombres de variables o
funciones. Debido a esta característica, se recomienda utilizar un linter incluso si
no desea ceñirse a un "estilo de código" en particular.
Aquí hay algunas herramientas de pelusa conocidas:
JSLint : uno de los primeros linters.
JSHint : más configuraciones que JSLint.
ESLint : probablemente el más nuevo.
Todos ellos pueden hacer el trabajo. El autor usa ESLint .
La mayoría de los linters están integrados con muchos editores populares:
simplemente habilite el complemento en el editor y configure el estilo.
Por ejemplo, para ESLint debe hacer lo siguiente:
1. Instalar [Link] .
2. Instale ESLint con el comando npm install -g eslint(npm es un
instalador de paquetes de JavaScript).
3. Cree un archivo de configuración con el nombre .eslintrcen la raíz de
su proyecto de JavaScript (en la carpeta que contiene todos sus archivos).
4. Instale / habilite el complemento para su editor que se integra con
ESLint. La mayoría de los editores tienen uno.
A continuación, se muestra un ejemplo de un .eslintrcarchivo:
"extends": "eslint:recommended",
"env": {
"browser": true,
"node": true,
"es6": true
},
"rules": {
"no-console": 0,
"indent": ["warning", 2]
}
Aquí la directiva "extends"denota que la configuración se basa en el conjunto
de ajustes “eslint: recommended”. Después de eso, especificamos el nuestro.
También es posible descargar conjuntos de reglas de estilo de la web y
ampliarlos. Consulte [Link]
obtener más detalles sobre la instalación.
Además, ciertos IDE tienen linting incorporado, lo cual es conveniente pero no
tan personalizable como ESLint.
Resumen
Todas las reglas de sintaxis descritas en este capítulo (y en las guías de estilo a
las que se hace referencia) tienen como objetivo aumentar la legibilidad de su
código. Todos ellos son discutibles.
Cuando pensamos en escribir un código "mejor", las preguntas que debemos
hacernos son: "¿Qué hace que el código sea más legible y más fácil de
entender?" y "¿Qué puede ayudarnos a evitar errores?" Estos son los aspectos
principales a tener en cuenta al elegir y debatir estilos de código.
La lectura de guías de estilo populares le permitirá mantenerse actualizado con
las últimas ideas sobre las tendencias de estilo de código y las mejores
prácticas.
17. Comentarios
Como sabemos por el capítulo de estructura de código , los comentarios
pueden ser de una sola línea: comenzando con //y de varias líneas: /* ... */.
Normalmente los usamos para describir cómo y por qué funciona el código.
A primera vista, comentar puede ser obvio, pero los principiantes en
programación a menudo los usan incorrectamente.
Malos comentarios
Los novatos tienden a utilizar comentarios para explicar "lo que está pasando en
el código". Me gusta esto:
// This code will do this thing (...) and that thing (...)
// ...and who knows what else...
very;
complex;
code;
Pero en un buen código, la cantidad de tales comentarios "explicativos" debería
ser mínima. En serio, el código debería ser fácil de entender sin ellos.
Hay una gran regla al respecto: "si el código es tan poco claro que requiere un
comentario, entonces tal vez debería reescribirse".
Receta: funciones de factorización
A veces es beneficioso reemplazar un fragmento de código con una función,
como aquí:
function showPrimes(n) {
nextPrime:
for (let i = 2; i < n; i++) {
// check if i is a prime number
for (let j = 2; j < i; j++) {
if (i % j == 0) continue nextPrime;
alert(i);
La mejor variante, con una función factorizada isPrime:
function showPrimes(n) {
for (let i = 2; i < n; i++) {
if (!isPrime(i)) continue;
alert(i);
}
function isPrime(n) {
for (let i = 2; i < n; i++) {
if (n % i == 0) return false;
return true;
Ahora podemos entender el código fácilmente. La función en sí se convierte en
el comentario. Este código se denomina autodescriptivo .
Receta: crear funciones
Y si tenemos una "hoja de códigos" larga como esta:
// here we add whiskey
for(let i = 0; i < 10; i++) {
let drop = getWhiskey();
smell(drop);
add(drop, glass);
// here we add juice
for(let t = 0; t < 3; t++) {
let tomato = getTomato();
examine(tomato);
let juice = press(tomato);
add(juice, glass);
// ...
Entonces podría ser una mejor variante refactorizarlo en funciones como:
addWhiskey(glass);
addJuice(glass);
function addWhiskey(container) {
for(let i = 0; i < 10; i++) {
let drop = getWhiskey();
//...
function addJuice(container) {
for(let t = 0; t < 3; t++) {
let tomato = getTomato();
//...
}
Una vez más, las funciones mismas dicen lo que está sucediendo. No hay nada
que comentar. Y también la estructura del código es mejor cuando se
divide. Está claro lo que hace cada función, lo que necesita y lo que devuelve.
En realidad, no podemos evitar por completo los comentarios
"explicativos". Hay algoritmos complejos. Y hay "ajustes" inteligentes con fines
de optimización. Pero, en general, deberíamos tratar de mantener el código
simple y autodescriptivo.
Buenos comentarios
Entonces, los comentarios explicativos suelen ser malos. ¿Qué comentarios son
buenos?
Describe la arquitectura
Proporcione una descripción general de alto nivel de los componentes,
cómo interactúan, cuál es el flujo de control en varias situaciones ... En
resumen, la vista de pájaro del código. Hay un lenguaje
especial UML para construir diagramas de arquitectura de alto nivel que
explican el código. Definitivamente vale la pena estudiarlo.
Parámetros y uso de la función del documento
Hay una sintaxis especial JSDoc para documentar una función: uso,
parámetros, valor devuelto.
Por ejemplo:
/**
* Returns x raised to the n-th power.
* @param {number} x The number to raise.
* @param {number} n The power, must be a natural number.
* @return {number} x raised to the n-th power.
*/
function pow(x, n) {
...
Dichos comentarios nos permiten comprender el propósito de la función y
usarla de la manera correcta sin mirar en su código.
Por cierto, muchos editores como WebStorm también pueden entenderlos y
usarlos para proporcionar autocompletar y alguna verificación automática de
código.
Además, existen herramientas como JSDoc 3 que pueden generar
documentación HTML a partir de los comentarios. Puede leer más información
sobre JSDoc en [Link]
¿Por qué se resuelve la tarea de esta manera?
Lo que está escrito es importante. Pero lo que no está escrito puede ser
aún más importante para comprender lo que está sucediendo. ¿Por qué
la tarea se resuelve exactamente de esta manera? El código no da
respuesta.
Si hay muchas formas de resolver la tarea, ¿por qué esta? Especialmente
cuando no es el más obvio.
Sin tales comentarios, es posible la siguiente situación:
1. Usted (o su colega) abre el código escrito hace algún tiempo y ve que es
"subóptimo".
2. Piensas: "Qué estúpido era entonces, y cuánto más inteligente soy
ahora", y reescribe usando la variante "más obvia y correcta".
3. … La necesidad de reescribir era buena. Pero en el proceso se ve que en
realidad falta la solución "más obvia". Incluso recuerdas vagamente por
qué, porque ya lo probaste hace mucho tiempo. Vuelve a la variante
correcta, pero se perdió el tiempo.
Los comentarios que explican la solución son muy importantes. Ayudan a
continuar el desarrollo de la manera correcta.
¿Alguna característica sutil del código? ¿Dónde se utilizan?
Si el código tiene algo sutil y contrario a la intuición, definitivamente vale
la pena comentarlo.
Resumen
Un signo importante de un buen desarrollador son los comentarios: su
presencia e incluso su ausencia.
Los buenos comentarios nos permiten mantener bien el código, volver a él
después de un tiempo y usarlo de manera más efectiva.
Comenta esto:
Arquitectura general, vista de alto nivel.
Uso de funciones.
Soluciones importantes, especialmente cuando no son inmediatamente
obvias.
Evite los comentarios:
Que dicen "cómo funciona el código" y "qué hace".
Colóquelos solo si es imposible hacer que el código sea tan simple y
autodescriptivo que no los requiera.
Los comentarios también se utilizan para herramientas de documentación
automática como JSDoc3: los leen y generan documentos HTML (o documentos
en otro formato).
18. Objetos
Como sabemos por el capítulo Tipos de datos , hay ocho tipos de datos en
JavaScript. Siete de ellos se denominan "primitivos", porque sus valores
contienen sólo una cosa (ya sea una cadena o un número o lo que sea).
Por el contrario, los objetos se utilizan para almacenar colecciones con clave de
varios datos y entidades más complejas. En JavaScript, los objetos penetran en
casi todos los aspectos del lenguaje. Por lo tanto, primero debemos entenderlos
antes de profundizar en cualquier otro lugar.
Se puede crear un objeto con corchetes de figura {…}con una lista opcional
de propiedades . Una propiedad es un par "clave: valor", donde keyes una
cadena (también llamada "nombre de propiedad"), y valuepuede ser cualquier
cosa.
Podemos imaginar un objeto como un armario con archivos firmados. Cada
dato se almacena en su archivo mediante la clave. Es fácil encontrar un archivo
por su nombre o agregar / eliminar un archivo.
Se puede crear un objeto vacío ("gabinete vacío") usando una de dos sintaxis:
let user = new Object(); // "object constructor" syntax
let user = {}; // "object literal" syntax
Por lo general, {...}se utilizan los corchetes de cifras . Esa declaración se
llama literal de objeto .
Literales y propiedades
Podemos poner inmediatamente algunas propiedades en {...}pares "clave:
valor":
let user = { // an object
name: "John", // by key "name" store value "John"
age: 30 // by key "age" store value 30
};
Una propiedad tiene una clave (también conocida como "nombre" o
"identificador") antes de los dos puntos ":"y un valor a la derecha.
En el userobjeto, hay dos propiedades:
1. La primera propiedad tiene el nombre "name"y el valor "John".
2. El segundo tiene el nombre "age"y el valor 30.
El userobjeto resultante se puede imaginar como un gabinete con dos archivos
firmados etiquetados como "nombre" y "edad".
Podemos agregar, eliminar y leer archivos en cualquier momento.
Los valores de propiedad son accesibles usando la notación de puntos:
// get property values of the object:
alert( [Link] ); // John
alert( [Link] ); // 30
El valor puede ser de cualquier tipo. Agreguemos uno booleano:
[Link] = true;
Para eliminar una propiedad, podemos usar el deleteoperador:
delete [Link];
También podemos usar nombres de propiedades de varias palabras, pero luego
deben citarse:
let user = {
name: "John",
age: 30,
"likes birds": true // multiword property name must be quoted
};
La última propiedad de la lista puede terminar con una coma:
let user = {
name: "John",
age: 30,
Eso se llama una coma "final" o "colgante". Hace que sea más fácil agregar /
eliminar / mover propiedades, porque todas las líneas se vuelven iguales.
El objeto con constante se puede cambiar
Tenga en cuenta: un objeto declarado como const se puede modificar.
Por ejemplo:
const user = {
name: "John"
};
[Link] = "Pete"; // (*)
alert([Link]); // Pete
Podría parecer que la línea (*)causaría un error, pero no. El constfija el valor
de user, pero no su contenido.
El constdaría un error solo si intentamos establecer user=...como un todo.
Hay otra forma de hacer que las propiedades de los objetos sean constantes, la
cubriremos más adelante en el capítulo Indicadores y descriptores de
propiedades .
Corchetes
Para propiedades de varias palabras, el acceso de puntos no funciona:
// this would give a syntax error
[Link] birds = true
JavaScript no entiende eso. Piensa que abordamos [Link] luego da un
error de sintaxis cuando se encuentra inesperado birds.
El punto requiere que la clave sea un identificador de variable válido. Eso
implica: no contiene espacios, no comienza con un dígito y no incluye
caracteres especiales ( $y _están permitidos).
Existe una "notación de corchetes" alternativa que funciona con cualquier
cadena:
let user = {};
// set
user["likes birds"] = true;
// get
alert(user["likes birds"]); // true
// delete
delete user["likes birds"];
Ahora todo esta bien. Tenga en cuenta que la cadena dentro de los corchetes
está correctamente citada (cualquier tipo de comillas servirá).
Los corchetes también proporcionan una forma de obtener el nombre de la
propiedad como resultado de cualquier expresión, a diferencia de una cadena
literal, como de una variable de la siguiente manera:
let key = "likes birds";
// same as user["likes birds"] = true;
user[key] = true;
Aquí, la variable keypuede calcularse en tiempo de ejecución o depender de la
entrada del usuario. Y luego lo usamos para acceder a la propiedad. Eso nos da
mucha flexibilidad.
Por ejemplo:
let user = {
name: "John",
age: 30
};
let key = prompt("What do you want to know about the user?",
"name");
// access by variable
alert( user[key] ); // John (if enter "name")
La notación de puntos no se puede utilizar de forma similar:
let user = {
name: "John",
age: 30
};
let key = "name";
alert( [Link] ) // undefined
Propiedades calculadas
Podemos usar corchetes en un objeto literal, al crear un objeto. Eso se
llama propiedades calculadas .
Por ejemplo:
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {
[fruit]: 5, // the name of the property is taken from the
variable fruit
};
alert( [Link] ); // 5 if fruit="apple"
El significado de una propiedad calculada es simple: [fruit]significa que se
debe tomar el nombre de la propiedad fruit.
Entonces, si entra un visitante "apple", bagse convertirá en {apple: 5}.
Básicamente, eso funciona igual que:
let fruit = prompt("Which fruit to buy?", "apple");
let bag = {};
// take property name from the fruit variable
bag[fruit] = 5;
… Pero se ve mejor.
Podemos usar expresiones más complejas entre corchetes:
let fruit = 'apple';
let bag = {
[fruit + 'Computers']: 5 // [Link] = 5
};
Los corchetes son mucho más poderosos que la notación de puntos. Permiten
cualquier nombre de propiedad y variable. Pero también son más engorrosos
de escribir.
Entonces, la mayoría de las veces, cuando los nombres de propiedad son
conocidos y simples, se usa el punto. Y si necesitamos algo más complejo,
cambiamos a corchetes.
Valor de propiedad taquigrafía
En código real, a menudo usamos variables existentes como valores para
nombres de propiedades.
Por ejemplo:
function makeUser(name, age) {
return {
name: name,
age: age,
// ...other properties
};
let user = makeUser("John", 30);
alert([Link]); // John
En el ejemplo anterior, las propiedades tienen los mismos nombres que las
variables. El caso de uso de hacer una propiedad a partir de una variable es tan
común que hay una abreviatura de valor de propiedad especial para acortarlo.
En lugar de name:namesimplemente podemos escribir name, así:
function makeUser(name, age) {
return {
name, // same as name: name
age, // same as age: age
// ...
};
Podemos usar propiedades normales y abreviaturas en el mismo objeto:
let user = {
name, // same as name:name
age: 30
};
Limitaciones de los nombres de propiedad
Como ya sabemos, una variable no puede tener un nombre igual a una de las
palabras reservadas en el idioma como "para", "dejar", "volver", etc.
Pero para una propiedad de objeto, no existe tal restricción:
// these properties are all right
let obj = {
for: 1,
let: 2,
return: 3
};
alert( [Link] + [Link] + [Link] ); // 6
En resumen, no existen limitaciones en los nombres de las propiedades. Pueden
ser cualquier cadena o símbolo (un tipo especial de identificadores, que se
tratará más adelante).
Otros tipos se convierten automáticamente en cadenas.
Por ejemplo, un número se 0convierte en una cadena "0"cuando se usa como
clave de propiedad:
let obj = {
0: "test" // same as "0": "test"
};
// both alerts access the same property (the number 0 is
converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)
Hay un problema menor con una propiedad especial nombrada __proto__. No
podemos establecerlo en un valor que no sea de objeto:
let obj = {};
obj.__proto__ = 5; // assign a number
alert(obj.__proto__); // [object Object] - the value is an
object, didn't work as intended
Como vemos en el código, 5se ignora la asignación a una primitiva .
Cubriremos la naturaleza especial de __proto__en los capítulos siguientes y
sugeriremos las formas de corregir dicho comportamiento.
Prueba de existencia de propiedad, operador "en"
Una característica notable de los objetos en JavaScript, en comparación con
muchos otros lenguajes, es que es posible acceder a cualquier propiedad. ¡No
habrá error si la propiedad no existe!
Leer una propiedad inexistente simplemente regresa undefined. Entonces
podemos probar fácilmente si la propiedad existe:
let user = {};
alert( [Link] === undefined ); // true means "no
such property"
También hay un operador especial "in"para eso.
La sintaxis es:
"key" in object
Por ejemplo:
let user = { name: "John", age: 30 };
alert( "age" in user ); // true, [Link] exists
alert( "blabla" in user ); // false, [Link] doesn't exist
Tenga en cuenta que en el lado izquierdo indebe haber un nombre de
propiedad . Suele ser una cadena entre comillas.
Si omitimos las comillas, eso significa una variable, debe contener el nombre
real que se va a probar. Por ejemplo:
let user = { age: 30 };
let key = "age";
alert( key in user ); // true, property "age" exists
¿Por qué existe el inoperador? ¿No es suficiente para comparar undefined?
Bueno, la mayoría de las veces la comparación undefinedfunciona bien. Pero
hay un caso especial cuando falla, pero "in"funciona correctamente.
Es cuando existe una propiedad de objeto, pero almacena undefined:
let obj = {
test: undefined
};
alert( [Link] ); // it's undefined, so - no such property?
alert( "test" in obj ); // true, the property does exist!
En el código anterior, la propiedad [Link] técnicamente. Entonces
el inoperador trabaja bien.
Situaciones como esta ocurren muy raramente, porque undefinedno deben
asignarse explícitamente. Usamos principalmente nullpara valores
"desconocidos" o "vacíos". Entonces, el inoperador es un invitado exótico en el
código.
El bucle "for ... in"
Para caminar sobre todas las teclas de un objeto, existe una forma especial del
bucle: for..in. Esto es algo completamente diferente del for(;;)constructo
que estudiamos antes.
La sintaxis:
for (key in object) {
// executes the body for each key among object properties
Por ejemplo, generemos todas las propiedades de user:
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
// keys
alert( key ); // name, age, isAdmin
// values for the keys
alert( user[key] ); // John, 30, true
Tenga en cuenta que todas las construcciones "for" nos permiten declarar la
variable de bucle dentro del bucle, como let keyaquí.
Además, podríamos usar otro nombre de variable aquí en lugar de key. Por
ejemplo, "for (let prop in obj)"también se usa ampliamente.
Ordenado como un objeto
¿Están ordenados los objetos? En otras palabras, si recorremos un objeto,
¿obtenemos todas las propiedades en el mismo orden en que fueron
agregadas? ¿Podemos confiar en esto?
La respuesta corta es: “ordenada de una manera especial”: las propiedades
enteras están ordenadas, otras aparecen en orden de creación. Los detalles
siguen.
Como ejemplo, consideremos un objeto con los códigos telefónicos:
let codes = {
"49": "Germany",
"41": "Switzerland",
"44": "Great Britain",
// ..,
"1": "USA"
};
for (let code in codes) {
alert(code); // 1, 41, 44, 49
El objeto se puede usar para sugerir una lista de opciones al usuario. Si estamos
creando un sitio principalmente para la audiencia alemana, probablemente
queramos 49ser los primeros.
Pero si ejecutamos el código, vemos una imagen totalmente diferente:
USA (1) va primero
luego Suiza (41) y así sucesivamente.
Los códigos telefónicos van en orden ascendente, porque son números
enteros. Entonces vemos 1, 41, 44, 49.
¿Propiedades enteras? ¿Que es eso?
El término "propiedad de entero" aquí significa una cadena que se puede
convertir ay desde un entero sin cambios.
Entonces, "49" es un nombre de propiedad entero, porque cuando se
transforma en un número entero y viceversa, sigue siendo el mismo. Pero "+49"
y "1.2" no son:
// [Link] is a built-in function that removes the decimal
part
alert( String([Link](Number("49"))) ); // "49", same,
integer property
alert( String([Link](Number("+49"))) ); // "49", not same
"+49" ⇒ not integer property
alert( String([Link](Number("1.2"))) ); // "1", not same
"1.2" ⇒ not integer property
... Por otro lado, si las claves no son enteras, entonces se enumeran en el orden
de creación, por ejemplo:
let user = {
name: "John",
surname: "Smith"
};
[Link] = 25; // add one more
// non-integer properties are listed in the creation order
for (let prop in user) {
alert( prop ); // name, surname, age
Entonces, para solucionar el problema con los códigos telefónicos, podemos
"hacer trampa" haciendo que los códigos no sean enteros. "+"Es
suficiente agregar un signo más antes de cada código.
Me gusta esto:
let codes = {
"+49": "Germany",
"+41": "Switzerland",
"+44": "Great Britain",
// ..,
"+1": "USA"
};
for (let code in codes) {
alert( +code ); // 49, 41, 44, 1
Ahora funciona según lo previsto.
Resumen
Los objetos son matrices asociativas con varias características especiales.
Almacenan propiedades (pares clave-valor), donde:
Las claves de propiedad deben ser cadenas o símbolos (generalmente
cadenas).
Los valores pueden ser de cualquier tipo.
Para acceder a una propiedad, podemos utilizar:
La notación de puntos: [Link].
Notación de corchetes obj["property"]. Los corchetes permiten tomar
la clave de una variable, como obj[varWithKey].
Operadores adicionales:
Para eliminar una propiedad: delete [Link].
Para comprobar si existe una propiedad con la clave dada: "key" in
obj.
Para iterar sobre un objeto: for (let key in obj)bucle.
Lo que hemos estudiado en este capítulo se llama "objeto simple", o
simplemente Object.
Hay muchos otros tipos de objetos en JavaScript:
Array para almacenar colecciones de datos ordenadas,
Date para almacenar la información sobre la fecha y la hora,
Error para almacenar la información sobre un error.
…Y así.
Tienen sus características especiales que estudiaremos más adelante. A veces,
las personas dicen algo como "Tipo de matriz" o "Tipo de fecha", pero
formalmente no son tipos propios, sino que pertenecen a un solo tipo de datos
de "objeto". Y lo amplían de diversas formas.
Los objetos en JavaScript son muy poderosos. Aquí acabamos de tocar la
superficie de un tema que es realmente enorme. Trabajaremos de cerca con los
objetos y aprenderemos más sobre ellos en otras partes del tutorial.
19. Copia de objetos, referencias
Una de las diferencias fundamentales de los objetos frente a los primitivos es
que se almacenan y copian "por referencia".
Los valores primitivos: cadenas, números, booleanos - se asignan / copian
"como un valor total".
Por ejemplo:
let message = "Hello!";
let phrase = message;
Como resultado, tenemos dos variables independientes, cada una almacena la
cadena "Hello!".
Los objetos no son así.
Una variable almacena no el objeto en sí, sino su "dirección en la memoria", en
otras palabras, "una referencia" a él.
Aquí está la imagen del objeto:
let user = {
name: "John"
};
Aquí, el objeto se almacena en algún lugar de la memoria. Y la
variable usertiene una "referencia" a ella.
Cuando se copia una variable de objeto, la referencia se copia, el objeto no se
duplica.
Por ejemplo:
let user = { name: "John" };
let admin = user; // copy the reference
Ahora tenemos dos variables, cada una con la referencia al mismo objeto:
Podemos utilizar cualquier variable para acceder al objeto y modificar su
contenido:
let user = { name: 'John' };
let admin = user;
[Link] = 'Pete'; // changed by the "admin" reference
alert([Link]); // 'Pete', changes are seen from the "user"
reference
El ejemplo anterior demuestra que solo hay un objeto. Como si tuviéramos un
armario con dos llaves y usáramos una de ellas ( admin) para meternos en
él. Entonces, si luego usamos otra tecla ( user) podemos ver cambios.
Comparación por referencia
Los operadores de igualdad ==e igualdad estricta ===para objetos funcionan
exactamente igual.
Dos objetos son iguales solo si son el mismo objeto.
Aquí dos variables hacen referencia al mismo objeto, por lo que son iguales:
let a = {};
let b = a; // copy the reference
alert( a == b ); // true, both variables reference the same
object
alert( a === b ); // true
Y aquí dos objetos independientes no son iguales, aunque ambos estén vacíos:
let a = {};
let b = {}; // two independent objects
alert( a == b ); // false
Para comparaciones como obj1 > obj2o para una comparación con un
primitivo obj == 5, los objetos se convierten en primitivos. Muy pronto
estudiaremos cómo funcionan las conversiones de objetos, pero a decir verdad,
estas comparaciones ocurren muy raramente, generalmente como resultado de
un error de codificación.
Clonación y fusión, [Link]
Entonces, copiar una variable de objeto crea una referencia más al mismo
objeto.
Pero, ¿y si necesitamos duplicar un objeto? Crear una copia independiente, ¿un
clon?
Eso también es factible, pero un poco más difícil, porque no hay un método
incorporado para eso en JavaScript. En realidad, eso rara vez se necesita. Copiar
por referencia es bueno la mayor parte del tiempo.
Pero si realmente queremos eso, entonces necesitamos crear un nuevo objeto y
replicar la estructura del existente iterando sobre sus propiedades y copiándolas
en el nivel primitivo.
Me gusta esto:
let user = {
name: "John",
age: 30
};
let clone = {}; // the new empty object
// let's copy all user properties into it
for (let key in user) {
clone[key] = user[key];
// now clone is a fully independent object with the same content
[Link] = "Pete"; // changed the data in it
alert( [Link] ); // still John in the original object
También podemos usar el método [Link] para eso.
La sintaxis es:
[Link](dest, [src1, src2, src3...])
El primer argumento destes un objeto de destino.
Otros argumentos src1, ..., srcN(pueden ser tantos como sean
necesarios) son objetos fuente.
Copia las propiedades de todos los objetos de origen src1, ...,
srcNen el destino dest. En otras palabras, las propiedades de todos los
argumentos a partir del segundo se copian en el primer objeto.
Vuelve la llamada dest.
Por ejemplo, podemos usarlo para fusionar varios objetos en uno:
let user = { name: "John" };
let permissions1 = { canView: true };
let permissions2 = { canEdit: true };
// copies all properties from permissions1 and permissions2 into
user
[Link](user, permissions1, permissions2);
// now user = { name: "John", canView: true, canEdit: true }
Si el nombre de propiedad copiado ya existe, se sobrescribe:
let user = { name: "John" };
[Link](user, { name: "Pete" });
alert([Link]); // now user = { name: "Pete" }
También podemos usar [Link] reemplazar el for..inbucle para
una clonación simple:
let user = {
name: "John",
age: 30
};
let clone = [Link]({}, user);
Copia todas las propiedades de useren el objeto vacío y lo devuelve.
Clonación anidada
Hasta ahora asumimos que todas las propiedades de userson primitivas. Pero
las propiedades pueden ser referencias a otros objetos. Qué hacer con ellos?
Me gusta esto:
let user = {
name: "John",
sizes: {
height: 182,
width: 50
};
alert( [Link] ); // 182
Ahora no es suficiente copiar [Link] = [Link],
porque [Link] un objeto, se copiará por
referencia. Entonces cloney usercompartiremos los mismos tamaños:
Me gusta esto:
let user = {
name: "John",
sizes: {
height: 182,
width: 50
};
let clone = [Link]({}, user);
alert( [Link] === [Link] ); // true, same object
// user and clone share sizes
[Link]++; // change a property from one place
alert([Link]); // 51, see the result from the other
one
Para solucionar eso, debemos usar el ciclo de clonación que examina cada valor
de user[key]y, si es un objeto, luego replicar su estructura también. Eso se
denomina "clonación profunda".
Podemos usar la recursividad para implementarlo. O, para no reinventar la
rueda, tome una implementación existente, por ejemplo _.cloneDeep (obj) de la
biblioteca JavaScript lodash .
Resumen
Los objetos se asignan y copian por referencia. En otras palabras, una variable
almacena no el "valor del objeto", sino una "referencia" (dirección en la
memoria) para el valor. Entonces, copiar dicha variable o pasarla como un
argumento de función copia esa referencia, no el objeto.
Todas las operaciones a través de referencias copiadas (como agregar / eliminar
propiedades) se realizan en el mismo objeto.
Para hacer una "copia real" (un clon) podemos utilizar [Link] la
llamada "copia superficial" (los objetos anidados se copian por referencia) o una
función de "clonación profunda", como _.cloneDeep (obj) .
20. Recolección de basura
La gestión de la memoria en JavaScript se realiza de forma automática e
invisible para nosotros. Creamos primitivas, objetos, funciones… Todo eso toma
memoria.
¿Qué sucede cuando algo ya no se necesita? ¿Cómo lo descubre y limpia el
motor JavaScript?
Accesibilidad
El concepto principal de gestión de memoria en JavaScript es la accesibilidad .
En pocas palabras, los valores "alcanzables" son aquellos que son accesibles o
utilizables de alguna manera. Se garantiza que se almacenarán en la memoria.
1. Existe un conjunto básico de valores inherentemente alcanzables que no
se pueden eliminar por razones obvias.
Por ejemplo:
o Variables locales y parámetros de la función actual.
o Variables y parámetros para otras funciones en la cadena actual de
llamadas anidadas.
o Variables globales.
o (hay algunos otros internos también)
Estos valores se denominan raíces .
2. Cualquier otro valor se considera accesible si es accesible desde una raíz
mediante una referencia o una cadena de referencias.
Por ejemplo, si hay un objeto en una variable global y ese objeto tiene una
propiedad que hace referencia a otro objeto, ese objeto se considera
accesible. Y aquellos a los que hace referencia también son accesibles. Ejemplos
detallados a seguir.
Hay un proceso en segundo plano en el motor de JavaScript que se
llama recolector de basura . Supervisa todos los objetos y elimina los que se han
vuelto inalcanzables.
Un simple ejemplo
Este es el ejemplo más simple:
// user has a reference to the object
let user = {
name: "John"
};
Aquí la flecha muestra una referencia de objeto. La variable global
hace "user"referencia al objeto {name: "John"}(lo llamaremos John para
abreviar). La "name"propiedad de John almacena un primitivo, por lo que está
pintado dentro del objeto.
Si userse sobrescribe el valor de , se pierde la referencia:
user = null;
Ahora John se vuelve inalcanzable. No hay forma de acceder a él, no hay
referencias a él. El recolector de basura desechará los datos y liberará la
memoria.
Dos referencias
Ahora imaginemos que copiamos la referencia de usera admin:
// user has a reference to the object
let user = {
name: "John"
};
let admin = user;
Ahora si hacemos lo mismo:
user = null;
… Entonces, el objeto todavía es accesible a través de adminuna variable global,
por lo que está en la memoria. Si admintambién sobrescribimos , se puede
eliminar.
Objetos interconectados
Ahora un ejemplo más complejo. La familia:
function marry(man, woman) {
[Link] = man;
[Link] = woman;
return {
father: man,
mother: woman
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
La función marry"casa" dos objetos dándoles referencias entre sí y devuelve un
nuevo objeto que los contiene a ambos.
La estructura de memoria resultante:
A partir de ahora, todos los objetos son accesibles.
Ahora eliminemos dos referencias:
delete [Link];
delete [Link];
No es suficiente eliminar solo una de estas dos referencias, porque todos los
objetos seguirían siendo accesibles.
Pero si eliminamos ambos, entonces podemos ver que John ya no tiene ninguna
referencia entrante:
Las referencias salientes no importan. Solo los entrantes pueden hacer que un
objeto sea accesible. Entonces, John ahora es inalcanzable y se eliminará de la
memoria con todos sus datos que también se volvieron inaccesibles.
Después de la recolección de basura:
Isla inalcanzable
Es posible que toda la isla de objetos interconectados se vuelva inalcanzable y
se elimine de la memoria.
El objeto de origen es el mismo que el anterior. Luego:
family = null;
La imagen en memoria se convierte en:
Este ejemplo demuestra la importancia del concepto de accesibilidad.
Es obvio que John y Ann todavía están vinculados, ambos tienen referencias
entrantes. Pero eso no es suficiente.
El "family"objeto anterior se ha desvinculado de la raíz, ya no hay ninguna
referencia a él, por lo que toda la isla se vuelve inalcanzable y se eliminará.
Algoritmos internos
El algoritmo básico de recolección de basura se llama "marcar y barrer".
Los siguientes pasos de "recolección de basura" se realizan con regularidad:
El recolector de basura echa raíces y las “marca” (recuerda).
Luego visita y "marca" todas las referencias de ellos.
Luego visita los objetos marcados y marca sus referencias. Todos los
objetos visitados se recuerdan, para no visitar el mismo objeto dos veces en el
futuro.
… Y así sucesivamente hasta que se visiten todas las referencias
accesibles (desde las raíces).
Se eliminan todos los objetos excepto los marcados.
Por ejemplo, deje que nuestra estructura de objeto se vea así:
Podemos ver claramente una "isla inalcanzable" al lado derecho. Ahora veamos
cómo lo maneja el recolector de basura “marcar y barrer”.
El primer paso marca las raíces:
Luego se marcan sus referencias:
… Y sus referencias, si bien es posible:
Ahora los objetos que no se pudieron visitar en el proceso se consideran
inalcanzables y se eliminarán:
También podemos imaginar el proceso como derramar un enorme cubo de
pintura desde las raíces, que fluye a través de todas las referencias y marca
todos los objetos alcanzables. A continuación, se eliminan los que no están
marcados.
Ese es el concepto de cómo funciona la recolección de basura. Los motores de
JavaScript aplican muchas optimizaciones para que se ejecute más rápido y no
afecte la ejecución.
Algunas de las optimizaciones:
Colección generacional : los objetos se dividen en dos conjuntos:
"nuevos" y "antiguos". Aparecen muchos objetos, hacen su trabajo y mueren
rápido, se pueden limpiar agresivamente. Aquellos que sobreviven lo suficiente,
se vuelven "viejos" y se examinan con menos frecuencia.
Colección incremental : si hay muchos objetos e intentamos caminar y
marcar todo el conjunto de objetos a la vez, puede llevar algún tiempo e
introducir retrasos visibles en la ejecución. Entonces, el motor intenta dividir la
recolección de basura en pedazos. Luego, las piezas se ejecutan una a una, por
separado. Eso requiere una contabilidad adicional entre ellos para rastrear los
cambios, pero tenemos muchos retrasos pequeños en lugar de uno grande.
Recolección en tiempo de inactividad : el recolector de basura intenta
ejecutarse solo mientras la CPU está inactiva, para reducir el posible efecto en la
ejecución.
Existen otras optimizaciones y sabores de algoritmos de recolección de
basura. Por mucho que me gustaría describirlos aquí, tengo que esperar, porque
diferentes motores implementan diferentes ajustes y técnicas. Y, lo que es aún
más importante, las cosas cambian a medida que se desarrollan los motores,
por lo que estudiar más a fondo "por adelantado", sin una necesidad real,
probablemente no valga la pena. A menos que, por supuesto, sea una cuestión
de puro interés, a continuación encontrará algunos enlaces para usted.
Resumen
Las principales cosas que debes saber:
La recolección de basura se realiza automáticamente. No podemos
forzarlo ni prevenirlo.
Los objetos se conservan en la memoria mientras están accesibles.
Ser referenciado no es lo mismo que ser accesible (desde una raíz): un
paquete de objetos interconectados puede volverse inalcanzable como un todo.
Los motores modernos implementan algoritmos avanzados de recolección de
basura.
Un libro general "El manual de recolección de basura: el arte de la gestión
automática de la memoria" (R. Jones et al) cubre algunos de ellos.
Si está familiarizado con la programación de bajo nivel, encontrará información
más detallada sobre el recolector de basura V8 en el artículo Un recorrido por
V8: Recolección de basura .
El blog V8 también publica artículos sobre cambios en la gestión de la memoria
de vez en cuando. Naturalmente, para aprender la recolección de basura, es
mejor que se prepare aprendiendo sobre los componentes internos de V8 en
general y lea el blog de Vyacheslav Egorov, quien trabajó como uno de los
ingenieros de V8. Digo: "V8", porque se cubre mejor con artículos en
Internet. Para otros motores, muchos enfoques son similares, pero la
recolección de basura difiere en muchos aspectos.
El conocimiento profundo de los motores es bueno cuando necesita
optimizaciones de bajo nivel. Sería aconsejable planificarlo como el siguiente
paso después de que se haya familiarizado con el idioma.
21. Métodos de objeto, "esto"
Los objetos generalmente se crean para representar entidades del mundo real,
como usuarios, pedidos, etc.
let user = {
name: "John",
age: 30
};
Y, en el mundo real, un usuario puede actuar : seleccionar algo del carrito de
compras, iniciar sesión, cerrar sesión, etc.
Las acciones están representadas en JavaScript por funciones en propiedades.
Ejemplos de métodos
Para empezar, enseñemos usera saludar:
let user = {
name: "John",
age: 30
};
[Link] = function() {
alert("Hello!");
};
[Link](); // Hello!
Aquí acabamos de usar una expresión de función para crear la función y
asignarla a la propiedad [Link] objeto.
Entonces podemos llamarlo. ¡El usuario ahora puede hablar!
Una función que es propiedad de un objeto se denomina método .
Entonces, aquí tenemos un método sayHidel objeto user.
Por supuesto, podríamos usar una función declarada previamente como
método, como este:
let user = {
// ...
};
// first, declare
function sayHi() {
alert("Hello!");
};
// then add as a method
[Link] = sayHi;
[Link](); // Hello!
Programación orientada a objetos
Cuando escribimos nuestro código usando objetos para representar entidades,
eso se llama programación orientada a objetos , en resumen: "OOP".
La programación orientada a objetos es una gran cosa, una ciencia interesante
en sí misma. ¿Cómo elegir las entidades adecuadas? ¿Cómo organizar la
interacción entre ellos? Eso es arquitectura, y hay grandes libros sobre ese tema,
como "Patrones de diseño: elementos de software orientado a objetos
reutilizable" de E. Gamma, R. Helm, R. Johnson, J. Vissides o "Análisis y diseño
orientado a objetos con Aplicaciones ”de G. Booch, y más.
Método abreviado
Existe una sintaxis más corta para los métodos en un objeto literal:
// these objects do the same
user = {
sayHi: function() {
alert("Hello");
};
// method shorthand looks better, right?
user = {
sayHi() { // same as "sayHi: function()"
alert("Hello");
};
Como se demostró, podemos omitir "function"y simplemente
escribir sayHi().
A decir verdad, las notaciones no son completamente idénticas. Existen
diferencias sutiles relacionadas con la herencia de objetos (que se tratarán más
adelante), pero por ahora no importan. En casi todos los casos se prefiere la
sintaxis más corta.
"Esto" en métodos
Es común que un método de objeto necesite acceder a la información
almacenada en el objeto para hacer su trabajo.
Por ejemplo, el código interno [Link]()puede necesitar el nombre
del user.
Para acceder al objeto, un método puede utilizar la thispalabra clave.
El valor de thises el objeto "antes del punto", el que se usa para llamar al
método.
Por ejemplo:
let user = {
name: "John",
age: 30,
sayHi() {
// "this" is the "current object"
alert([Link]);
};
[Link](); // John
Aquí durante la ejecución de [Link](), el valor de thisserá user.
Técnicamente, también es posible acceder al objeto sin this, haciendo
referencia a él a través de la variable externa:
let user = {
name: "John",
age: 30,
sayHi() {
alert([Link]); // "user" instead of "this"
};
… Pero tal código no es confiable. Si decidimos copiar usera otra variable, por
ejemplo, admin = usery sobrescribir usercon otra cosa, entonces accederá al
objeto incorrecto.
Eso se demuestra a continuación:
let user = {
name: "John",
age: 30,
sayHi() {
alert( [Link] ); // leads to an error
}
};
let admin = user;
user = null; // overwrite to make things obvious
[Link](); // Whoops! inside sayHi(), the old name is used!
error!
Si usáramos en [Link] de [Link] de alert, entonces el
código funcionaría.
"Esto" no está obligado
En JavaScript, la palabra clave se thiscomporta a diferencia de la mayoría de
los otros lenguajes de programación. Se puede utilizar en cualquier función.
No hay ningún error de sintaxis en el siguiente ejemplo:
function sayHi() {
alert( [Link] );
El valor de thisse evalúa durante el tiempo de ejecución, según el contexto.
Por ejemplo, aquí se asigna la misma función a dos objetos diferentes y tiene un
"esto" diferente en las llamadas:
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert( [Link] );
// use the same function in two objects
user.f = sayHi;
admin.f = sayHi;
// these calls have different this
// "this" inside the function is the object "before the dot"
user.f(); // John (this == user)
admin.f(); // Admin (this == admin)
admin['f'](); // Admin (dot or square brackets access the method
– doesn't matter)
La regla es simple: si obj.f()se llama, entonces thises objdurante la llamada
de f. Entonces es usero adminen el ejemplo anterior.
Llamando sin un objeto: this == undefined
Incluso podemos llamar a la función sin ningún objeto:
function sayHi() {
alert(this);
sayHi(); // undefined
En este caso thisestá undefineden modo estricto. Si intentamos
acceder [Link], habrá un error.
En modo no estricto, el valor de thisen tal caso será el objeto
global ( windowen un navegador, lo veremos más adelante en el capítulo Objeto
global ). Este es un comportamiento histórico que "use strict"arregla.
Por lo general, dicha llamada es un error de programación. Si hay thisuna
función dentro, espera ser llamada en un contexto de objeto.
Las consecuencias de desatar this
Si viene de otro lenguaje de programación, entonces probablemente esté
acostumbrado a la idea de un "límite this", donde los métodos definidos en un
objeto siempre hacen thisreferencia a ese objeto.
En JavaScript thises "gratuito", su valor se evalúa en el momento de la llamada
y no depende de dónde se declaró el método, sino de qué objeto está "antes
del punto".
El concepto de tiempo de ejecución evaluado thistiene ventajas y
desventajas. Por un lado, una función se puede reutilizar para diferentes
objetos. Por otro lado, la mayor flexibilidad crea más posibilidades de errores.
Aquí nuestra posición no es juzgar si esta decisión de diseño de lenguaje es
buena o mala. Entenderemos cómo trabajar con él, cómo obtener beneficios y
evitar problemas.
Las funciones de flecha no tienen "esto"
Las funciones de flecha son especiales: no tienen su propia función this. Si
hacemos referencia thisa una función de este tipo, se toma de la función
"normal" externa.
Por ejemplo, aquí se arrow()usa thisel [Link]()método externo :
let user = {
firstName: "Ilya",
sayHi() {
let arrow = () => alert([Link]);
arrow();
};
[Link](); // Ilya
Esa es una característica especial de las funciones de flecha, es útil cuando en
realidad no queremos tener una separada this, sino tomarla del contexto
externo. Más adelante en el capítulo Funciones de flecha revisadas ,
profundizaremos en las funciones de flecha.
Resumen
Las funciones que se almacenan en las propiedades del objeto se
denominan "métodos".
Los métodos permiten que los objetos "actúen"
como [Link]().
Los métodos pueden hacer referencia al objeto como this.
El valor de thisse define en tiempo de ejecución.
Cuando se declara una función, se puede usar this, pero eso thisno
tiene valor hasta que se llama a la función.
Una función se puede copiar entre objetos.
Cuando se llama a una función en la sintaxis del
"método":, [Link]()el valor de thisdurante la llamada es object.
Tenga en cuenta que las funciones de flecha son especiales: no
tienen this. Cuando thisse accede dentro de una función de flecha, se toma
desde fuera.
22. Constructor, operador "nuevo"
La {...}sintaxis regular permite crear un objeto. Pero a menudo necesitamos
crear muchos objetos similares, como múltiples usuarios o elementos de menú,
etc.
Eso se puede hacer usando funciones constructoras y el "new"operador.
Función constructora
Las funciones de constructor técnicamente son funciones regulares. Sin
embargo, hay dos convenciones:
1. Se nombran con mayúscula primero.
2. Deben ejecutarse solo con "new"operador.
Por ejemplo:
function User(name) {
[Link] = name;
[Link] = false;
let user = new User("Jack");
alert([Link]); // Jack
alert([Link]); // false
Cuando se ejecuta una función con new, realiza los siguientes pasos:
1. Se crea y se asigna un nuevo objeto vacío this.
2. Se ejecuta el cuerpo de la función. Por lo general, lo modifica this, le
agrega nuevas propiedades.
3. Se thisdevuelve el valor de .
En otras palabras, new User(...)hace algo como:
function User(name) {
// this = {}; (implicitly)
// add properties to this
[Link] = name;
[Link] = false;
// return this; (implicitly)
Entonces let user = new User("Jack")da el mismo resultado que:
let user = {
name: "Jack",
isAdmin: false
};
Ahora, si queremos crear otros usuarios, podemos llamar new
User("Ann"), new User("Alice")y así sucesivamente. Mucho más corto que
usar literales cada vez, y también fácil de leer.
Ese es el propósito principal de los constructores: implementar código de
creación de objetos reutilizable.
Observemos una vez más: técnicamente, cualquier función se puede usar como
constructor. Es decir: se puede ejecutar con cualquier función newy ejecutará el
algoritmo anterior. La "letra mayúscula primero" es un acuerdo común, para
dejar en claro que se debe ejecutar una función new.
nueva función () {…}
Si tenemos muchas líneas de código sobre la creación de un solo objeto
complejo, podemos envolverlas en la función constructora, así:
let user = new function() {
[Link] = "John";
[Link] = false;
// ...other code for user creation
// maybe complex logic and statements
// local variables etc
};
No se puede volver a llamar al constructor, porque no se guarda en ningún
lugar, solo se crea y se llama. Entonces, este truco tiene como objetivo
encapsular el código que construye el objeto único, sin reutilización futura.
Prueba del modo de constructor: [Link]
Cosas avanzadas
La sintaxis de esta sección rara vez se usa, omítala a menos que quieras saberlo
todo.
Dentro de una función, podemos verificar si fue llamada con newo sin ella,
usando una [Link] especial .
Está vacío para llamadas regulares y es igual a la función si se llama con new:
function User() {
alert([Link]);
// without "new":
User(); // undefined
// with "new":
new User(); // function User { ... }
Eso se puede usar dentro de la función para saber si se llamó con new, "en
modo constructor", o sin él, "en modo regular".
También podemos hacer newllamadas regulares para hacer lo mismo, así:
function User(name) {
if (![Link]) { // if you run me without new
return new User(name); // ...I will add new for you
}
[Link] = name;
let john = User("John"); // redirects call to new User
alert([Link]); // John
Este enfoque se utiliza a veces en bibliotecas para hacer que la sintaxis sea más
flexible. Para que la gente pueda llamar a la función con o sin new, y todavía
funciona.
Sin embargo, probablemente no sea algo bueno para usar en todas partes,
porque omitir newhace que sea un poco menos obvio lo que está
sucediendo. Con newtodos sabemos que se está creando el nuevo objeto.
Retorno de constructores
Por lo general, los constructores no tienen una returndeclaración. Su tarea es
escribir todas las cosas necesarias thisy automáticamente se convierte en el
resultado.
Pero si hay una returndeclaración, entonces la regla es simple:
Si returnse llama con un objeto, se devuelve el objeto en lugar de this.
Si returnse llama con una primitiva, se ignora.
En otras palabras, returncon un objeto devuelve ese objeto, en todos los
demás casos thisse devuelve.
Por ejemplo, aquí se returnanula thisal devolver un objeto:
function BigUser() {
[Link] = "John";
return { name: "Godzilla" }; // <-- returns this object
alert( new BigUser().name ); // Godzilla, got that object
Y aquí hay un ejemplo con un vacío return(o podríamos colocar un primitivo
después, no importa):
function SmallUser() {
[Link] = "John";
return; // <-- returns this
alert( new SmallUser().name ); // John
Por lo general, los constructores no tienen una returndeclaración. Aquí
mencionamos el comportamiento especial con los objetos que regresan
principalmente por razones de integridad.
Omitir paréntesis
Por cierto, podemos omitir los paréntesis después new, si no tiene argumentos:
let user = new User; // <-- no parentheses
// same as
let user = new User();
Omitir paréntesis aquí no se considera un "buen estilo", pero la sintaxis está
permitida por la especificación.
Métodos en constructor
El uso de funciones de constructor para crear objetos proporciona una gran
flexibilidad. La función constructora puede tener parámetros que definan cómo
construir el objeto y qué poner en él.
Por supuesto, podemos agregar thisno solo propiedades, sino también
métodos.
Por ejemplo, a new User(name)continuación se crea un objeto con nameel
método dado sayHi:
function User(name) {
[Link] = name;
[Link] = function() {
alert( "My name is: " + [Link] );
};
let john = new User("John");
[Link](); // My name is: John
/*
john = {
name: "John",
sayHi: function() { ... }
*/
Para crear objetos complejos, hay una sintaxis más avanzada, clases , que
cubriremos más adelante.
Resumen
Las funciones de constructor o, brevemente, los constructores, son
funciones regulares, pero hay un acuerdo común para nombrarlas con
mayúscula primero.
Las funciones de constructor solo deben llamarse usando new. Tal
llamada implica una creación de vacío thisal principio y devolver el poblado al
final.
Podemos usar funciones de constructor para crear múltiples objetos similares.
JavaScript proporciona funciones de constructor para muchos objetos de
lenguaje integrados: como Datefechas, Setconjuntos y otros que planeamos
estudiar.
23. Encadenamiento opcional '?.'
Una adición reciente
Esta es una adición reciente al idioma. Los navegadores antiguos pueden
necesitar polyfills.
El encadenamiento opcional ?.es una forma a prueba de errores de acceder a
las propiedades de los objetos anidados, incluso si no existe una propiedad
intermedia.
El problema
Si acaba de comenzar a leer el tutorial y aprender JavaScript, tal vez el problema
aún no lo haya afectado, pero es bastante común.
Por ejemplo, algunos de nuestros usuarios tienen direcciones, pero pocos no las
proporcionaron. Entonces no podemos leer con
seguridad [Link]:
let user = {}; // the user happens to be without address
alert([Link]); // Error!
O, en el desarrollo web, nos gustaría obtener información sobre un elemento en
la página, pero puede que no exista:
// Error if the result of querySelector(...) is null
let html = [Link]('.my-element').innerHTML;
Antes de que ?.apareciera en el idioma, el &&operador estaba acostumbrado a
solucionarlo.
Por ejemplo:
let user = {}; // user has no address
alert( user && [Link] && [Link] ); //
undefined (no error)
La operación AND de la ruta completa a la propiedad asegura que todos los
componentes existen, pero es engorroso de escribir.
Encadenamiento opcional
El encadenamiento opcional ?.detiene la evaluación y regresa undefinedsi la
parte anterior ?.es undefinedo null.
Más adelante en este artículo, por brevedad, diremos que algo "existe" si no lo
es nully no lo es undefined.
Esta es la forma segura de acceder [Link]:
let user = {}; // user has no address
alert( user?.address?.street ); // undefined (no error)
Leer la dirección con user?.addressfunciona incluso si el userobjeto no existe:
let user = null;
alert( user?.address ); // undefined
alert( user?.[Link] ); // undefined
Tenga en cuenta: la ?.sintaxis hace opcional el valor anterior, pero no más.
En el ejemplo anterior, user?.solo permite userser null/undefined.
Por otro lado, si userexiste, entonces debe tener [Link]; de
lo contrario, user?.[Link] un error en el segundo punto.
No abuse del encadenamiento opcional
Debemos usar ?.solo donde esté bien que algo no exista.
Por ejemplo, si de acuerdo con nuestra lógica de codificación el userobjeto
debe estar ahí, pero addresses opcional,
entonces [Link]?.streetsería mejor.
Entonces, si userno está definido debido a un error, lo sabremos y lo
arreglaremos. De lo contrario, los errores de codificación pueden silenciarse
cuando no sea apropiado y volverse más difíciles de depurar.
La variable anterior ?.debe declararse
Si no hay ninguna variable user, se user?.anythinggenera un error:
// ReferenceError: user is not defined
user?.address;
Debe haber let/const/var user. El encadenamiento opcional funciona solo
para variables declaradas.
Cortocircuito
Como se dijo antes, el ?.inmediatamente detiene (“cortocircuita”) la evaluación
si la parte izquierda no existe.
Entonces, si hay más llamadas a funciones o efectos secundarios, no ocurren:
let user = null;
let x = 0;
user?.sayHi(x++); // nothing happens
alert(x); // 0, value not incremented
Otros casos: ?.(), ?.[]
El encadenamiento opcional ?.no es un operador, sino una construcción de
sintaxis especial, que también funciona con funciones y corchetes.
Por ejemplo, ?.()se utiliza para llamar a una función que puede no existir.
En el siguiente código, algunos de nuestros usuarios tienen un adminmétodo y
otros no:
let user1 = {
admin() {
alert("I am admin");
let user2 = {};
[Link]?.(); // I am admin
[Link]?.();
Aquí, en ambas líneas, primero usamos el punto .para obtener
la adminpropiedad, porque el objeto de usuario debe existir, por lo que es
seguro leerlo.
Luego ?.()verifica la parte izquierda: si la función de administración existe,
entonces se ejecuta (para user1). De lo contrario (para user2) la evaluación se
detiene sin errores.
La ?.[]sintaxis también funciona, si queremos usar corchetes []para acceder a
las propiedades en lugar del punto .. Al igual que en casos anteriores, permite
leer de forma segura una propiedad de un objeto que puede no existir.
let user1 = {
firstName: "John"
};
let user2 = null; // Imagine, we couldn't authorize the user
let key = "firstName";
alert( user1?.[key] ); // John
alert( user2?.[key] ); // undefined
alert( user1?.[key]?.something?.not?.existing); // undefined
También podemos usar ?.con delete:
delete user?.name; // delete [Link] if user exists
Podemos usar ?.para leer y borrar de forma segura, pero no para escribir
El encadenamiento opcional ?.no tiene ningún uso en el lado izquierdo de una
asignación:
// the idea of the code below is to write [Link], if user
exists
user?.name = "John"; // Error, doesn't work
// because it evaluates to undefined = "John"
Resumen
La ?.sintaxis tiene tres formas:
1. obj?.prop- devuelve [Link] objexiste, de lo contrario undefined.
2. obj?.[prop]- devuelve obj[prop]si objexiste, de lo
contrario undefined.
3. obj?.method()- llama [Link]()si objexiste, de lo contrario
regresa undefined.
Como podemos ver, todos son sencillos y fáciles de usar. Los ?.controles de la
parte izquierda de null/undefinedy permite la evaluación de proceder si no es
así.
Una cadena de ?.permite acceder de forma segura a propiedades anidadas.
Aún así, debemos aplicarlo con ?.cuidado, solo donde esté bien que la parte
izquierda no exista.
Para que no nos oculte errores de programación, si ocurren.
24. Tipo de símbolo
Por especificación, las claves de propiedad del objeto pueden ser de tipo
cadena o de tipo símbolo. No números, no booleanos, solo cadenas o símbolos,
estos dos tipos.
Hasta ahora hemos estado usando solo cadenas. Ahora veamos los beneficios
que nos pueden dar los símbolos.
Simbolos
Un "símbolo" representa un identificador único.
Se puede crear un valor de este tipo usando Symbol():
// id is a new symbol
let id = Symbol();
Una vez creado, podemos darle al símbolo una descripción (también llamado
nombre de símbolo), que es muy útil para fines de depuración:
// id is a symbol with the description "id"
let id = Symbol("id");
Se garantiza que los símbolos son únicos. Incluso si creamos muchos símbolos
con la misma descripción, son valores diferentes. La descripción es solo una
etiqueta que no afecta a nada.
Por ejemplo, aquí hay dos símbolos con la misma descripción, no son iguales:
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
Si está familiarizado con Ruby u otro idioma que también tenga algún tipo de
"símbolos", no se equivoque. Los símbolos de JavaScript son diferentes.
Los símbolos no se convierten automáticamente en una cadena
La mayoría de los valores en JavaScript admiten la conversión implícita a una
cadena. Por ejemplo, podemos alertcasi cualquier valor y funcionará. Los
símbolos son especiales. No se convierten automáticamente.
Por ejemplo, esto alertmostrará un error:
let id = Symbol("id");
alert(id); // TypeError: Cannot convert a Symbol value to a
string
Eso es una “protección del lenguaje” contra el desorden, porque las cadenas y
los símbolos son fundamentalmente diferentes y no deben convertirse
accidentalmente uno en otro.
Si realmente queremos mostrar un símbolo, debemos invocarlo
explícitamente .toString(), como aquí:
let id = Symbol("id");
alert([Link]()); // Symbol(id), now it works
O obtenga la [Link] para mostrar solo la descripción:
let id = Symbol("id");
alert([Link]); // id
Propiedades "ocultas"
Los símbolos nos permiten crear propiedades "ocultas" de un objeto, a las que
ninguna otra parte del código puede acceder o sobrescribir accidentalmente.
Por ejemplo, si estamos trabajando con userobjetos que pertenecen a un
código de terceros. Nos gustaría agregarles identificadores.
Usemos una tecla de símbolo para ello:
let user = { // belongs to another code
name: "John"
};
let id = Symbol("id");
user[id] = 1;
alert( user[id] ); // we can access the data using the symbol as
the key
¿Cuál es el beneficio de usar Symbol("id")sobre una cuerda "id"?
Como los userobjetos pertenecen a otro código, y ese código también
funciona con ellos, no deberíamos simplemente agregarle campos. Eso es
inseguro. Pero no se puede acceder a un símbolo accidentalmente, el código de
terceros probablemente ni siquiera lo verá, por lo que probablemente esté bien
hacerlo.
Además, imagine que otro script quiere tener su propio identificador
dentro user, para sus propios fines. Esa puede ser otra biblioteca de JavaScript,
por lo que los scripts no se conocen por completo.
Entonces ese script puede crear el suyo propio Symbol("id"), así:
// ...
let id = Symbol("id");
user[id] = "Their id value";
No habrá conflicto entre nuestros identificadores y sus identificadores, porque
los símbolos siempre son diferentes, incluso si tienen el mismo nombre.
... Pero si usamos una cadena en "id"lugar de un símbolo para el mismo
propósito, entonces habría un conflicto:
let user = { name: "John" };
// Our script uses "id" property
[Link] = "Our id value";
// ...Another script also wants "id" for its purposes...
[Link] = "Their id value"
// Boom! overwritten by another script!
Símbolos en un objeto literal
Si queremos usar un símbolo en un objeto literal {...}, necesitamos corchetes
alrededor.
Me gusta esto:
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // not "id": 123
};
Eso es porque necesitamos el valor de la variable idcomo clave, no la cadena
"id".
Los símbolos se omiten durante ... en
Las propiedades simbólicas no participan en el for..inbucle.
Por ejemplo:
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
for (let key in user) alert(key); // name, age (no symbols)
// the direct access by the symbol works
alert( "Direct: " + user[id] );
[Link](user)también los ignora. Eso es parte del principio general de
"ocultar propiedades simbólicas". Si otro script o biblioteca recorre nuestro
objeto, no accederá inesperadamente a una propiedad simbólica.
Por el contrario, [Link] copia las propiedades de cadena y símbolo:
let id = Symbol("id");
let user = {
[id]: 123
};
let clone = [Link]({}, user);
alert( clone[id] ); // 123
Aquí no hay ninguna paradoja. Eso es por diseño. La idea es que cuando
clonamos un objeto o fusionamos objetos, normalmente queremos que se
copien todas las propiedades (incluidos los símbolos como id).
Símbolos globales
Como hemos visto, normalmente todos los símbolos son diferentes, incluso si
tienen el mismo nombre. Pero a veces queremos que los símbolos del mismo
nombre sean las mismas entidades. Por ejemplo, diferentes partes de nuestra
aplicación quieren acceder al símbolo que "id"significa exactamente la misma
propiedad.
Para lograrlo, existe un registro de símbolos global . Podemos crear símbolos en
él y acceder a ellos más tarde, y garantiza que accesos repetidos con el mismo
nombre devuelvan exactamente el mismo símbolo.
Para leer (crear si está ausente) un símbolo del registro,
utilice [Link](key).
Esa llamada verifica el registro global, y si hay un símbolo descrito como key, lo
devuelve; de lo contrario, crea un nuevo símbolo Symbol(key)y lo almacena en
el registro por el dado key.
Por ejemplo:
// read from the global registry
let id = [Link]("id"); // if the symbol did not exist, it is
created
// read it again (maybe from another part of the code)
let idAgain = [Link]("id");
// the same symbol
alert( id === idAgain ); // true
Los símbolos dentro del registro se denominan símbolos globales . Si queremos
un símbolo para toda la aplicación, accesible en todas partes del código, para
eso están.
Eso suena como ruby
En algunos lenguajes de programación, como Ruby, hay un solo símbolo por
nombre.
En JavaScript, como podemos ver, eso es correcto para los símbolos globales.
[Link]
Para los símbolos globales, no solo [Link](key)devuelve un símbolo por
nombre, sino que hay una llamada inversa:, [Link](sym)que hace lo
contrario: devuelve un nombre mediante un símbolo global.
Por ejemplo:
// get symbol by name
let sym = [Link]("name");
let sym2 = [Link]("id");
// get name by symbol
alert( [Link](sym) ); // name
alert( [Link](sym2) ); // id
La [Link] utiliza el registro global de símbolo para
buscar la clave para el símbolo. Por tanto, no funciona con símbolos no
globales. Si el símbolo no es global, no podrá encontrarlo y
regresará undefined.
Dicho esto, cualquier símbolo tiene descriptionpropiedad.
Por ejemplo:
let globalSymbol = [Link]("name");
let localSymbol = Symbol("name");
alert( [Link](globalSymbol) ); // name, global symbol
alert( [Link](localSymbol) ); // undefined, not global
alert( [Link] ); // name
Símbolos del sistema
Existen muchos símbolos de "sistema" que JavaScript usa internamente, y
podemos usarlos para ajustar varios aspectos de nuestros objetos.
Se enumeran en la especificación en la tabla de símbolos conocidos :
[Link]
[Link]
[Link]
[Link]
…y así.
Por ejemplo, [Link] permite describir la conversión de
objeto a primitivo. Veremos su uso muy pronto.
Otros símbolos también se familiarizarán cuando estudiemos las características
del lenguaje correspondiente.
Resumen
Symbol es un tipo primitivo para identificadores únicos.
Los símbolos se crean con una Symbol()llamada con una descripción opcional
(nombre).
Los símbolos son siempre valores diferentes, incluso si tienen el mismo
nombre. Si queremos que los símbolos del mismo nombre sean iguales,
entonces debemos usar el registro global: [Link](key)devuelve (crea si
es necesario) un símbolo global con keyel nombre. Varias llamadas
de [Link] el mismo keydevuelven exactamente el mismo símbolo.
Los símbolos tienen dos casos de uso principales:
1. Propiedades del objeto "oculto". Si queremos agregar una propiedad a
un objeto que “pertenece” a otro script o biblioteca, podemos crear un símbolo
y usarlo como clave de propiedad. Una propiedad simbólica no
aparece for..in, por lo que no se procesará accidentalmente junto con otras
propiedades. Además, no se accederá directamente, porque otro script no tiene
nuestro símbolo. Por lo tanto, la propiedad estará protegida contra el uso
accidental o sobreescritura.
De modo que podemos ocultar algo "encubiertamente" en los objetos que
necesitamos, pero otros no deberían ver, utilizando propiedades simbólicas.
2. Hay muchos símbolos del sistema utilizados por JavaScript a los que se
puede acceder como Symbol.*. Podemos usarlos para alterar algunos
comportamientos incorporados. Por ejemplo, más adelante en el tutorial lo
usaremos [Link] iterables , [Link]
configurar la conversión de objeto a primitivo, etc.
Técnicamente, los símbolos no están 100% ocultos. Hay un método incorporado
[Link] (obj) que nos permite obtener todos los
símbolos. También hay un método llamado [Link] (obj) que
devuelve todas las claves de un objeto, incluidas las simbólicas. Por tanto, no
están realmente ocultos. Pero la mayoría de las bibliotecas, funciones integradas
y construcciones de sintaxis no utilizan estos métodos.
25. Objeto a conversión primitiva
¿Qué sucede cuando se suman obj1 + obj2, restan obj1 - obj2o
imprimen objetos usando alert(obj)?
En ese caso, los objetos se convierten automáticamente en primitivas y luego se
lleva a cabo la operación.
En el capítulo Conversiones de tipos , hemos visto las reglas para las
conversiones numéricas, de cadena y booleanas de primitivas. Pero dejamos un
hueco para los objetos. Ahora, como sabemos sobre métodos y símbolos, es
posible completarlo.
1. Todos los objetos están trueen un contexto booleano. Solo hay
conversiones numéricas y de cadenas.
2. La conversión numérica ocurre cuando restamos objetos o aplicamos
funciones matemáticas. Por ejemplo, los Dateobjetos (que se tratarán en el
capítulo Fecha y hora ) se pueden restar y el resultado de date1 - date2es la
diferencia de tiempo entre dos fechas.
3. En cuanto a la conversión de cadenas, generalmente ocurre cuando
generamos un objeto como alert(obj)y en contextos similares.
ToPrimitive
Podemos ajustar la conversión numérica y de cadenas utilizando métodos de
objetos especiales.
Hay tres variantes de conversión de tipos, las denominadas "sugerencias", que
se describen en la especificación :
"string"
Para una conversión de objeto a cadena, cuando estamos haciendo una
operación en un objeto que espera una cadena, como alert:
// output
alert(obj);
// using object as a property key
anotherObj[obj] = 123;
"number"
Para una conversión de objeto a número, como cuando estamos
haciendo matemáticas:
// explicit conversion
let num = Number(obj);
// maths (except binary plus)
let n = +obj; // unary plus
let delta = date1 - date2;
// less/greater comparison
let greater = user1 > user2;
"default"
Ocurre en casos raros cuando el operador "no está seguro" de qué tipo
esperar.
Por ejemplo, binary plus +puede funcionar tanto con cadenas (las
concatena) como con números (las agrega), por lo que tanto cadenas
como números funcionarían. Entonces, si un binario más obtiene un
objeto como argumento, usa la "default"sugerencia para convertirlo.
Además, si un objeto se compara ==con una cadena, un número o un
símbolo, tampoco está claro qué conversión se debe realizar, por lo
que "default"se usa la pista.
// binary plus uses the "default" hint
let total = obj1 + obj2;
// obj == number uses the "default" hint
if (user == 1) { ... };
Los operadores de comparación mayor y menor, como < >, también
pueden funcionar con cadenas y números. Aún así, usan
la "number"pista, no "default". Eso es por razones históricas.
Sin embargo, en la práctica, no necesitamos recordar estos detalles
peculiares, porque todos los objetos incorporados excepto un caso
( Dateobjeto, lo aprenderemos más adelante) implementan
la "default"conversión de la misma manera que "number". Y podemos
hacer lo mismo.
Sin "boolean"pista
Tenga en cuenta que solo hay tres sugerencias. Es así de simple.
No hay ninguna pista "booleana" (todos los objetos están trueen contexto
booleano) ni nada más. Y si tratamos "default"y "number"lo mismo, como lo
hacen la mayoría de los integrados, entonces solo hay dos conversiones.
Para realizar la conversión, JavaScript intenta buscar y llamar a tres métodos de
objeto:
1. Llamar obj[[Link]](hint): el método con la tecla
simbólica [Link](símbolo del sistema), si tal método existe,
2. De lo contrario, si la pista es "string"
o prueba [Link]()y [Link](), lo que sea que exista.
3. De lo contrario, si la pista es "number"o"default"
o prueba [Link]()y [Link](), lo que sea que exista.
Símbolo a Primitivo
Empecemos por el primer método. Hay un símbolo incorporado
llamado [Link] debe usarse para nombrar el método de
conversión, como este:
obj[[Link]] = function(hint) {
// must return a primitive value
// hint = one of "string", "number", "default"
};
Por ejemplo, aquí el userobjeto lo implementa:
let user = {
name: "John",
money: 1000,
[[Link]](hint) {
alert(`hint: ${hint}`);
return hint == "string" ? `{name: "${[Link]}"}` :
[Link];
};
// conversions demo:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500
Como podemos ver en el código, se userconvierte en una cadena
autodescriptiva o una cantidad de dinero dependiendo de la conversión. El
método único user[[Link]]maneja todos los casos de
conversión.
toString / valueOf
Métodos toStringy valueOfproceden de la antigüedad. No son símbolos (los
símbolos no existían hace tanto tiempo), sino métodos "regulares" con nombre
de cadena. Proporcionan una forma alternativa "al estilo antiguo" de
implementar la conversión.
Si no hay [Link], JavaScript intenta encontrarlos y probar en el
orden:
toString -> valueOf para la pista de "cadena".
valueOf -> toString de otra manera.
Estos métodos deben devolver un valor
primitivo. Si toStringo valueOfdevuelve un objeto, se ignora (lo mismo que si
no hubiera un método).
De forma predeterminada, un objeto simple tiene los
siguientes métodos toStringy valueOf:
El toStringmétodo devuelve una cadena "[object Object]".
El valueOfmétodo devuelve el objeto en sí.
Aquí está la demostración:
let user = {name: "John"};
alert(user); // [object Object]
alert([Link]() === user); // true
Entonces, si intentamos usar un objeto como una cadena, como en uno alerto
así, entonces vemos por defecto [object Object].
Y el valor predeterminado valueOfse menciona aquí solo en aras de la
integridad, para evitar cualquier confusión. Como puede ver, devuelve el objeto
en sí, por lo que se ignora. No me preguntes por qué, eso es por razones
históricas. Entonces podemos asumir que no existe.
Implementemos estos métodos.
Por ejemplo, aquí userhace lo mismo que el anterior usando una combinación
de toStringy en valueOflugar de [Link]:
let user = {
name: "John",
money: 1000,
// for hint="string"
toString() {
return `{name: "${[Link]}"}`;
},
// for hint="number" or "default"
valueOf() {
return [Link];
};
alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
Como podemos ver, el comportamiento es el mismo que en el ejemplo anterior
con [Link].
A menudo queremos un único lugar "general" para manejar todas las
conversiones primitivas. En este caso, solo podemos implementar toString, así:
let user = {
name: "John",
toString() {
return [Link];
};
alert(user); // toString -> John
alert(user + 500); // toString -> John500
En ausencia de [Link] valueOf, toStringmanejará todas las
conversiones primitivas.
Tipos de devolución
Lo importante que debe saber acerca de todos los métodos de conversión
primitiva es que no necesariamente devuelven la primitiva "insinuada".
No hay control si toStringdevuelve exactamente una cadena o si
el [Link]étodo devuelve un número para una pista "number".
Lo único obligatorio: estos métodos deben devolver una primitiva, no un objeto.
Notas históricas
Por razones históricas, si toStringo valueOfdevuelve un objeto, no hay error,
pero dicho valor se ignora (como si el método no existiera). Eso es porque en la
antigüedad no había un buen concepto de "error" en JavaScript.
Por el contrario, [Link] debe devolver una primitiva, de lo
contrario habrá un error.
Más conversiones
Como ya sabemos, muchos operadores y funciones realizan conversiones de
tipos, por ejemplo, la multiplicación *convierte operandos en números.
Si pasamos un objeto como argumento, entonces hay dos etapas:
1. El objeto se convierte en un primitivo (utilizando las reglas descritas
anteriormente).
2. Si la primitiva resultante no es del tipo correcto, se convierte.
Por ejemplo:
let obj = {
// toString handles all conversions in the absence of other
methods
toString() {
return "2";
};
alert(obj * 2); // 4, object converted to primitive "2", then
multiplication made it a number
1. La multiplicación obj * 2primero convierte el objeto en primitivo (que
es una cadena "2").
2. Luego se "2" * 2convierte en 2 * 2(la cadena se convierte en número).
Binary plus concatenará cadenas en la misma situación, ya que acepta con gusto
una cadena:
let obj = {
toString() {
return "2";
};
alert(obj + 2); // 22 ("2" + 2), conversion to primitive
returned a string => concatenation
Resumen
La conversión de objeto a primitivo es llamada automáticamente por muchas
funciones y operadores incorporados que esperan un primitivo como valor.
Hay 3 tipos (pistas) de esto:
"string"(para alerty otras operaciones que necesitan una cadena)
"number" (para matemáticas)
"default" (pocos operadores)
La especificación describe explícitamente qué operador usa qué sugerencia. Hay
muy pocos operadores que "no saben qué esperar" y usan
la "default"pista. Por lo general, para los objetos incorporados,
la "default"sugerencia se maneja de la misma manera que "number", por lo
que en la práctica, los dos últimos a menudo se combinan.
El algoritmo de conversión es:
1. Llame obj[[Link]](hint)si el método existe,
2. De lo contrario, si la pista es "string"
o prueba [Link]()y [Link](), lo que sea que exista.
3. De lo contrario, si la pista es "number"o"default"
o prueba [Link]()y [Link](), lo que sea que exista.
En la práctica, a menudo es suficiente implementar solo [Link]()como
un método "general" para todas las conversiones que devuelven una
representación "legible por humanos" de un objeto, con fines de registro o
depuración.
26. Métodos JSON, toJSON
Digamos que tenemos un objeto complejo y nos gustaría convertirlo en una
cadena, enviarlo a través de una red o simplemente generarlo con fines de
registro.
Naturalmente, dicha cadena debe incluir todas las propiedades importantes.
Podríamos implementar la conversión de esta manera:
let user = {
name: "John",
age: 30,
toString() {
return `{name: "${[Link]}", age: ${[Link]}}`;
};
alert(user); // {name: "John", age: 30}
… Pero en el proceso de desarrollo, se agregan nuevas propiedades, las
propiedades antiguas se renombran y eliminan. Actualizarlo toStringcada vez
puede convertirse en una molestia. Podríamos intentar recorrer las propiedades
en él, pero ¿qué pasa si el objeto es complejo y tiene objetos anidados en las
propiedades? Necesitaríamos implementar su conversión también.
Afortunadamente, no es necesario escribir el código para manejar todo esto. La
tarea ya está resuelta.
[Link]
El JSON (JavaScript Object Notation) es un formato general para representar los
valores y objetos. Se describe como en el estándar RFC 4627 . Inicialmente se
hizo para JavaScript, pero muchos otros lenguajes también tienen bibliotecas
para manejarlo. Por lo tanto, es fácil usar JSON para el intercambio de datos
cuando el cliente usa JavaScript y el servidor está escrito en Ruby / PHP / Java /
Whatever.
JavaScript proporciona métodos:
[Link] para convertir objetos en JSON.
[Link] para convertir JSON de nuevo en un objeto.
Por ejemplo, aquí somos [Link] estudiante:
let student = {
name: 'John',
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
wife: null
};
let json = [Link](student);
alert(typeof json); // we've got a string!
alert(json);
/* JSON-encoded object:
"name": "John",
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
"wife": null
*/
El método [Link](student)toma el objeto y lo convierte en una
cadena.
El resultante jsoncadena se denomina JSON
codificado o serializado o stringified o marshalled objeto. Estamos listos para
enviarlo por cable o ponerlo en un almacén de datos simple.
Tenga en cuenta que un objeto codificado en JSON tiene varias diferencias
importantes con el objeto literal:
Las cadenas utilizan comillas dobles. No hay comillas simples ni comillas
inversas en JSON. Así se 'John'convierte "John".
Los nombres de las propiedades de los objetos también están entre
comillas dobles. Eso es obligatorio. Así se age:30convierte "age":30.
[Link] también se puede aplicar a primitivas.
JSON admite los siguientes tipos de datos:
Objetos { ... }
Matrices [ ... ]
Primitivas:
o instrumentos de cuerda,
o números,
o valores booleanos true/false,
o null.
Por ejemplo:
// a number in JSON is just a number
alert( [Link](1) ) // 1
// a string in JSON is still a string, but double-quoted
alert( [Link]('test') ) // "test"
alert( [Link](true) ); // true
alert( [Link]([1, 2, 3]) ); // [1,2,3]
JSON es una especificación independiente del lenguaje de solo datos, por lo
que se omiten algunas propiedades de objetos específicas de
JavaScript [Link].
A saber:
Propiedades de la función (métodos).
Propiedades simbólicas.
Propiedades que almacenan undefined.
let user = {
sayHi() { // ignored
alert("Hello");
},
[Symbol("id")]: 123, // ignored
something: undefined // ignored
};
alert( [Link](user) ); // {} (empty object)
Normalmente eso está bien. Si eso no es lo que queremos, pronto veremos
cómo personalizar el proceso.
Lo mejor es que los objetos anidados se admiten y se convierten
automáticamente.
Por ejemplo:
let meetup = {
title: "Conference",
room: {
number: 23,
participants: ["john", "ann"]
};
alert( [Link](meetup) );
/* The whole structure is stringified:
"title":"Conference",
"room":{"number":23,"participants":["john","ann"]},
*/
La limitación importante: no debe haber referencias circulares.
Por ejemplo:
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: ["john", "ann"]
};
[Link] = room; // meetup references room
[Link] = meetup; // room references meetup
[Link](meetup); // Error: Converting circular structure
to JSON
Aquí, la conversión falla, debido a la referencia
circular: [Link] meetupy [Link] room:
Excluyendo y transformando: reemplazante
La sintaxis completa de [Link]:
let json = [Link](value[, replacer, space])
valor
Un valor para codificar.
sustituto
Matriz de propiedades para codificar o función de
mapeo function(key, value).
espacio
Cantidad de espacio a utilizar para formatear
La mayoría de las veces, [Link] usa solo con el primer
argumento. Pero si necesitamos ajustar el proceso de reemplazo, como para
filtrar referencias circulares, podemos usar el segundo argumento
de [Link].
Si le pasamos una matriz de propiedades, solo se codificarán estas propiedades.
Por ejemplo:
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup references room
};
[Link] = meetup; // room references meetup
alert( [Link](meetup, ['title', 'participants']) );
// {"title":"Conference","participants":[{},{}]}
Aquí probablemente seamos demasiado estrictos. La lista de propiedades se
aplica a toda la estructura del objeto. Entonces los objetos
de participantsestán vacíos, porque nameno están en la lista.
Incluyamos en la lista todas las propiedades excepto la [Link]
causaría la referencia circular:
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup references room
};
[Link] = meetup; // room references meetup
alert( [Link](meetup, ['title', 'participants', 'place',
'name', 'number']) );
/*
"title":"Conference",
"participants":[{"name":"John"},{"name":"Alice"}],
"place":{"number":23}
*/
Ahora todo excepto occupiedByestá serializado. Pero la lista de propiedades es
bastante larga.
Afortunadamente, podemos usar una función en lugar de una matriz
como replacer.
La función se llamará para cada (key, value)par y debería devolver el valor
"reemplazado", que se utilizará en lugar del original. O undefinedsi se debe
omitir el valor.
En nuestro caso, podemos devolver value"tal cual" para todo
excepto occupiedBy. Para ignorar occupiedBy, el siguiente código
devuelve undefined:
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup references room
};
[Link] = meetup; // room references meetup
alert( [Link](meetup, function replacer(key, value) {
alert(`${key}: ${value}`);
return (key == 'occupiedBy') ? undefined : value;
}));
/* key:value pairs that come to replacer:
: [object Object]
title: Conference
participants: [object Object],[object Object]
0: [object Object]
name: John
1: [object Object]
name: Alice
place: [object Object]
number: 23
*/
Tenga en cuenta que la replacerfunción obtiene cada par clave / valor,
incluidos los objetos anidados y los elementos de la matriz. Se aplica de forma
recursiva. El valor de thisinside replaceres el objeto que contiene la
propiedad actual.
La primera llamada es especial. Se hace uso de un especial de “envoltorio
objeto”: {"": meetup}. En otras palabras, el primer (key, value)par tiene
una clave vacía y el valor es el objeto de destino como un todo. Es por eso que
la primera línea está ":[object Object]"en el ejemplo anterior.
La idea es proporcionar tanta potencia replacercomo sea posible: tiene la
oportunidad de analizar y reemplazar / omitir incluso el objeto completo si es
necesario.
Formateo: espacio
El tercer argumento de [Link](value, replacer, space)es el
número de espacios a usar para un formato bonito.
Anteriormente, todos los objetos en cadena no tenían sangrías ni espacios
adicionales. Eso está bien si queremos enviar un objeto a través de una
red. El spaceargumento se usa exclusivamente para una buena salida.
Aquí space = 2le dice a JavaScript que muestre objetos anidados en varias
líneas, con sangría de 2 espacios dentro de un objeto:
let user = {
name: "John",
age: 25,
roles: {
isAdmin: false,
isEditor: true
}
};
alert([Link](user, null, 2));
/* two-space indents:
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
*/
/* for [Link](user, null, 4) the result would be more
indented:
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
*/
El spaceparámetro se utiliza únicamente para fines de registro y salida
agradable.
Personalizado "toJSON"
Al igual que toStringpara la conversión de cadenas, un objeto puede
proporcionar un método toJSONpara la conversión a JSON. [Link]
llama automáticamente si está disponible.
Por ejemplo:
let room = {
number: 23
};
let meetup = {
title: "Conference",
date: new Date([Link](2017, 0, 1)),
room
};
alert( [Link](meetup) );
/*
"title":"Conference",
"date":"2017-01-01T00:00:00.000Z", // (1)
"room": {"number":23} // (2)
*/
Aquí podemos ver que se date (1)convirtió en una cuerda. Eso es porque
todas las fechas tienen un toJSONmétodo incorporado que devuelve ese tipo
de cadena.
Ahora agreguemos una personalizada toJSONpara nuestro objeto room (2):
let room = {
number: 23,
toJSON() {
return [Link];
};
let meetup = {
title: "Conference",
room
};
alert( [Link](room) ); // 23
alert( [Link](meetup) );
/*
"title":"Conference",
"room": 23
*/
Como podemos ver, toJSONse usa tanto para la llamada
directa [Link](room)como cuando roomestá anidado en otro objeto
codificado.
[Link]
Para decodificar una cadena JSON, necesitamos otro método
llamado [Link] .
La sintaxis:
let value = [Link](str, [reviver]);
str
JSON-string para analizar.
vivificador
Función opcional (clave, valor) que se llamará para cada (key,
value)par y puede transformar el valor.
Por ejemplo:
// stringified array
let numbers = "[0, 1, 2, 3]";
numbers = [Link](numbers);
alert( numbers[1] ); // 1
O para objetos anidados:
let userData = '{ "name": "John", "age": 35, "isAdmin": false,
"friends": [0,1,2,3] }';
let user = [Link](userData);
alert( [Link][1] ); // 1
El JSON puede ser tan complejo como sea necesario, los objetos y las matrices
pueden incluir otros objetos y matrices. Pero deben obedecer el mismo formato
JSON.
Aquí hay errores típicos en JSON escrito a mano (a veces tenemos que escribirlo
con fines de depuración):
let json = `{
name: "John", // mistake: property name
without quotes
"surname": 'Smith', // mistake: single quotes in
value (must be double)
'isAdmin': false // mistake: single quotes in
key (must be double)
"birthday": new Date(2000, 2, 3), // mistake: no "new" is
allowed, only bare values
"friends": [0,1,2,3] // here all fine
}`;
Además, JSON no admite comentarios. Agregar un comentario a JSON lo
invalida.
Hay otro formato llamado JSON5 , que permite claves sin comillas, comentarios,
etc. Pero esta es una biblioteca independiente, no en la especificación del
lenguaje.
El JSON normal es tan estricto no porque sus desarrolladores sean perezosos,
sino para permitir implementaciones fáciles, confiables y muy rápidas del
algoritmo de análisis.
Usando reviver
Imagínese, obtuvimos un meetupobjeto en cadena del servidor.
Se parece a esto:
// title: (meetup title), date: (meetup date)
let str = '{"title":"Conference","date":"2017-11-
30T12:00:00.000Z"}';
... Y ahora tenemos que deserializarlo , para convertirlo de nuevo en un objeto
JavaScript.
Hagámoslo llamando a [Link]:
let str = '{"title":"Conference","date":"2017-11-
30T12:00:00.000Z"}';
let meetup = [Link](str);
alert( [Link]() ); // Error!
¡Ups! ¡Un error!
El valor de [Link] una cadena, no un Dateobjeto. ¿Cómo
podría [Link] que debería transformar esa cadena en una Date?
Pasemos a [Link] función reviving como segundo argumento, que
devuelve todos los valores "como están", pero datese convertirá en Date:
let str = '{"title":"Conference","date":"2017-11-
30T12:00:00.000Z"}';
let meetup = [Link](str, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert( [Link]() ); // now works!
Por cierto, eso también funciona para objetos anidados:
let schedule = `{
"meetups": [
{"title":"Conference","date":"2017-11-30T12:00:00.000Z"},
{"title":"Birthday","date":"2017-04-18T12:00:00.000Z"}
}`;
schedule = [Link](schedule, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert( [Link][1].[Link]() ); // works!
Resumen
JSON es un formato de datos que tiene su propio estándar
independiente y bibliotecas para la mayoría de los lenguajes de programación.
JSON admite objetos simples, matrices, cadenas, números, valores
booleanos y null.
JavaScript proporciona métodos [Link] para serializar en JSON
y [Link] para leer desde JSON.
Ambos métodos admiten funciones de transformador para lectura /
escritura inteligente.
Si un objeto tiene toJSON, lo llama [Link].
27. Métodos GET y POST
¿Qué es HTTP?
HTTP (Hypertext Transfer Protocol) está diseñado para permitir una
comunicación entre clientes y servidores.
HTTP funciona como un protocolo de pedido-respuesta entre el cliente y
servidor
Un navegador web puede ser el cliente, y la aplicación en una computadora que
aloja un sitio web puede ser el servidor.
Ejemplo: Un cliente (navegador web) envía un pedido HTTP al servidor, luego el
servidor responde con una respuesta al cliente, la respuesta contiene la
información de estado sobre el pedido realizado y también podrá contener el
contenido solicitado (pedido). Este contenido puede ser un archivo .html (una
página web), o archivo a descargar, etc.
Método GET
El método GET se utiliza para solicitar la URL de un servidor web para así
obtener los documentos HTML. El método GET es uno de los métodos HTTP
más utilizado.
Notar que los parámetros de consulta que envía, se encuentran en la URL
mismo, en la forma par valor:
/test/demo_form.php?nombre1=valor1&nombre2=valor2
El método GET tiene algunos problemas de seguridad porque los datos
insertados son visibles en la URL. Sólo una cantidad restringida de datos puede
ser pasada a través del método GET, ya que la longitud de la URL que un
navegador puede atravesar podría ser de mil caracteres.
Método POST
El método POST es adecuado en la situación en la que una cantidad significativa
de información puede transmitirse.
Cuando un servidor recibe la solicitud a través de un formulario que utiliza
POST, sigue “escuchando” la información que queda. En pocas palabras, el
método transfiere toda la información relevante de la entrada del
formulario inmediatamente después de que se realiza la solicitud a la URL.
El método POST necesita establecer dos contactos con el servidor web mientras
que GET sólo hace uno. Las peticiones en el POST se gestionan de la misma
manera que en el método GET, donde los espacios se representan en el signo
más (+) y los caracteres restantes se codifican en el patrón URL. También puede
enviar los elementos de un archivo.
En este caso los parámetros se envían en el cuerpo del mensaje, y no en la URL
mismo (cómo sí lo hace el método GET):
/test/demo_form.php HTTP/1.1
Host: [Link]
name1=value1&name2=value2
Diferencias entre el método GET y POST
Después de ver por separado en qué consisten el método GET y POST, ahora
vamos a ver sus principales diferencias para así entenderlos mejor.
GET y POST son dos técnicas eficientes que pueden enviar los datos a un
servidor o navegador y necesariamente que estos se comuniquen. Los dos
métodos son distintos cuando el método GET añade los datos codificados a la
URI, mientras que en el caso del método POST los datos se añaden al cuerpo y
no a la URI. Además, se utiliza el método GET para recuperar los datos. Por el
contrario, el método POST se utiliza para almacenar o actualizar los datos.
La etiqueta <form> se utiliza para expresar el contenido del formulario; esto
también se conoce como control de formulario. Estos formularios se rellenan
con los datos correspondientes y se envían a la máquina remota para su
posterior procesamiento. El funcionamiento del formulario incluye dos cosas
cruciales: la primera es la especificación de la dirección del programa que
maneja el contenido del formulario con la ayuda de action. Más adelante se
encuentra la especificación del método dentro del cual fluyen los datos del
formulario con la ayuda del atributo method.
El atributo actiondescribe cómo se debe manejar el formulario HTML. El atributo
methodgestiona el proceso de presentación de los datos. Los métodos GET y
POST se encuentran bajo el atributo method.
El método GET coloca los parámetros dentro de la URI mientras que el método
POST los agrega al cuerpo.
2
GET se utiliza esencialmente para obtener la información. En cambio, el
objetivo del método POST es actualizar los datos.
Los resultados de la consulta POST no pueden marcarse, mientras que los
resultados de la consulta GET pueden marcarse porque existen en forma de
URL.
En el método GET la información es visible en la URL lo que aumenta las
vulnerabilidades y el riesgo de hacking. Por el contrario, el método POST no
muestra variables en la URL y también se pueden utilizar múltiples técnicas de
codificación, lo que lo hace resistente.
Cuando se utiliza el método GET en el formulario, sólo se aceptan caracteres
ASCII en los tipos de datos. Por el contrario, el método POST no enlaza tipos de
datos de formulario y permite caracteres binarios y ASCII.
El tamaño variable en el método GET es de aproximadamente 2000 caracteres.
A la inversa, el método POST permite hasta 8 Mb de tamaño variable.
Los datos del método GET se pueden almacenar en caché, mientras que los
datos del método POST no
28. Fetch (Comunicación con el servidor)
JavaScript puede enviar pedidos por la red a un servidor, cuando se desea obtener
nueva información
Por ejemplo podemos mandar pedidos al servidor para:
Obtener la información completa de un usuario
Cargar un listado de productos
Recibir notificaciones desde un servidor
Y todo esto ¡sin refrescar la página!
Hay un término global "AJAX" (Asynchronous JavaScript And XML) para realizar
pedidos al servidor desde JavaScript. En realidad ya no se debe usar XML, el
término viene desde los viejos tiempos.
Cómo sus siglas lo indican, permite realizar pedidos a servidor de
forma asincrónica, lo cuál permite realizar pedidos sin tener que refrescar la
página
Hay múltiples formas de crear pedidos y obtener información desde el servidor.
Hoy en día el estándar desde JavaScript, se hacen a partir de la función fetch().
La sintáxis básica es:
let promise = fetch(url, [options])
url: la URL a la que se envía el pedido
options: parámetros opcionales: método HTTP, encabezados, etc.
Sin especificar options, se realizará un simple pedido GET, descargando el contenido
que se encuentre en la url especificada. Obteniendo la respuesta, generalmente se da
en un proceso de 2 fases:
Primero, la promesa, devuelta por fetch, resuelve con un objeto de tipo Response tan
pronto cómo el servidor (al que se le realizó el pedido) responda.
En esta fase podemos consultar por el estado HTTP, para saber si el pedido ha sido
respondido con éxito
La promesa es rechazada si el fetch no pudo realizar el pedido HTTP, por ejemplo por
problemas de red, url inválida, etc.
Para ver el estado HTTP (por ejemplo el número 200), en las propiedades del objeto
devuelto de tipo Response:
status: código HTTP, por ejemplo 200.
ok: booleano, verdadero si el código de estado HTTP está entre 200-
299.
La segunda fase, es obtener el cuerpo de la respuesta, para eso necesitamos realizar
una llamada adicional
El objeto de tipo Response provee múltiples métodos para acceder al cuerpo de la
respuesta en variados formatos, dos de ellos son:
[Link](): lee la respuesta y la devuelve en forma de texto
[Link](): se asume que la respuesta es notación JSON, y devuelve el
objeto construido a partir de dicha notación.
29. DOM ( Document Object Model )
Introducción a DOM (Document Object Model)
La creación del Document Object Model o DOM es una de las innovaciones que más ha
influido en el desarrollo de las páginas web dinámicas y de las aplicaciones web más
complejas.
DOM permite a los programadores web acceder y manipular las páginas HTML.
A pesar de sus orígenes, DOM se ha convertido en una utilidad disponible para la
mayoría de lenguajes de programación (Java, PHP, JavaScript) y cuyas únicas
diferencias se encuentran en la forma de implementarlo.
Árbol de nodos
Una de las tareas habituales en la programación de aplicaciones web con JavaScript
consiste en la manipulación de las páginas web. De esta forma, es habitual obtener el
valor almacenado por algunos elementos (por ejemplo, los elementos de un
formulario), crear o eliminar un elemento ( <p> , <div> , <img> , etc.) de forma
dinámica, manipular (poner/quitar/cambiar) una clase de un elemento, etc. Todas
estas tareas habituales son muy sencillas de realizar gracias a DOM, pero para ello es
necesario "cambiar" la página original.
Los navegadores web transforman automáticamente todas las páginas web en una
estructura más eficiente de manipular, el DOM.
Esta transformación la realizan todos los navegadores de forma automática y nos
permite utilizar las herramientas de DOM de forma muy sencilla. DOM transforma
todos los documentos HTML en un conjunto de elementos llamados nodos, que están
interconectados y que representan los contenidos de las páginas web y las relaciones
entre ellos. Por su aspecto, la unión de todos los nodos se llama " árbol de nodos ".
La raíz del árbol de nodos de cualquier página HTML siempre es la misma: un nodo de
tipo especial denominado document. A partir de ese nodo raíz, cada etiqueta HTML se
transforma en un nodo de tipo "Elemento". Como se puede suponer, las páginas HTML
habituales producen árboles con miles de nodos. Aun así, el proceso de transformación
es rápido y automático.
Tipos de nodo
La especificación completa de DOM define 12 tipos de nodos, aunque en las páginas
HTML se manipulan habitualmente cuatro tipos de nodos:
Document: nodo raíz del que derivan todos los demás nodos del
árbol.
Element: representa cada una de las etiquetas HTML. Se trata del
único nodo que puede contener atributos y el único del que pueden
derivar otros nodos.
Attr: se define un nodo de este tipo para representar cada uno de
los atributos de las etiquetas HTML, es decir, uno por cada par
atributo=valor.
Text: nodo que contiene el texto encerrado por una etiqueta HTML.
Acceso a nodos
Una vez que se ha accedido a un nodo, el siguiente paso consiste en acceder y/o
modificar sus atributos y propiedades. Mediante DOM, es posible acceder de forma
sencilla a todos los atributos HTML y todas las propiedades CSS de cualquier elemento
de la página.
Los atributos HTML de los elementos de la página se transforman automáticamente en
propiedades de los nodos. Para acceder a su valor, simplemente se indica el nombre
del atributo XHTML detrás del nombre del nodo.
<a href="[Link]
var enlace = [Link]('a');
alert(enlace[0].href); // mostrando la URL
En el ejemplo anterior, se obtiene el nodo DOM que representa el enlace mediante el
método [Link]() que devuelve un array (arreglo) con
todos los elementos <a> de la página. A continuación, se obtiene el atributo href del
enlace mediante enlace[0].href. Para obtener por ejemplo el atributo target, se
utilizará enlace[0].target.
Las propiedades CSS no son tan fáciles de obtener como los atributos XHTML. Para
obtener el valor de cualquier propiedad CSS del nodo, se debe utilizar el atributo style .
Si el nombre de una propiedad CSS es compuesto, se accede a su valor modificando
ligeramente su nombre. La transformación del nombre de las propiedades CSS
compuestas consiste en eliminar todos los guiones medios (-) y escribir en mayúscula
la letra siguiente a cada guión medio.
El único atributo HTML que no tiene el mismo nombre en HTML y en las propiedades
DOM es el atributo class. Como la palabra class está reservada por JavaScript, no es
posible utilizarla para acceder al atributo class del elemento HTML. En su lugar, DOM
utiliza el nombre className para acceder al atributo class de HTML.
Como hemos dicho, tenemos varias maneras de acceder a un determinado nodo:
acceder un determinado nodo:
getElementsByTagName('etiquetaHTML');
getElementsByName('nameHTML');
getElementById('id');
getElementsByClassName('class');
Creación/eliminación de nodos
Cuatro pasos para crear elementos HTML simples:
1. Creación de un nodo de tipo Element que represente al
elemento. createElement('etiqueta');.
2. Creación de un nodo de tipo Text que represente el contenido del
elemento. createTextNode('contenido');.
3. Añadir el nodo Text como nodo hijo del nodo
Element. [Link](nodoHijo);.
4. Añadir el nodo Element a la página, en forma de nodo hijo
[Link](nodoHijo); del nodo correspondiente al
lugar en el que se quiere insertar el elemento.
Ejemplos
5. Crear nodo de tipo Element
var parrafo = [Link]("p");
6. Crear nodo de tipo Text
var contenido = [Link]("Hola Mundo!");
7. Añadir el nodo Text como hijo del nodo Element
[Link](contenido);
8. Añadir el nodo Element como hijo de la página
[Link](parrafo);
9. Acceso a los atributos
Añadir y eliminar clases es una de las tareas más comunes que llevamos a cabo al
trabajar con JavaScript y el DOM. Los elementos del DOM tienen una propiedad
llamada classList la cual es una matriz que contiene todas las clases que tiene el
elemento.
Además, tiene varios métodos adicionales:
add: Añade una clase.
remove: Elimina una clase.
toggle: Añade una clase si está presente y si no, la elimina.
contains: Revisa si la clase está presente en el elemento.
Añadiendo y eliminando clases
Añadir y eliminar una clase es muy sencillo, simplemente pasa la clase que quieres
añadir/eliminar como argumento al método.