-
Notifications
You must be signed in to change notification settings - Fork 76
Expand file tree
/
Copy path18_http.html
More file actions
701 lines (470 loc) · 90.9 KB
/
Copy path18_http.html
File metadata and controls
701 lines (470 loc) · 90.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>HTTP y Formularios :: Eloquent JavaScript</title>
<link rel=stylesheet href="css/ejs.css"><script>
var page = {"type":"chapter","number":18}</script></head>
<article>
<nav><a href="17_canvas.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="19_paint.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button>
</nav>
<h1>HTTP y Formularios</h1>
<blockquote>
<p><a class="p_ident" id="p-f0gipy3BQx" href="#p-f0gipy3BQx" tabindex="-1" role="presentation"></a>Lo que más le costaba a la gente entender sobre el diseño era que no había nada más allá de las URL, HTTP y HTML. No había una computadora central “controlando” la Web, no existía una sola red en la que funcionaran estos protocolos, ni siquiera una organización en algún lugar que “dirigiera” la Web. La Web no era una “cosa” física que existía en un cierto “lugar”. Era un “espacio” en el que la información podía existir.</p>
<footer>Tim Berners-Lee</footer>
</blockquote><figure class="chapter framed"><img src="img/chapter_picture_18.jpg" alt="Ilustración mostrando un formulario de registro en la web en un pergamino"></figure>
<p><a class="p_ident" id="p-2aFcOtKP1P" href="#p-2aFcOtKP1P" tabindex="-1" role="presentation"></a>El <em>Protocolo de Transferencia de Hipertexto</em>, mencionado anteriormente en el <a href="13_browser.html#web">Capítulo 13</a>, es el mecanismo a través del cual se solicita y proporciona la información en la World Wide Web. Este capítulo describe el protocolo con más detalle y explica la forma en que el JavaScript del navegador tiene acceso a él.</p>
<h2><a class="h_ident" id="h-HKv2ecvyyl" href="#h-HKv2ecvyyl" tabindex="-1" role="presentation"></a>El protocolo</h2>
<p><a class="p_ident" id="p-brhpZDFXCC" href="#p-brhpZDFXCC" tabindex="-1" role="presentation"></a>Si escribes <em>eloquentjavascript.net/18_http.html</em> en la barra de direcciones de tu navegador, el navegador primero busca la dirección del servidor asociado con <em>eloquentjavascript.net</em> e intenta abrir una conexión TCP con él en el puerto 80, el puerto predeterminado para el tráfico HTTP. Si el servidor existe y acepta la conexión, el navegador podría enviar algo como esto:</p>
<pre class="snippet" data-language="http" ><a class="c_ident" id="c-UwbXbfsfNi" href="#c-UwbXbfsfNi" tabindex="-1" role="presentation"></a><span class="tok-keyword">GET</span> <span class="tok-string2">/18_http.html</span> <span class="tok-keyword">HTTP/1.1</span>
<span class="tok-atom">Host:</span><span class="tok-string"> eloquentjavascript.net</span>
<span class="tok-atom">User-Agent:</span><span class="tok-string"> Nombre de tu navegador</span></pre>
<p><a class="p_ident" id="p-JguTDR8pO3" href="#p-JguTDR8pO3" tabindex="-1" role="presentation"></a>Luego el servidor responde, a través de esa misma conexión.</p>
<pre class="snippet" data-language="http" ><a class="c_ident" id="c-BGoNd9zHG8" href="#c-BGoNd9zHG8" tabindex="-1" role="presentation"></a><span class="tok-keyword">HTTP/1.1</span> <span class="tok-atom">200</span> OK
<span class="tok-atom">Content-Length:</span><span class="tok-string"> 87320</span>
<span class="tok-atom">Content-Type:</span><span class="tok-string"> text/html</span>
<span class="tok-atom">Last-Modified:</span><span class="tok-string"> Vie, 13 Oct 2023 10:05:41 GMT</span>
<!doctype html>
... el resto del documento</pre>
<p><a class="p_ident" id="p-gi+KMNQ407" href="#p-gi+KMNQ407" tabindex="-1" role="presentation"></a>El navegador toma la parte de la respuesta después de la línea en blanco —su <em>cuerpo</em> (no confundir con la etiqueta HTML <code><body></code>)— y lo muestra como un documento HTML.</p>
<p><a class="p_ident" id="p-xL+6D3ieyU" href="#p-xL+6D3ieyU" tabindex="-1" role="presentation"></a>La información enviada por el cliente se llama la <em>solicitud</em>. Comienza con esta línea:</p>
<pre class="snippet" data-language="http" ><a class="c_ident" id="c-q82hekYuxl" href="#c-q82hekYuxl" tabindex="-1" role="presentation"></a><span class="tok-keyword">GET</span> <span class="tok-string2">/18_http.html</span> <span class="tok-keyword">HTTP/1.1</span></pre>
<p><a class="p_ident" id="p-qV3nDEt1J3" href="#p-qV3nDEt1J3" tabindex="-1" role="presentation"></a>La primera palabra es el <em>método</em> de la solicitud. <code>GET</code> significa que queremos <em>obtener</em> el recurso especificado. Otros métodos comunes son <code>DELETE</code> para eliminar un recurso, <code>PUT</code> para crearlo o reemplazarlo, y <code>POST</code> para enviarle información. Cabe destacar que el servidor no está obligado a llevar a cabo cada solicitud que recibe. Si vas a un sitio web aleatorio y le dices que haga <code>DELETE</code> de su página principal, probablemente se negará.</p>
<p><a class="p_ident" id="p-zXboswq37W" href="#p-zXboswq37W" tabindex="-1" role="presentation"></a>La parte después del nombre del método es la ruta del <em>recurso</em> al que aplica la solicitud. En el caso más simple, un recurso es simplemente un archivo en el servidor, pero el protocolo no lo requiere así. Un recurso puede ser cualquier cosa que pueda transferirse <em>como si fuera</em> un archivo. Muchos servidores generan las respuestas que producen al vuelo. Por ejemplo, si abres <a href="https://github.com/marijnh"><em>https://github.com/marijnh</em></a>, el servidor buscará en su base de datos un usuario llamado “marijnh” y, si lo encuentra, generará una página de perfil para ese usuario.</p>
<p><a class="p_ident" id="p-RHYp09fcSY" href="#p-RHYp09fcSY" tabindex="-1" role="presentation"></a>Después de la ruta del recurso, la primera línea de la solicitud dice <code>HTTP/1.1</code> para indicar la versión del protocolo HTTP que está utilizando.</p>
<p><a class="p_ident" id="p-YR9q/NKjNZ" href="#p-YR9q/NKjNZ" tabindex="-1" role="presentation"></a>En la práctica, muchos sitios utilizan la versión 2 de HTTP, que soporta los mismos conceptos que la versión 1.1 pero es mucho más complicada para que pueda ser más rápida. Los navegadores cambiarán automáticamente a la versión de protocolo adecuada al comunicarse con un servidor dado, y el resultado de una solicitud es el mismo independientemente de la versión utilizada. Dado que la versión 1.1 es más directa y más fácil de entender, es la que usaremos para ilustrar el protocolo.</p>
<p><a class="p_ident" id="p-grXfHJYoNS" href="#p-grXfHJYoNS" tabindex="-1" role="presentation"></a>La respuesta del servidor comenzará también con una versión, seguida del estado de la respuesta, primero como un código de estado de tres dígitos y luego como una cadena legible por humanos.</p>
<pre class="snippet" data-language="http" ><a class="c_ident" id="c-A6dkQ5Qg2b" href="#c-A6dkQ5Qg2b" tabindex="-1" role="presentation"></a><span class="tok-keyword">HTTP/1.1</span> <span class="tok-atom">200</span> OK</pre>
<p><a class="p_ident" id="p-Vf6jwTapru" href="#p-Vf6jwTapru" tabindex="-1" role="presentation"></a>Los códigos de estado que comienzan con 2 indican que la solicitud tuvo éxito. Los códigos que comienzan con 4 significan que hubo un problema con la solicitud. El 404 es probablemente el código de estado de HTTP más famoso, y significa que el recurso no se pudo encontrar. Los códigos que comienzan con 5 indican que ocurrió un error en el servidor y la solicitud no es la responsable.</p>
<p id="headers"><a class="p_ident" id="p-OJRvH7+9b5" href="#p-OJRvH7+9b5" tabindex="-1" role="presentation"></a>La primera línea de una solicitud o respuesta puede ir seguida de cualquier número de <em>cabeceras</em>. Estas son líneas en la forma <code>nombre: valor</code> que especifican información adicional sobre la solicitud o respuesta. Estas cabeceras eran parte del ejemplo de respuesta de antes:</p>
<pre class="snippet" data-language="null" ><a class="c_ident" id="c-R3sNWYeP1y" href="#c-R3sNWYeP1y" tabindex="-1" role="presentation"></a>Content-Length: 87320
Content-Type: text/html
Last-Modified: Fri, 13 Oct 2023 10:05:41 GMT</pre>
<p><a class="p_ident" id="p-A3ZMAKjMlb" href="#p-A3ZMAKjMlb" tabindex="-1" role="presentation"></a>Esto nos indica el tamaño y tipo del documento de respuesta. En este caso, es un documento HTML de 87320 bytes. También nos dice cuándo se modificó por última vez ese documento.</p>
<p><a class="p_ident" id="p-DT9FQ93OKH" href="#p-DT9FQ93OKH" tabindex="-1" role="presentation"></a>El cliente y el servidor son libres de decidir qué cabeceras incluir en sus solicitudes o respuestas. Sin embargo, algunas de ellas son necesarias para que todo funcione. Por ejemplo, sin la cabecera <code>Content-Type</code> en la respuesta, el navegador no sabrá cómo mostrar el documento.</p>
<p><a class="p_ident" id="p-PMy+17Nlv+" href="#p-PMy+17Nlv+" tabindex="-1" role="presentation"></a>Después de las cabeceras, tanto las solicitudes como las respuestas pueden incluir una línea en blanco seguida de un cuerpo, que contiene el documento real que se envía. Las solicitudes <code>GET</code> y <code>DELETE</code> no envían ningún dato, pero las solicitudes <code>PUT</code> y <code>POST</code> sí lo hacen. Algunos tipos de respuestas, como las respuestas de error, tampoco requieren un cuerpo.</p>
<h2><a class="h_ident" id="h-rs4RDnlf5a" href="#h-rs4RDnlf5a" tabindex="-1" role="presentation"></a>Navegadores y HTTP</h2>
<p><a class="p_ident" id="p-WW33cPPUpo" href="#p-WW33cPPUpo" tabindex="-1" role="presentation"></a>Como vimos, un navegador hará una solicitud cuando introducimos una URL en la barra de direcciones. Cuando la página HTML resultante hace referencia a otros archivos, como imágenes y archivos de JavaScript, el navegador los recuperará también.</p>
<p><a class="p_ident" id="p-2Vwcxm5Dhy" href="#p-2Vwcxm5Dhy" tabindex="-1" role="presentation"></a>Un sitio web moderadamente complicado puede incluir fácilmente entre 10 y 200 recursos. Para poder obtenerlos rápidamente, los navegadores harán varias solicitudes <code>GET</code> simultáneamente en lugar de esperar las respuestas una por una.</p>
<p><a class="p_ident" id="p-D0XFTzCQHq" href="#p-D0XFTzCQHq" tabindex="-1" role="presentation"></a>Las páginas HTML pueden incluir formularios, que permiten al usuario rellenar información y enviarla al servidor. A continuación se muestra un ejemplo de un formulario:</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-gna75opS++" href="#c-gna75opS++" tabindex="-1" role="presentation"></a><<span class="tok-typeName">form</span> method=<span class="tok-string">"GET"</span> action=<span class="tok-string">"example/message.html"</span>>
<<span class="tok-typeName">p</span>>Nombre: <<span class="tok-typeName">input</span> type=<span class="tok-string">"text"</span> name=<span class="tok-string">"name"</span>></<span class="tok-typeName">p</span>>
<<span class="tok-typeName">p</span>>Mensaje:<<span class="tok-typeName">br</span>><<span class="tok-typeName">textarea</span> name=<span class="tok-string">"message"</span>></<span class="tok-typeName">textarea</span>></<span class="tok-typeName">p</span>>
<<span class="tok-typeName">p</span>><<span class="tok-typeName">button</span> type=<span class="tok-string">"submit"</span>>Enviar</<span class="tok-typeName">button</span>></<span class="tok-typeName">p</span>>
</<span class="tok-typeName">form</span>></pre>
<p><a class="p_ident" id="p-96my7yKrRN" href="#p-96my7yKrRN" tabindex="-1" role="presentation"></a>Este código describe un formulario con dos campos: uno pequeño que pide un nombre y otro más grande para escribir un mensaje. Cuando se hace clic en el botón Enviar, el formulario se envía, lo que significa que el contenido de sus campos se empaqueta en una solicitud HTTP y el navegador navega hacia el resultado de esa solicitud.</p>
<p><a class="p_ident" id="p-xFXMcurm8s" href="#p-xFXMcurm8s" tabindex="-1" role="presentation"></a>Cuando el atributo <code>method</code> del elemento <code><form></code> es <code>GET</code> (o se omite), la información del formulario se agrega al final de la URL de <code>action</code> como una cadena de consulta. El navegador podría hacer una solicitud a esta URL:</p>
<pre class="snippet" data-language="null" ><a class="c_ident" id="c-1RUljJwVrY" href="#c-1RUljJwVrY" tabindex="-1" role="presentation"></a>GET /example/message.html?name=Jean&message=Yes%3F HTTP/1.1</pre>
<p><a class="p_ident" id="p-4BNZ9HklRt" href="#p-4BNZ9HklRt" tabindex="-1" role="presentation"></a>El signo de interrogación indica el final de la parte de la ruta de la URL y el inicio de la consulta. Le siguen pares de nombres y valores, correspondientes al atributo <code>name</code> en los elementos del campo del formulario y al contenido de esos elementos, respectivamente. Un carácter ampersand (<code>&</code>) se utiliza para separar los pares.</p>
<p><a class="p_ident" id="p-8eZkJZZsue" href="#p-8eZkJZZsue" tabindex="-1" role="presentation"></a>El mensaje real codificado en la URL es “Yes?”, pero el signo de interrogación se reemplaza por un código extraño. Algunos caracteres en las cadenas de consulta deben ser escapados. El signo de interrogación, representado como <code>%3F</code>, es uno de ellos. Parece haber una regla no escrita de que cada formato necesita su propia forma de escapar caracteres. Este, llamado codificación de URL, utiliza un signo de porcentaje seguido de dos dígitos hexadecimales (base 16) que codifican el código de caracteres. En este caso, 3F, que es 63 en notación decimal, es el código de un signo de interrogación. JavaScript proporciona las funciones <code>encodeURIComponent</code> y <code>decodeURIComponent</code> para codificar y decodificar este formato.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-v2cBL8Tnwf" href="#c-v2cBL8Tnwf" tabindex="-1" role="presentation"></a>console.log(encodeURIComponent(<span class="tok-string">"Yes?"</span>));
<span class="tok-comment">// → Yes%3F</span>
console.log(decodeURIComponent(<span class="tok-string">"Yes%3F"</span>));
<span class="tok-comment">// → Yes?</span></pre>
<p><a class="p_ident" id="p-GvMIy4JsJW" href="#p-GvMIy4JsJW" tabindex="-1" role="presentation"></a>Si cambiamos el atributo <code>method</code> del formulario HTML en el ejemplo que vimos anteriormente a <code>POST</code>, la solicitud HTTP realizada para enviar el formulario utilizará el método <code>POST</code> y colocará la cadena de consulta en el cuerpo de la solicitud, en lugar de agregarla a la URL.</p>
<pre class="snippet" data-language="http" ><a class="c_ident" id="c-5Xyh6tIq0d" href="#c-5Xyh6tIq0d" tabindex="-1" role="presentation"></a><span class="tok-keyword">POST</span> <span class="tok-string2">/example/message.html</span> <span class="tok-keyword">HTTP/1.1</span>
<span class="tok-atom">Content-length:</span><span class="tok-string"> 24</span>
<span class="tok-atom">Content-type:</span><span class="tok-string"> application/x-www-form-urlencoded</span>
name=Jean&message=Yes%3F</pre>
<p><a class="p_ident" id="p-RWCy4TTFSd" href="#p-RWCy4TTFSd" tabindex="-1" role="presentation"></a>Las solicitudes <code>GET</code> deben utilizarse para solicitudes que no tengan efectos secundarios, sino simplemente para solicitar información. Las solicitudes que cambian algo en el servidor, como por ejemplo crear una nueva cuenta o publicar un mensaje, deben expresarse con otros métodos, como <code>POST</code>. El software del lado del cliente, como un navegador, sabe que no debe hacer solicitudes <code>POST</code> a ciegas, pero a menudo implícitamente realiza solicitudes <code>GET</code>, por ejemplo, para precargar un recurso que cree que pronto el usuario necesitará.</p>
<p><a class="p_ident" id="p-AdKfIb+y28" href="#p-AdKfIb+y28" tabindex="-1" role="presentation"></a>Volveremos a hablar de formularios y cómo interactuar con ellos desde JavaScript <a href="18_http.html#formularios">más adelante en el capítulo</a>.</p>
<h2 id="fetch"><a class="h_ident" id="h-1Iqv5okrKE" href="#h-1Iqv5okrKE" tabindex="-1" role="presentation"></a>Fetch</h2>
<p><a class="p_ident" id="p-B4ntAb3zzU" href="#p-B4ntAb3zzU" tabindex="-1" role="presentation"></a>La interfaz a través de la cual el JavaScript del navegador puede hacer solicitudes HTTP se llama <code>fetch</code>.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-p4FlCby1YQ" href="#c-p4FlCby1YQ" tabindex="-1" role="presentation"></a>fetch(<span class="tok-string">"example/data.txt"</span>).then(<span class="tok-definition">respuesta</span> => {
console.log(respuesta.status);
<span class="tok-comment">// → 200</span>
console.log(respuesta.headers.get(<span class="tok-string">"Content-Type"</span>));
<span class="tok-comment">// → text/plain</span>
});</pre>
<p><a class="p_ident" id="p-x6ujb0jI/b" href="#p-x6ujb0jI/b" tabindex="-1" role="presentation"></a>Llamar a <code>fetch</code> devuelve una promesa que se resuelve en un objeto <code>Response</code> que contiene información sobre la respuesta del servidor, como su código de estado y sus encabezados. Los encabezados están envueltos en un objeto similar a un <code>Map</code> que trata sus claves (los nombres de los encabezados) como insensibles a mayúsculas y minúsculas porque los nombres de los encabezados no deben serlo. Esto significa que <code>headers.<wbr>get("Content-Type")</code> y <code>headers.<wbr>get("content-TYPE")</code> devolverán el mismo valor.</p>
<p><a class="p_ident" id="p-fSj4ILx7Ix" href="#p-fSj4ILx7Ix" tabindex="-1" role="presentation"></a>Ten en cuenta que la promesa devuelta por <code>fetch</code> se resuelve con éxito incluso si el servidor responde con un código de error. También puede ser rechazada si hay un error de red o si el servidor al que se dirige la solicitud no se puede encontrar.</p>
<p><a class="p_ident" id="p-bkg/oy1Hze" href="#p-bkg/oy1Hze" tabindex="-1" role="presentation"></a>El primer argumento de <code>fetch</code> es la URL que se debe solicitar. Cuando esa URL no comienza con un nombre de protocolo (como <em>http:</em>), se trata como <em>relativa</em>, lo que significa que se interpreta en relación con el documento actual. Cuando comienza con una barra (/), reemplaza la ruta actual, que es la parte después del nombre del servidor. Cuando no lo hace, la parte de la ruta actual hasta e incluyendo su último carácter de barra se coloca al principio de la URL relativa.</p>
<p><a class="p_ident" id="p-xYd9FtTyOM" href="#p-xYd9FtTyOM" tabindex="-1" role="presentation"></a>Para acceder al contenido en sí de una respuesta, puedes usar su método <code>text</code>. Como la promesa inicial se resuelve tan pronto como se reciben los encabezados de la respuesta y dado que leer el cuerpo de la respuesta podría llevar un poco más de tiempo, esto devuelve nuevamente una promesa.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-x6TPRKyHG2" href="#c-x6TPRKyHG2" tabindex="-1" role="presentation"></a>fetch(<span class="tok-string">"ejemplo/datos.txt"</span>)
.then(<span class="tok-definition">resp</span> => resp.text())
.then(<span class="tok-definition">text</span> => console.log(text));
<span class="tok-comment">// → Este es el contenido de datos.txt</span></pre>
<p><a class="p_ident" id="p-n4utrPaAws" href="#p-n4utrPaAws" tabindex="-1" role="presentation"></a>Un método similar, llamado <code>json</code>, devuelve una promesa que se resuelve al valor que obtienes al analizar el cuerpo como JSON o se rechaza si no es un JSON válido.</p>
<p><a class="p_ident" id="p-Hw44ya2xAQ" href="#p-Hw44ya2xAQ" tabindex="-1" role="presentation"></a>Por defecto, <code>fetch</code> utiliza el método <code>GET</code> para realizar su solicitud y no incluye un cuerpo de solicitud. Puedes configurarlo de manera diferente pasando un objeto con opciones adicionales como segundo argumento. Por ejemplo, esta solicitud intenta eliminar <code>example/data.txt</code>:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-hrJqISfbr3" href="#c-hrJqISfbr3" tabindex="-1" role="presentation"></a>fetch(<span class="tok-string">"example/data.txt"</span>, {<span class="tok-definition">method</span>: <span class="tok-string">"DELETE"</span>}).then(<span class="tok-definition">resp</span> => {
console.log(resp.status);
<span class="tok-comment">// → 405</span>
});</pre>
<p><a class="p_ident" id="p-0beQN19rwJ" href="#p-0beQN19rwJ" tabindex="-1" role="presentation"></a>El código de estado 405 significa “método no permitido”, la forma en que un servidor HTTP dice “Me temo que no puedo hacer eso”.</p>
<p><a class="p_ident" id="p-jNuYZ4aDPQ" href="#p-jNuYZ4aDPQ" tabindex="-1" role="presentation"></a>Para agregar un cuerpo de solicitud, puedes incluir una opción <code>body</code>. Para establecer cabeceras, está la opción <code>headers</code>. Por ejemplo, esta solicitud incluye una cabecera <code>Range</code>, que indica al servidor que devuelva solo una parte de un documento.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ayu6SNFLBc" href="#c-ayu6SNFLBc" tabindex="-1" role="presentation"></a>fetch(<span class="tok-string">"example/data.txt"</span>, {<span class="tok-definition">headers</span>: {<span class="tok-definition">Range</span>: <span class="tok-string">"bytes=8-19"</span>}})
.then(<span class="tok-definition">resp</span> => resp.text())
.then(console.log);
<span class="tok-comment">// → the content</span></pre>
<p><a class="p_ident" id="p-ARt091nKuf" href="#p-ARt091nKuf" tabindex="-1" role="presentation"></a>El navegador automáticamente añadirá algunas cabeceras de solicitud, como “Host” y aquellas necesarias para que el servidor pueda determinar el tamaño del cuerpo. Sin embargo, añadir tus propias cabeceras es muchas veces útil para incluir cosas como información de autenticación o para indicar al servidor qué formato de archivo te gustaría recibir.</p>
<h2 id="http_sandbox"><a class="h_ident" id="h-tFdPbe7u+y" href="#h-tFdPbe7u+y" tabindex="-1" role="presentation"></a>Aislamiento HTTP</h2>
<p><a class="p_ident" id="p-s0B696RpmM" href="#p-s0B696RpmM" tabindex="-1" role="presentation"></a>Realizar solicitudes HTTP en scripts de páginas web plantea preocupaciones sobre seguridad. La persona que controla el script puede no tener los mismos intereses que la persona en cuya computadora se está ejecutando. Específicamente, si visito <em>themafia.org</em>, no quiero que sus scripts puedan hacer una solicitud a <em>mybank.com</em>, utilizando información de identificación de mi navegador, con instrucciones para transferir todo mi dinero.</p>
<p><a class="p_ident" id="p-llWwqf7qfT" href="#p-llWwqf7qfT" tabindex="-1" role="presentation"></a>Por esta razón, los navegadores nos protegen al impedir que los scripts hagan solicitudes HTTP a otros dominios (nombres como <em>themafia.org</em> y <em>mybank.com</em>).</p>
<p><a class="p_ident" id="p-xeZ98zs9a0" href="#p-xeZ98zs9a0" tabindex="-1" role="presentation"></a>Esto puede ser un problema molesto al construir sistemas que necesitan acceder a varios dominios por razones legítimas. Afortunadamente, los servidores pueden incluir una cabecera como esta en sus respuestas para indicar explícitamente al navegador que está bien que la solicitud provenga de otro dominio:</p>
<pre class="snippet" data-language="null" ><a class="c_ident" id="c-kNUaXkbMWE" href="#c-kNUaXkbMWE" tabindex="-1" role="presentation"></a>Access-Control-Allow-Origin: *</pre>
<h2><a class="h_ident" id="h-gjDH4sd3Op" href="#h-gjDH4sd3Op" tabindex="-1" role="presentation"></a>Apreciando HTTP</h2>
<p><a class="p_ident" id="p-2vqlKxMwbk" href="#p-2vqlKxMwbk" tabindex="-1" role="presentation"></a>Cuando se construye un sistema que requiere comunicación entre un programa JavaScript que se ejecuta en el navegador (lado del cliente) y un programa en un servidor (lado del servidor), hay varias formas diferentes de modelar esta comunicación.</p>
<p><a class="p_ident" id="p-nl4mMNMIQt" href="#p-nl4mMNMIQt" tabindex="-1" role="presentation"></a>Un modelo comúnmente utilizado es el de las <em>llamadas de procedimiento remoto</em>. En este modelo, la comunicación sigue los patrones de llamadas de función normales, excepto por que la función en realidad se está ejecutando en otra máquina. Llamarla implica hacer una solicitud al servidor que incluye el nombre de la función y sus argumentos. La respuesta a esa solicitud contiene el valor devuelto.</p>
<p><a class="p_ident" id="p-j+WIcLxQ4Y" href="#p-j+WIcLxQ4Y" tabindex="-1" role="presentation"></a>Cuando se piensa en términos de llamadas de procedimiento remoto, HTTP es simplemente un vehículo de comunicación, y es muy probable que escribas una capa de abstracción que lo oculte por completo.</p>
<p><a class="p_ident" id="p-P0k7JSAy+k" href="#p-P0k7JSAy+k" tabindex="-1" role="presentation"></a>Otro enfoque es construir tu comunicación en torno al concepto de recursos y métodos HTTP. En lugar de un procedimiento remoto llamado <code>addUser</code>, usas una solicitud <code>PUT</code> a <code>/usuarios/larry</code>. En lugar de codificar las propiedades de ese usuario en argumentos de función, defines un formato de documento JSON (o utilizas un formato existente) que represente a un usuario. El cuerpo de la solicitud <code>PUT</code> para crear un nuevo recurso es entonces dicho documento. Se obtiene un recurso realizando una solicitud <code>GET</code> a la URL del recurso (por ejemplo, <code>/usuario/larry</code>), que de nuevo devuelve el documento que representa al recurso.</p>
<p><a class="p_ident" id="p-ou0JH35Di5" href="#p-ou0JH35Di5" tabindex="-1" role="presentation"></a>Este segundo enfoque facilita el uso de algunas de las características que proporciona HTTP, como el soporte para la caché de recursos (mantener una copia de un recurso en el cliente para un acceso rápido). Los conceptos utilizados en HTTP, que están bien diseñados, pueden proporcionar un conjunto útil de principios para diseñar la interfaz de tu servidor.</p>
<h2><a class="h_ident" id="h-h7cGRyhxkA" href="#h-h7cGRyhxkA" tabindex="-1" role="presentation"></a>Seguridad y HTTPS</h2>
<p><a class="p_ident" id="p-HnfoDF3qKu" href="#p-HnfoDF3qKu" tabindex="-1" role="presentation"></a>Los datos que viajan por Internet tienden a seguir un largo y peligroso camino. Para llegar a su destino, deben pasar por cualquier cosa, desde puntos de acceso Wi-Fi de cafeterías hasta redes controladas por varias empresas y estados. En cualquier punto a lo largo de su ruta, pueden ser inspeccionados o incluso modificados.</p>
<p><a class="p_ident" id="p-b21n8u5IbZ" href="#p-b21n8u5IbZ" tabindex="-1" role="presentation"></a>Si es importante que algo se mantenga en secreto, como la contraseña de tu cuenta de correo electrónico, o que llegue a su destino sin modificaciones, como el número de cuenta al que transfieres dinero a través del sitio web de tu banco, el HTTP simple no es suficiente.</p>
<p><a class="p_ident" id="p-q6s/2UXNiF" href="#p-q6s/2UXNiF" tabindex="-1" role="presentation"></a>El protocolo seguro de HTTP, utilizado para URLs que comienzan con <em>https://</em>, envuelve el tráfico HTTP de una manera que dificulta su lectura y manipulación. Antes de intercambiar datos, el cliente verifica que el servidor sea quien dice ser, solicitándole que demuestre que tiene un certificado criptográfico emitido por una autoridad de certificación que el navegador reconoce. Luego, todos los datos que pasan por la conexión están encriptados de una manera que debería evitar el espionaje y la manipulación.</p>
<p><a class="p_ident" id="p-SUZXSIVpEV" href="#p-SUZXSIVpEV" tabindex="-1" role="presentation"></a>Así, cuando funciona correctamente, HTTPS evita que otras personas se hagan pasar por el sitio web con el que estás intentando comunicarte <em>y</em> que espíen tu comunicación. No es perfecto, y ha habido varios incidentes en los que HTTPS falló debido a certificados falsificados o robados y software defectuoso, pero es <em>mucho</em> más seguro que el HTTP simple.</p>
<h2 id="formularios"><a class="h_ident" id="h-vR60n2b5eW" href="#h-vR60n2b5eW" tabindex="-1" role="presentation"></a>Campos de formulario</h2>
<p><a class="p_ident" id="p-urU/os5kQU" href="#p-urU/os5kQU" tabindex="-1" role="presentation"></a>Los formularios fueron diseñados originalmente para la Web pre-JavaScript para permitir que los sitios web envíen información del usuario en una solicitud HTTP. Este diseño asume que la interacción con el servidor siempre ocurre navegando a una nueva página.</p>
<p><a class="p_ident" id="p-CpuTWkD8RY" href="#p-CpuTWkD8RY" tabindex="-1" role="presentation"></a>Pero sus elementos son parte del DOM al igual que el resto de la página, y los elementos del DOM que representan los campos de formulario admiten una serie de propiedades y eventos que no están presentes en otros elementos. Esto hace posible inspeccionar y controlar dichos campos de entrada con programas JavaScript y hacer cosas como agregar nueva funcionalidad a un formulario o utilizar formularios y campos como bloques de construcción en una aplicación JavaScript.</p>
<p><a class="p_ident" id="p-F9Gy1z8vid" href="#p-F9Gy1z8vid" tabindex="-1" role="presentation"></a>Un formulario web consiste en cualquier número de campos de entrada agrupados en una etiqueta <code><form></code>. HTML permite varios estilos diferentes de campos, que van desde simples casillas de verificación de encendido/apagado hasta menús desplegables y campos para entrada de texto. Este libro no intentará discutir exhaustivamente todos los tipos de campos, pero comenzaremos con una vista general aproximada.</p>
<p><a class="p_ident" id="p-KqKV79NEDL" href="#p-KqKV79NEDL" tabindex="-1" role="presentation"></a>Muchos tipos de campos utilizan la etiqueta <code><input></code>. El atributo <code>type</code> de esta etiqueta se utiliza para seleccionar el estilo del campo. Estos son algunos tipos comúnmente utilizados de <code><input></code>:</p>
<table>
<tr><td><code>text</code></td><td>Un campo de una línea campo de texto</td>
</tr>
<tr><td><code>password</code></td><td>Igual que <code>text</code> pero oculta el texto que se escribe</td>
</tr>
<tr><td><code>checkbox</code></td><td>Un interruptor de encendido/apagado</td>
</tr>
<tr><td><code>color</code></td><td>Un color</td>
</tr>
<tr><td><code>date</code></td><td>Una fecha de calendario</td>
</tr>
<tr><td><code>radio</code></td><td>(Parte de) un campo de opción múltiple</td>
</tr>
<tr><td><code>file</code></td><td>Permite al usuario elegir un archivo de su computadora</td>
</tr>
</table>
<p><a class="p_ident" id="p-1/kyPRRaqb" href="#p-1/kyPRRaqb" tabindex="-1" role="presentation"></a>Los campos de formulario no necesariamente tienen que aparecer en una etiqueta <code><form></code>. Puedes ponerlos en cualquier parte de una página. Campos sin formulario no pueden ser enviados (solo un formulario en su totalidad puede), pero al responder a la entrada con JavaScript, a menudo no queremos enviar nuestros campos de forma normal de todos modos.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-uyXervXrEJ" href="#c-uyXervXrEJ" tabindex="-1" role="presentation"></a><<span class="tok-typeName">p</span>><<span class="tok-typeName">input</span> type=<span class="tok-string">"text"</span> value=<span class="tok-string">"abc"</span>> (texto)</<span class="tok-typeName">p</span>>
<<span class="tok-typeName">p</span>><<span class="tok-typeName">input</span> type=<span class="tok-string">"password"</span> value=<span class="tok-string">"abc"</span>> (contraseña)</<span class="tok-typeName">p</span>>
<<span class="tok-typeName">p</span>><<span class="tok-typeName">input</span> type=<span class="tok-string">"checkbox"</span> checked> (casilla de verificación)</<span class="tok-typeName">p</span>>
<<span class="tok-typeName">p</span>><<span class="tok-typeName">input</span> type=<span class="tok-string">"color"</span> value=<span class="tok-string">"naranja"</span>> (color)</<span class="tok-typeName">p</span>>
<<span class="tok-typeName">p</span>><<span class="tok-typeName">input</span> type=<span class="tok-string">"date"</span> value=<span class="tok-string">"2023-10-13"</span>> (fecha)</<span class="tok-typeName">p</span>>
<<span class="tok-typeName">p</span>><<span class="tok-typeName">input</span> type=<span class="tok-string">"radio"</span> value=<span class="tok-string">"A"</span> name=<span class="tok-string">"elección"</span>>
<<span class="tok-typeName">input</span> type=<span class="tok-string">"radio"</span> value=<span class="tok-string">"B"</span> name=<span class="tok-string">"elección"</span> checked>
<<span class="tok-typeName">input</span> type=<span class="tok-string">"radio"</span> value=<span class="tok-string">"C"</span> name=<span class="tok-string">"elección"</span>> (radio)</<span class="tok-typeName">p</span>>
<<span class="tok-typeName">p</span>><<span class="tok-typeName">input</span> type=<span class="tok-string">"file"</span>> (archivo)</<span class="tok-typeName">p</span>></pre>
<p><a class="p_ident" id="p-JHY57wyhqd" href="#p-JHY57wyhqd" tabindex="-1" role="presentation"></a>La interfaz de JavaScript para estos elementos difiere según el tipo de elemento.</p>
<p><a class="p_ident" id="p-mIbb5kma+g" href="#p-mIbb5kma+g" tabindex="-1" role="presentation"></a>Los campos de texto de varias líneas tienen su propia etiqueta, <code><textarea></code>, principalmente porque sería incómodo utilizar un atributo para especificar un valor de inicio de varias líneas. La etiqueta <code><textarea></code> requiere una etiqueta de cierre <code></<wbr>textarea></code> y utiliza el texto entre esas dos etiquetas, en lugar del atributo <code>valor</code>, como texto de inicio.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-uJfdUZxNUs" href="#c-uJfdUZxNUs" tabindex="-1" role="presentation"></a><<span class="tok-typeName">textarea</span>>
uno
dos
tres
</<span class="tok-typeName">textarea</span>></pre>
<p><a class="p_ident" id="p-Tj0x1Bc52k" href="#p-Tj0x1Bc52k" tabindex="-1" role="presentation"></a>Finalmente, la etiqueta <code><select></code> se usa para crear un campo que permite al usuario seleccionar de varias opciones predefinidas.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-fHWdamWCUc" href="#c-fHWdamWCUc" tabindex="-1" role="presentation"></a><<span class="tok-typeName">select</span>>
<<span class="tok-typeName">option</span>>Tortitas</<span class="tok-typeName">option</span>>
<<span class="tok-typeName">option</span>>Pudín</<span class="tok-typeName">option</span>>
<<span class="tok-typeName">option</span>>Helado</<span class="tok-typeName">option</span>>
</<span class="tok-typeName">select</span>></pre>
<p><a class="p_ident" id="p-EosklUZ5/x" href="#p-EosklUZ5/x" tabindex="-1" role="presentation"></a>Cada vez que cambia el valor de un campo de formulario, se desencadenará un evento “<code>change</code>”.</p>
<h2><a class="h_ident" id="h-d25sZu5XS1" href="#h-d25sZu5XS1" tabindex="-1" role="presentation"></a>Enfoque</h2>
<p><a class="p_ident" id="p-WsUvo7OvQM" href="#p-WsUvo7OvQM" tabindex="-1" role="presentation"></a>A diferencia de la mayoría de elementos en documentos HTML, los campos de formulario pueden obtener <em>enfoque de teclado</em>. Cuando se hace clic en ellos, se utiliza la tecla <span class="keyname">tab</span> para navegar hasta ellos, o se activan de alguna otra manera, se convierten en el elemento activo actual y en el receptor de la entrada de teclado.</p>
<p><a class="p_ident" id="p-f8xUY3zbxR" href="#p-f8xUY3zbxR" tabindex="-1" role="presentation"></a>Por lo tanto, puedes escribir en un campo de texto solo cuando está enfocado. Otros campos responden diferentemente a los eventos de teclado. Por ejemplo, un menú <code><select></code> intenta moverse a la opción que contiene el texto que el usuario escribió y responde a las teclas de flecha moviendo su selección hacia arriba y hacia abajo.</p>
<p><a class="p_ident" id="p-RMaSGEsWKt" href="#p-RMaSGEsWKt" tabindex="-1" role="presentation"></a>Podemos controlar el focus desde JavaScript con los métodos <code>focus</code> y <code>blur</code>. El primero mueve el enfoque al elemento del DOM en el que se llama, y el segundo elimina el enfoque. El valor en <code>document.<wbr>activeElement</code> corresponde al elemento actualmente enfocado.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-QrBgwnycJO" href="#c-QrBgwnycJO" tabindex="-1" role="presentation"></a><<span class="tok-typeName">input</span> type=<span class="tok-string">"text"</span>>
<<span class="tok-typeName">script</span>>
document.querySelector(<span class="tok-string">"input"</span>).focus();
console.log(document.activeElement.tagName);
<span class="tok-comment">// → INPUT</span>
document.querySelector(<span class="tok-string">"input"</span>).blur();
console.log(document.activeElement.tagName);
<span class="tok-comment">// → BODY</span>
</<span class="tok-typeName">script</span>></pre>
<p><a class="p_ident" id="p-mO3ky8ASWh" href="#p-mO3ky8ASWh" tabindex="-1" role="presentation"></a>Para algunas páginas, se espera que el usuario desee interactuar inmediatamente con un campo de formulario. JavaScript se puede utilizar para enfocar este campo cuando se carga el documento, pero HTML también proporciona el atributo <code>autofocus</code>, que produce el mismo efecto al mismo tiempo que le indica al navegador lo que estamos tratando de lograr. Esto le da al navegador la opción de deshabilitar el comportamiento cuando no es apropiado, como cuando el usuario ha puesto el enfoque en otra parte.</p>
<p><a class="p_ident" id="p-8IYN50zKq7" href="#p-8IYN50zKq7" tabindex="-1" role="presentation"></a>Los navegadores permiten al usuario mover el enfoque a través del documento presionando la tecla <span class="keyname">tab</span> para pasar al siguiente elemento enfocable, y <span class="keyname">shift-tab</span> para retroceder al elemento anterior. Por defecto, los elementos se visitan en el orden en que aparecen en el documento. Es posible usar el atributo <code>tabindex</code> para cambiar este orden. El siguiente ejemplo de documento permitirá que el enfoque salte del campo de texto al botón OK, en lugar de pasar primero por el enlace de ayuda:</p>
<pre tabindex="0" class="snippet" data-language="html" data-focus="true"><a class="c_ident" id="c-wUg0/FcvZ8" href="#c-wUg0/FcvZ8" tabindex="-1" role="presentation"></a><<span class="tok-typeName">input</span> type=<span class="tok-string">"text"</span> tabindex=<span class="tok-string">1</span>> <<span class="tok-typeName">a</span> href=<span class="tok-string">"."</span>>(ayuda)</<span class="tok-typeName">a</span>>
<<span class="tok-typeName">button</span> onclick=<span class="tok-string">"</span>console.log(<span class="tok-string">'ok'</span>)<span class="tok-string">"</span> tabindex=<span class="tok-string">2</span>>OK</<span class="tok-typeName">button</span>></pre>
<p><a class="p_ident" id="p-+ecbiJXCDm" href="#p-+ecbiJXCDm" tabindex="-1" role="presentation"></a>Por defecto, la mayoría de los tipos de elementos HTML no pueden ser enfocados. Pero se puede agregar un atributo <code>tabindex</code> a cualquier elemento para hacerlo enfocable. Un <code>tabindex</code> de 0 hace que un elemento sea enfocable sin afectar el orden de enfoque.</p>
<h2><a class="h_ident" id="h-JmKzGi+H2J" href="#h-JmKzGi+H2J" tabindex="-1" role="presentation"></a>Campos deshabilitados</h2>
<p><a class="p_ident" id="p-XGfC9aIuJw" href="#p-XGfC9aIuJw" tabindex="-1" role="presentation"></a>Todos los campos de formulario pueden ser <em>deshabilitados</em> a través de su atributo <code>disabled</code>. Es un atributo que se puede especificar sin valor; el simple hecho de que esté presente deshabilita el elemento.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-rU8dCQPEEy" href="#c-rU8dCQPEEy" tabindex="-1" role="presentation"></a><<span class="tok-typeName">button</span>>Estoy bien</<span class="tok-typeName">button</span>>
<<span class="tok-typeName">button</span> disabled>Estoy fuera</<span class="tok-typeName">button</span>></pre>
<p><a class="p_ident" id="p-apA0jOMPIr" href="#p-apA0jOMPIr" tabindex="-1" role="presentation"></a>Los campos deshabilitados no pueden se pueden enfocar ni modificar, y los navegadores los muestran de color gris y atenuados.</p>
<p><a class="p_ident" id="p-VHTwd9K7oy" href="#p-VHTwd9K7oy" tabindex="-1" role="presentation"></a>Cuando un programa está en proceso de manejar una acción provocada por algún botón u otro control que podría requerir comunicación con el servidor y por lo tanto llevar un tiempo, puede ser una buena idea deshabilitar el control hasta que la acción haya terminado. De esta forma, cuando el usuario se impaciente y haga clic nuevamente, no repetirán accidentalmente su acción.</p>
<h2><a class="h_ident" id="h-I8C+LttXH2" href="#h-I8C+LttXH2" tabindex="-1" role="presentation"></a>El formulario en su totalidad</h2>
<p><a class="p_ident" id="p-wQwycJHlbV" href="#p-wQwycJHlbV" tabindex="-1" role="presentation"></a>Cuando un field está contenido en un elemento <code><form></code>, su elemento del DOM tendrá una propiedad <code>form</code> que enlaza de vuelta al elemento del DOM del formulario. El elemento <code><form></code>, a su vez, tiene una propiedad llamada <code>elements</code> que contiene una colección parecida a un array de los campos dentro de él.</p>
<p><a class="p_ident" id="p-4HjtBqUpKB" href="#p-4HjtBqUpKB" tabindex="-1" role="presentation"></a>El atributo <code>name</code> de un campo de formulario determina la forma en que se identificará su valor cuando se entrega el formulario. También se puede utilizar como nombre de propiedad al acceder a la propiedad <code>elements</code> del formulario, la cual actúa como un objeto parecido a un array (es decir, accesible por número) y a su vez como un mapa (accesible por nombre).</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-uRzDK9DiiW" href="#c-uRzDK9DiiW" tabindex="-1" role="presentation"></a><<span class="tok-typeName">form</span> action=<span class="tok-string">"example/submit.html"</span>>
Nombre: <<span class="tok-typeName">input</span> type=<span class="tok-string">"text"</span> name=<span class="tok-string">"nombre"</span>><<span class="tok-typeName">br</span>>
Contraseña: <<span class="tok-typeName">input</span> type=<span class="tok-string">"password"</span> name=<span class="tok-string">"contraseña"</span>><<span class="tok-typeName">br</span>>
<<span class="tok-typeName">button</span> type=<span class="tok-string">"submit"</span>>Ingresar</<span class="tok-typeName">button</span>>
</<span class="tok-typeName">form</span>>
<<span class="tok-typeName">script</span>>
<span class="tok-keyword">let</span> <span class="tok-definition">formulario</span> = document.querySelector(<span class="tok-string">"form"</span>);
console.log(formulario.elements[<span class="tok-number">1</span>].type);
<span class="tok-comment">// → password</span>
console.log(formulario.elements.contraseña.type);
<span class="tok-comment">// → password</span>
console.log(formulario.elements.nombre.form == formulario);
<span class="tok-comment">// → true</span>
</<span class="tok-typeName">script</span>></pre>
<p><a class="p_ident" id="p-UL0wLhERLt" href="#p-UL0wLhERLt" tabindex="-1" role="presentation"></a>Un botón con un atributo <code>type</code> de <code>submit</code> hará que, al presionarlo, se entregue el formulario. Presionar <span class="keyname">enter</span> cuando un campo de formulario está enfocado tendrá el mismo efecto.</p>
<p><a class="p_ident" id="p-CE426zYDvk" href="#p-CE426zYDvk" tabindex="-1" role="presentation"></a>Enviar un formulario normalmente significa que el navegador se dirige a la página indicada por el atributo <code>action</code> del formulario, utilizando ya sea una solicitud <code>GET</code> o <code>POST</code>. Pero antes de que eso ocurra, se dispara un evento <code>"submit"</code>. Puedes manejar este evento con JavaScript y evitar este comportamiento por defecto llamando a <code>preventDefault</code> en el objeto de evento.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-cmki/mPZEe" href="#c-cmki/mPZEe" tabindex="-1" role="presentation"></a><<span class="tok-typeName">form</span>>
Valor: <<span class="tok-typeName">input</span> type=<span class="tok-string">"text"</span> name=<span class="tok-string">"valor"</span>>
<<span class="tok-typeName">button</span> type=<span class="tok-string">"submit"</span>>Guardar</<span class="tok-typeName">button</span>>
</<span class="tok-typeName">form</span>>
<<span class="tok-typeName">script</span>>
<span class="tok-keyword">let</span> <span class="tok-definition">formulario</span> = document.querySelector(<span class="tok-string">"form"</span>);
formulario.addEventListener(<span class="tok-string">"submit"</span>, <span class="tok-definition">evento</span> => {
console.log(<span class="tok-string">"Guardando valor"</span>, formulario.elements.valor.value);
evento.preventDefault();
});
</<span class="tok-typeName">script</span>></pre>
<p><a class="p_ident" id="p-STSqtYSJYL" href="#p-STSqtYSJYL" tabindex="-1" role="presentation"></a>Interceptar los eventos <code>"submit"</code> en JavaScript tiene varios usos. Podemos escribir código para verificar que los valores ingresados por el usuario tengan sentido y mostrar inmediatamente un mensaje de error en lugar de enviar el formulario. O podemos deshabilitar completamente la forma usual de enviar el formulario, como en el ejemplo, y hacer que nuestro programa maneje la entrada, posiblemente utilizando <code>fetch</code> para enviarla a un servidor sin recargar la página.</p>
<h2><a class="h_ident" id="h-aNECCZZECC" href="#h-aNECCZZECC" tabindex="-1" role="presentation"></a>Campos de texto</h2>
<p><a class="p_ident" id="p-2/lDSlOr14" href="#p-2/lDSlOr14" tabindex="-1" role="presentation"></a>Los campos creados por etiquetas <code><textarea></code>, o etiquetas <code><input></code> con un tipo de <code>text</code> o <code>password</code>, comparten una interfaz común. Sus elementos del DOM tienen una propiedad <code>value</code> que contiene su contenido actual como un valor de cadena. Establecer esta propiedad a otra cadena cambia el contenido del campo.</p>
<p><a class="p_ident" id="p-kPF/BAetTh" href="#p-kPF/BAetTh" tabindex="-1" role="presentation"></a>Las propiedades <code>selectionStart</code> y <code>selectionEnd</code> de los campos de texto nos brindan información sobre la posición del cursor y la selección en el texto. Cuando no se ha seleccionado nada, estas dos propiedades contienen el mismo número, indicando la posición del cursor. Por ejemplo, 0 indica el inicio del texto, y 10 indica que el cursor está después del 10<sup>º</sup> carácter. Cuando se selecciona parte del campo, las dos propiedades serán diferentes, dándonos el inicio y el final del texto seleccionado. Al igual que <code>value</code>, estas propiedades también se pueden escribir.</p>
<p><a class="p_ident" id="p-OWVzp9QFZ5" href="#p-OWVzp9QFZ5" tabindex="-1" role="presentation"></a>Imagina que estás escribiendo un artículo sobre Khasekhemwy pero tienes problemas para deletrear su nombre. El siguiente código vincula una etiqueta <code><textarea></code> con un controlador de eventos que, al presionar F2, inserta la cadena “Khasekhemwy” por ti.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-fzkRKJAhNa" href="#c-fzkRKJAhNa" tabindex="-1" role="presentation"></a><<span class="tok-typeName">textarea</span>></<span class="tok-typeName">textarea</span>>
<<span class="tok-typeName">script</span>>
<span class="tok-keyword">let</span> <span class="tok-definition">áreaDeTexto</span> = document.querySelector(<span class="tok-string">"textarea"</span>);
áreaDeTexto.addEventListener(<span class="tok-string">"keydown"</span>, <span class="tok-definition">evento</span> => {
<span class="tok-keyword">if</span> (evento.key == <span class="tok-string">"F2"</span>) {
replaceSelection(áreaDeTexto, <span class="tok-string">"Khasekhemwy"</span>);
evento.preventDefault();
}
});
<span class="tok-keyword">function</span> <span class="tok-definition">replaceSelection</span>(<span class="tok-definition">campo</span>, <span class="tok-definition">palabra</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">desde</span> = campo.selectionStart, <span class="tok-definition">hasta</span> = campo.selectionEnd;
campo.value = campo.value.slice(<span class="tok-number">0</span>, desde) + palabra +
campo.value.slice(hasta);
<span class="tok-comment">// Coloca el cursor después de la palabra</span>
campo.selectionStart = desde + palabra.length;
campo.selectionEnd = desde + palabra.length;
}
</<span class="tok-typeName">script</span>></pre>
<p><a class="p_ident" id="p-HiP/bodQoF" href="#p-HiP/bodQoF" tabindex="-1" role="presentation"></a>La función <code>replaceSelection</code> reemplaza la parte actualmente seleccionada del contenido de un campo de texto con la palabra proporcionada y luego mueve el cursor después de esa palabra para que el usuario pueda continuar escribiendo.</p>
<p><a class="p_ident" id="p-iymd5B4YOv" href="#p-iymd5B4YOv" tabindex="-1" role="presentation"></a>El evento <code>"change"</code> para un campo de texto no se activa cada vez que se escribe algo. En cambio, se activa cuando el campo pierde el enfoque después de que su contenido haya cambiado. Para responder de inmediato a los cambios en un campo de texto, se debe registrar un controlador para el evento <code>"input"</code>, que se activa cada vez que el usuario escribe un carácter, elimina texto o manipula de otra manera el contenido del campo.</p>
<p><a class="p_ident" id="p-vETjJ3j9ss" href="#p-vETjJ3j9ss" tabindex="-1" role="presentation"></a>El siguiente ejemplo muestra un campo de texto y un contador que muestra la longitud actual del texto en el campo:</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-6gTsqSqxau" href="#c-6gTsqSqxau" tabindex="-1" role="presentation"></a><<span class="tok-typeName">input</span> type=<span class="tok-string">"text"</span>> longitud: <<span class="tok-typeName">span</span> id=<span class="tok-string">"length"</span>>0</<span class="tok-typeName">span</span>>
<<span class="tok-typeName">script</span>>
<span class="tok-keyword">let</span> <span class="tok-definition">texto</span> = document.querySelector(<span class="tok-string">"input"</span>);
<span class="tok-keyword">let</span> <span class="tok-definition">output</span> = document.querySelector(<span class="tok-string">"#length"</span>);
texto.addEventListener(<span class="tok-string">"input"</span>, () => {
output.textContent = texto.value.length;
});
</<span class="tok-typeName">script</span>></pre>
<h2><a class="h_ident" id="h-glFUcSvM2s" href="#h-glFUcSvM2s" tabindex="-1" role="presentation"></a>Casillas de verificación y botones de radio</h2>
<p><a class="p_ident" id="p-Sm0wMxS3pF" href="#p-Sm0wMxS3pF" tabindex="-1" role="presentation"></a>Un campo de casilla de verificación es un interruptor binario. Su valor se puede extraer o cambiar a través de su propiedad <code>checked</code>, que contiene un valor Booleano.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-4QykavB/gS" href="#c-4QykavB/gS" tabindex="-1" role="presentation"></a><<span class="tok-typeName">label</span>>
<<span class="tok-typeName">input</span> type=<span class="tok-string">"checkbox"</span> id=<span class="tok-string">"purple"</span>> Hacer esta página morada
</<span class="tok-typeName">label</span>>
<<span class="tok-typeName">script</span>>
<span class="tok-keyword">let</span> <span class="tok-definition">casillaVerificacion</span> = document.querySelector(<span class="tok-string">"#purple"</span>);
casillaVerificacion.addEventListener(<span class="tok-string">"change"</span>, () => {
document.body.style.background =
casillaVerificacion.checked ? <span class="tok-string">"mediumpurple"</span> : <span class="tok-string">""</span>;
});
</<span class="tok-typeName">script</span>></pre>
<p><a class="p_ident" id="p-HIgnvYrmlE" href="#p-HIgnvYrmlE" tabindex="-1" role="presentation"></a>La etiqueta <code><label></code> asocia un fragmento de documento con un campo de entrada. Hacer clic en cualquier parte de la etiqueta activará el campo, lo enfocará e invertirá su valor cuando sea una casilla de verificación o un botón de radio.</p>
<p><a class="p_ident" id="p-OPMRZaFTvv" href="#p-OPMRZaFTvv" tabindex="-1" role="presentation"></a>Un botón de radio es similar a una casilla de verificación, pero está vinculado implícitamente a otros botones de radio con el mismo atributo <code>name</code> para que solo uno de ellos pueda estar activo en cualquier momento.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-lBGSCq/QeW" href="#c-lBGSCq/QeW" tabindex="-1" role="presentation"></a>Color:
<<span class="tok-typeName">label</span>>
<<span class="tok-typeName">input</span> type=<span class="tok-string">"radio"</span> name=<span class="tok-string">"color"</span> value=<span class="tok-string">"orange"</span>> Naranja
</<span class="tok-typeName">label</span>>
<<span class="tok-typeName">label</span>>
<<span class="tok-typeName">input</span> type=<span class="tok-string">"radio"</span> name=<span class="tok-string">"color"</span> value=<span class="tok-string">"lightgreen"</span>> Verde claro
</<span class="tok-typeName">label</span>>
<<span class="tok-typeName">label</span>>
<<span class="tok-typeName">input</span> type=<span class="tok-string">"radio"</span> name=<span class="tok-string">"color"</span> value=<span class="tok-string">"lightblue"</span>> Azul claro
</<span class="tok-typeName">label</span>>
<<span class="tok-typeName">script</span>>
<span class="tok-keyword">let</span> <span class="tok-definition">botones</span> = document.querySelectorAll(<span class="tok-string">"[name=color]"</span>);
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">botón</span> <span class="tok-keyword">of</span> Array.from(botones)) {
botón.addEventListener(<span class="tok-string">"change"</span>, () => {
document.body.style.background = botón.value;
});
}
</<span class="tok-typeName">script</span>></pre>
<p><a class="p_ident" id="p-Rj/Tn9ptX3" href="#p-Rj/Tn9ptX3" tabindex="-1" role="presentation"></a>Los corchetes cuadrados en la consulta CSS proporcionada a <code>querySelectorAll</code> se utilizan para hacer coincidir atributos. Selecciona elementos cuyo atributo <code>name</code> es <code>"color"</code>.</p>
<h2><a class="h_ident" id="h-piLGGn2UVZ" href="#h-piLGGn2UVZ" tabindex="-1" role="presentation"></a>Campos de selección</h2>
<p><a class="p_ident" id="p-LwFLblAPrh" href="#p-LwFLblAPrh" tabindex="-1" role="presentation"></a>Los campos de selección son conceptualmente similares a los botones de radio, ya que también permiten al usuario elegir entre un conjunto de opciones. Sin embargo, mientras que un botón de radio pone el diseño de las opciones bajo nuestro control, la apariencia de una etiqueta <code><select></code> está determinada por el navegador.</p>
<p><a class="p_ident" id="p-ovWpX69a8P" href="#p-ovWpX69a8P" tabindex="-1" role="presentation"></a>Los campos de selección también tienen una variante que se asemeja más a una lista de casillas de verificación que a botones de radio. Cuando se le otorga el atributo <code>multiple</code>, una etiqueta <code><select></code> permitirá al usuario seleccionar cualquier número de opciones, en lugar de una sola opción. Mientras que un campo de selección normal se muestra como un control de <em>lista desplegable</em>, que muestra las opciones inactivas solo cuando lo abres, un campo con <code>multiple</code> habilitado muestra múltiples opciones al mismo tiempo, permitiendo al usuario habilitarlas o deshabilitarlas individualmente.</p>
<p><a class="p_ident" id="p-lMYxrT0Q64" href="#p-lMYxrT0Q64" tabindex="-1" role="presentation"></a>Cada etiqueta <code><option></code> tiene un valor. Este valor se puede definir con un atributo <code>value</code>. Cuando este no se proporciona, el texto dentro de la opción se considerará como su valor. La propiedad <code>value</code> de un elemento <code><select></code> refleja la opción actualmente seleccionada. Sin embargo, para un campo <code>multiple</code>, esta propiedad no significa mucho, ya que dará el valor de solo <em>una</em> de las opciones actualmente seleccionadas.</p>
<p><a class="p_ident" id="p-0GXyUGSyIo" href="#p-0GXyUGSyIo" tabindex="-1" role="presentation"></a>Las etiquetas <code><option></code> para un campo <code><select></code> pueden ser accedidas como un objeto similar a un array a través de la propiedad <code>options</code> del campo. Cada opción tiene una propiedad llamada <code>selected</code>, que indica si esa opción está actualmente seleccionada. La propiedad también se puede escribir para seleccionar o deseleccionar una opción.</p>
<p><a class="p_ident" id="p-gohmWQPKBU" href="#p-gohmWQPKBU" tabindex="-1" role="presentation"></a>Este ejemplo extrae los valores seleccionados de un campo de selección <code>multiple</code> y los utiliza para componer un número binario a partir de bits individuales. Mantén pulsado <span class="keyname">control</span> (o <span class="keyname">command</span> en un Mac) para seleccionar múltiples opciones.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-IUZrTqBBtg" href="#c-IUZrTqBBtg" tabindex="-1" role="presentation"></a><<span class="tok-typeName">select</span> multiple>
<<span class="tok-typeName">option</span> value=<span class="tok-string">"1"</span>>0001</<span class="tok-typeName">option</span>>
<<span class="tok-typeName">option</span> value=<span class="tok-string">"2"</span>>0010</<span class="tok-typeName">option</span>>
<<span class="tok-typeName">option</span> value=<span class="tok-string">"4"</span>>0100</<span class="tok-typeName">option</span>>
<<span class="tok-typeName">option</span> value=<span class="tok-string">"8"</span>>1000</<span class="tok-typeName">option</span>>
</<span class="tok-typeName">select</span>> = <<span class="tok-typeName">span</span> id=<span class="tok-string">"salida"</span>>0</<span class="tok-typeName">span</span>>
<<span class="tok-typeName">script</span>>
<span class="tok-keyword">let</span> <span class="tok-definition">select</span> = document.querySelector(<span class="tok-string">"select"</span>);
<span class="tok-keyword">let</span> <span class="tok-definition">salida</span> = document.querySelector(<span class="tok-string">"#salida"</span>);
select.addEventListener(<span class="tok-string">"change"</span>, () => {
<span class="tok-keyword">let</span> <span class="tok-definition">número</span> = <span class="tok-number">0</span>;
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">opción</span> <span class="tok-keyword">of</span> Array.from(select.options)) {
<span class="tok-keyword">if</span> (opción.selected) {
número += Number(opción.value);
}
}
salida.textContent = número;
});
</<span class="tok-typeName">script</span>></pre>
<h2><a class="h_ident" id="h-L2DhQ3Cqto" href="#h-L2DhQ3Cqto" tabindex="-1" role="presentation"></a>Campos de archivo</h2>
<p><a class="p_ident" id="p-L7+DtC3oUW" href="#p-L7+DtC3oUW" tabindex="-1" role="presentation"></a>Los campos de archivo fueron diseñados originalmente como una forma de subir archivos desde la máquina del usuario a través de un formulario. En los navegadores modernos, también proporcionan una forma de leer dichos archivos desde programas JavaScript. El campo actúa como una especie de guardián. El script no puede simplemente comenzar a leer archivos privados desde la computadora del usuario, pero si el usuario selecciona un archivo en dicho campo, el navegador interpreta esa acción como que el script puede leer el archivo.</p>
<p><a class="p_ident" id="p-x5QCNUXKJj" href="#p-x5QCNUXKJj" tabindex="-1" role="presentation"></a>Un campo de archivo suele parecerse a un botón etiquetado con algo como “elegir archivo” o “explorar”, con información sobre el archivo elegido al lado.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-awLytAtIDt" href="#c-awLytAtIDt" tabindex="-1" role="presentation"></a><<span class="tok-typeName">input</span> type=<span class="tok-string">"file"</span>>
<<span class="tok-typeName">script</span>>
<span class="tok-keyword">let</span> <span class="tok-definition">input</span> = document.querySelector(<span class="tok-string">"input"</span>);
input.addEventListener(<span class="tok-string">"change"</span>, () => {
<span class="tok-keyword">if</span> (input.files.length > <span class="tok-number">0</span>) {
<span class="tok-keyword">let</span> <span class="tok-definition">archivo</span> = input.files[<span class="tok-number">0</span>];
console.log(<span class="tok-string">"Has elegido"</span>, archivo.name);
<span class="tok-keyword">if</span> (archivo.type) console.log(<span class="tok-string">"Tiene tipo"</span>, archivo.type);
}
});
</<span class="tok-typeName">script</span>></pre>
<p><a class="p_ident" id="p-u0pRmFLpFo" href="#p-u0pRmFLpFo" tabindex="-1" role="presentation"></a>La propiedad <code>files</code> de un elemento campo de archivo es un objeto similar a un array (una vez más, no es un array de verdad) que contiene los archivos elegidos en el campo. Inicialmente está vacío. La razón por la que no hay simplemente una propiedad <code>file</code> es que los campos de archivo también admiten un atributo <code>multiple</code>, lo que permite seleccionar varios archivos al mismo tiempo.</p>
<p><a class="p_ident" id="p-seFEA3+tCC" href="#p-seFEA3+tCC" tabindex="-1" role="presentation"></a>Los objetos en <code>files</code> tienen propiedades como <code>name</code> (el nombre de archivo), <code>size</code> (el tamaño del archivo en bytes, que son trozos de 8 bits) y <code>type</code> (el tipo de medio del archivo, como <code>text/plain</code> o <code>image/jpeg</code>).</p>
<p id="filereader"><a class="p_ident" id="p-eqjNs8AmUZ" href="#p-eqjNs8AmUZ" tabindex="-1" role="presentation"></a>Lo que no tiene es una propiedad que contenga el contenido del archivo. Acceder a eso es un poco más complicado. Como leer un archivo desde el disco puede llevar tiempo, la interfaz es asíncrona para evitar que se congele la ventana.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-L5So9ayRF5" href="#c-L5So9ayRF5" tabindex="-1" role="presentation"></a><<span class="tok-typeName">input</span> type=<span class="tok-string">"file"</span> multiple>
<<span class="tok-typeName">script</span>>
<span class="tok-keyword">let</span> <span class="tok-definition">input</span> = document.querySelector(<span class="tok-string">"input"</span>);
input.addEventListener(<span class="tok-string">"change"</span>, () => {
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">archivo</span> <span class="tok-keyword">of</span> Array.from(input.files)) {
<span class="tok-keyword">let</span> <span class="tok-definition">lector</span> = <span class="tok-keyword">new</span> FileReader();
lector.addEventListener(<span class="tok-string">"load"</span>, () => {
console.log(<span class="tok-string">"El archivo"</span>, archivo.name, <span class="tok-string">"comienza con"</span>,
lector.result.slice(<span class="tok-number">0</span>, <span class="tok-number">20</span>));
});
lector.readAsText(archivo);
}
});
</<span class="tok-typeName">script</span>></pre>
<p><a class="p_ident" id="p-IAAHaZydCD" href="#p-IAAHaZydCD" tabindex="-1" role="presentation"></a>La lectura de un archivo se realiza creando un objeto <code>FileReader</code>, registrando un controlador de eventos <code>"load"</code> para él y llamando a su método <code>readAsText</code>, dándole el archivo que queremos leer. Una vez que la carga finaliza, la propiedad <code>result</code> del lector contiene el contenido del archivo.</p>
<p><a class="p_ident" id="p-3R1w51K2r3" href="#p-3R1w51K2r3" tabindex="-1" role="presentation"></a>Los <code>FileReader</code>s también disparan un evento <code>"error"</code> cuando la lectura del archivo falla por cualquier motivo. El objeto de error en sí terminará en la propiedad <code>error</code> del lector. Esta interfaz fue diseñada antes de que las promesas se convirtieran en parte del lenguaje. Podrías envolverlo en una promesa de la siguiente manera:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-Kr3V9NFjD7" href="#c-Kr3V9NFjD7" tabindex="-1" role="presentation"></a><span class="tok-keyword">function</span> <span class="tok-definition">readFileText</span>(<span class="tok-definition">file</span>) {
<span class="tok-keyword">return</span> <span class="tok-keyword">new</span> Promise((<span class="tok-definition">resolve</span>, <span class="tok-definition">reject</span>) => {
<span class="tok-keyword">let</span> <span class="tok-definition">reader</span> = <span class="tok-keyword">new</span> FileReader();
reader.addEventListener(
<span class="tok-string">"load"</span>, () => resolve(reader.result));
reader.addEventListener(
<span class="tok-string">"error"</span>, () => reject(reader.error));
reader.readAsText(file);
});
}</pre>
<h2><a class="h_ident" id="h-pubr/frF3q" href="#h-pubr/frF3q" tabindex="-1" role="presentation"></a>Almacenando datos del lado del cliente</h2>
<p><a class="p_ident" id="p-nQ5HeD4HcD" href="#p-nQ5HeD4HcD" tabindex="-1" role="presentation"></a>Páginas simples de HTML con un poco de JavaScript pueden ser un gran formato para “mini aplicaciones” —pequeños programas auxiliares que automatizan tareas básicas. Conectando unos cuantos campos de formulario con controladores de eventos, puedes hacer desde un conversor entre centímetros y pulgadas hasta calcular contraseñas a partir de una contraseña maestra y un nombre de sitio web.</p>
<p><a class="p_ident" id="p-thk+vTbuyG" href="#p-thk+vTbuyG" tabindex="-1" role="presentation"></a>Cuando una aplicación así necesita recordar algo entre sesiones, no puedes usar las asociaciones (o variables) de JavaScript, ya que estas se descartan cada vez que se cierra la página. Podrías configurar un servidor, conectarlo a Internet y hacer que tu aplicación almacene algo allí. Veremos cómo hacerlo en el <a href="20_node.html">Capítulo 20</a>. Pero eso implica mucho trabajo extra y complejidad. A veces es suficiente con mantener los datos en el navegador.</p>
<p><a class="p_ident" id="p-nh16i7O6QQ" href="#p-nh16i7O6QQ" tabindex="-1" role="presentation"></a>El objeto <code>localStorage</code> se puede utilizar para almacenar datos de una manera que sobreviva a las recargas de página. Este objeto te permite guardar valores de cadena bajo nombres.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-w3VD9NkOfn" href="#c-w3VD9NkOfn" tabindex="-1" role="presentation"></a>localStorage.setItem(<span class="tok-string">"nombre de usuario"</span>, <span class="tok-string">"marijn"</span>);
console.log(localStorage.getItem(<span class="tok-string">"nombre de usuario"</span>));
<span class="tok-comment">// → marijn</span>
localStorage.removeItem(<span class="tok-string">"nombre de usuario"</span>);</pre>
<p><a class="p_ident" id="p-RZBecOjMtG" href="#p-RZBecOjMtG" tabindex="-1" role="presentation"></a>Un valor en <code>localStorage</code> permanece hasta que se sobrescribe, se elimina con <code>removeItem</code> o el usuario elimina sus datos locales.</p>
<p><a class="p_ident" id="p-tYhOJXItN1" href="#p-tYhOJXItN1" tabindex="-1" role="presentation"></a>Los sitios de diferentes dominios obtienen compartimentos de almacenamiento diferentes. Eso significa que los datos almacenados en <code>localStorage</code> por un sitio web dado, en principio, solo pueden ser leídos (y sobrescritos) por scripts en ese mismo sitio.</p>
<p><a class="p_ident" id="p-EvGZhGCgpj" href="#p-EvGZhGCgpj" tabindex="-1" role="presentation"></a>Los navegadores aplican un límite en el tamaño de los datos que un sitio puede almacenar en <code>localStorage</code>. Esta restricción, junto con el hecho de que llenar los discos duros de la gente con basura no es realmente rentable, evita que la función ocupe demasiado espacio.</p>
<p><a class="p_ident" id="p-kyXkj0Kaxd" href="#p-kyXkj0Kaxd" tabindex="-1" role="presentation"></a>El siguiente código implementa una aplicación rudimentaria de toma de notas. Mantiene un conjunto de notas con nombres y permite al usuario editar notas y crear nuevas.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-Pv9vacB4FL" href="#c-Pv9vacB4FL" tabindex="-1" role="presentation"></a>Notas: <<span class="tok-typeName">select</span>></<span class="tok-typeName">select</span>> <<span class="tok-typeName">button</span>>Añadir</<span class="tok-typeName">button</span>><<span class="tok-typeName">br</span>>
<<span class="tok-typeName">textarea</span> style=<span class="tok-string">"</span>width: <span class="tok-number">100</span><span class="tok-keyword">%</span><span class="tok-string">"</span>></<span class="tok-typeName">textarea</span>>
<<span class="tok-typeName">script</span>>
<span class="tok-keyword">let</span> <span class="tok-definition">lista</span> = document.querySelector(<span class="tok-string">"select"</span>);
<span class="tok-keyword">let</span> <span class="tok-definition">nota</span> = document.querySelector(<span class="tok-string">"textarea"</span>);
<span class="tok-keyword">let</span> <span class="tok-definition">estado</span>;
<span class="tok-keyword">function</span> <span class="tok-definition">setState</span>(<span class="tok-definition">nuevoEstado</span>) {
lista.textContent = <span class="tok-string">""</span>;
<span class="tok-keyword">for</span> (<span class="tok-keyword">let</span> <span class="tok-definition">nombre</span> <span class="tok-keyword">of</span> Object.keys(nuevoEstado.notes)) {
<span class="tok-keyword">let</span> <span class="tok-definition">opción</span> = document.createElement(<span class="tok-string">"option"</span>);
opción.textContent = nombre;
<span class="tok-keyword">if</span> (nuevoEstado.selected == nombre) opción.selected = true;
lista.appendChild(opción);
}
nota.value = nuevoEstado.notes[nuevoEstado.selected];
localStorage.setItem(<span class="tok-string">"Notas"</span>, JSON.stringify(nuevoEstado));
estado = nuevoEstado;
}
setState(JSON.parse(localStorage.getItem(<span class="tok-string">"Notas"</span>)) ?? {
<span class="tok-definition">notes</span>: {<span class="tok-string">"lista de compras"</span>: <span class="tok-string">"Zanahorias</span><span class="tok-string2">\n</span><span class="tok-string">Pasas"</span>},
<span class="tok-definition">selected</span>: <span class="tok-string">"lista de compras"</span>
});
lista.addEventListener(<span class="tok-string">"change"</span>, () => {
setState({<span class="tok-definition">notes</span>: estado.notes, <span class="tok-definition">selected</span>: lista.value});
});
nota.addEventListener(<span class="tok-string">"change"</span>, () => {
<span class="tok-keyword">let</span> {selected} = estado;
setState({
<span class="tok-definition">notes</span>: {...estado.notes, [selected]: nota.value},
<span class="tok-definition">selected</span>
});
});
document.querySelector(<span class="tok-string">"button"</span>)
.addEventListener(<span class="tok-string">"click"</span>, () => {
<span class="tok-keyword">let</span> <span class="tok-definition">nombre</span> = prompt(<span class="tok-string">"Nombre de la nota"</span>);
<span class="tok-keyword">if</span> (nombre) setState({
<span class="tok-definition">notes</span>: {...estado.notes, [nombre]: <span class="tok-string">""</span>},
<span class="tok-definition">selected</span>: nombre
});
});
</<span class="tok-typeName">script</span>></pre>
<p><a class="p_ident" id="p-j38aAM3IQT" href="#p-j38aAM3IQT" tabindex="-1" role="presentation"></a>El script obtiene su estado inicial del valor <code>"Notas"</code> almacenado en <code>localStorage</code> o, si este no existe, crea un estado de ejemplo que solo contiene una lista de compras. Leer un campo que no existe en <code>localStorage</code> devolverá <code>null</code>. Pasar <code>null</code> a <code>JSON.parse</code> hará que analice la cadena <code>"null"</code> y devuelva <code>null</code>. Por tanto, en una situación como esta se puede utilizar el operador <code>??</code> para proporcionar un valor predeterminado.</p>
<p><a class="p_ident" id="p-P4AyBp+4Wu" href="#p-P4AyBp+4Wu" tabindex="-1" role="presentation"></a>El método <code>setState</code> se asegura de que el DOM muestre un estado dado y almacena el nuevo estado en <code>localStorage</code>. Los controladores de eventos llaman a esta función para moverse a un nuevo estado.</p>
<p><a class="p_ident" id="p-d73ikDmnEL" href="#p-d73ikDmnEL" tabindex="-1" role="presentation"></a>La sintaxis <code>...</code> en el ejemplo se utiliza para crear un nuevo objeto que es un clon del antiguo <code>estado.notes</code>, pero con una propiedad añadida o sobrescrita. Utiliza la sintaxis de expansión para primero añadir las propiedades del objeto antiguo y luego establecer una nueva propiedad. La notación de corchetes cuadrados en el literal del objeto se utiliza para crear una propiedad cuyo nombre se basa en algún valor dinámico.</p>
<p><a class="p_ident" id="p-/7RsOyK0uu" href="#p-/7RsOyK0uu" tabindex="-1" role="presentation"></a>Existe otro objeto, similar a <code>localStorage</code>, llamado <code>sessionStorage</code>. La diferencia entre los dos es que el contenido de <code>sessionStorage</code> se olvida al final de cada <em>sesión</em>, lo que en la mayoría de los navegadores significa cada vez que se cierra el navegador.</p>
<h2><a class="h_ident" id="h-NUFOUyK+lw" href="#h-NUFOUyK+lw" tabindex="-1" role="presentation"></a>Resumen</h2>
<p><a class="p_ident" id="p-Kxuc0AeHn4" href="#p-Kxuc0AeHn4" tabindex="-1" role="presentation"></a>En este capítulo, discutimos cómo funciona el protocolo HTTP. Un <em>cliente</em> envía una solicitud, que contiene un método (generalmente <code>GET</code>) y una ruta que identifica un recurso. El <em>servidor</em> luego decide qué hacer con la solicitud y responde con un código de estado y un cuerpo de respuesta. Tanto las solicitudes como las respuestas pueden contener encabezados que proporcionan información adicional.</p>
<p><a class="p_ident" id="p-SdZFcq61He" href="#p-SdZFcq61He" tabindex="-1" role="presentation"></a>La interfaz a través de la cual el JavaScript del navegador puede realizar solicitudes HTTP se llama <code>fetch</code>. Este es el aspecto que tiene una tal solicitud:</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-XuMQraOA7R" href="#c-XuMQraOA7R" tabindex="-1" role="presentation"></a>fetch(<span class="tok-string">"/18_http.html"</span>).then(<span class="tok-definition">r</span> => r.text()).then(<span class="tok-definition">texto</span> => {
console.log(<span class="tok-string2">`La página comienza con </span>${texto.slice(<span class="tok-number">0</span>, <span class="tok-number">15</span>)}<span class="tok-string2">`</span>);
});</pre>
<p><a class="p_ident" id="p-ujoeRsyQ0k" href="#p-ujoeRsyQ0k" tabindex="-1" role="presentation"></a>Los navegadores hacen solicitudes <code>GET</code> para obtener los recursos necesarios para mostrar una página web. Una página también puede contener formularios, que permiten enviar información ingresada por el usuario como una solicitud de una nueva página cuando se envía el formulario.</p>
<p><a class="p_ident" id="p-8J7KIuzXjY" href="#p-8J7KIuzXjY" tabindex="-1" role="presentation"></a>HTML puede representar varios tipos de campos de formulario, como campos de texto, casillas de verificación, campos de selección múltiple y selectores de archivos.</p>
<p><a class="p_ident" id="p-Yb3ZSXokZx" href="#p-Yb3ZSXokZx" tabindex="-1" role="presentation"></a>Estos campos pueden ser inspeccionados y manipulados con JavaScript. Disparan el evento <code>"change"</code> al cambiar, disparan el evento <code>"input"</code> al escribir texto y reciben eventos del teclado cuando tienen el foco del teclado. Propiedades como <code>value</code> (para campos de texto y select) o <code>checked</code> (para casillas de verificación y botones de radio) se utilizan para leer o establecer el contenido del campo.</p>
<p><a class="p_ident" id="p-JuvOi1Znba" href="#p-JuvOi1Znba" tabindex="-1" role="presentation"></a>Cuando un formulario se envía, se dispara un evento <code>"submit"</code> en él. Un controlador de JavaScript puede llamar a <code>preventDefault</code> en ese evento para deshabilitar el comportamiento predeterminado del navegador. Los elementos de campo de formulario también pueden ocurrir fuera de una etiqueta de formulario.</p>
<p><a class="p_ident" id="p-yCWUZfm1OX" href="#p-yCWUZfm1OX" tabindex="-1" role="presentation"></a>Cuando el usuario ha seleccionado un archivo de su sistema de archivos local en un campo de selección de archivos, la interfaz <code>FileReader</code> se puede utilizar para acceder al contenido de este archivo desde un programa JavaScript.</p>
<p><a class="p_ident" id="p-JdMjhupJB+" href="#p-JdMjhupJB+" tabindex="-1" role="presentation"></a>Los objetos <code>localStorage</code> y <code>sessionStorage</code> se pueden usar para guardar información de una manera que sobrevive a las recargas de la página. El primer objeto guarda los datos para siempre (o hasta que el usuario decida borrarlos) y el segundo los guarda hasta que se cierra el navegador.</p>
<h2><a class="h_ident" id="h-tkm7ntLto1" href="#h-tkm7ntLto1" tabindex="-1" role="presentation"></a>Ejercicios</h2>
<h3><a class="i_ident" id="i-DceWkz2eC7" href="#i-DceWkz2eC7" tabindex="-1" role="presentation"></a>Negociación de contenido</h3>
<p><a class="p_ident" id="p-l9PgkJX1Sa" href="#p-l9PgkJX1Sa" tabindex="-1" role="presentation"></a>Una de las cosas que HTTP puede hacer es la <em>negociación de contenido</em>. El encabezado de solicitud <code>Accept</code> se utiliza para indicar al servidor qué tipo de documento le gustaría obtener al cliente. Muchos servidores ignoran este encabezado, pero cuando un servidor conoce diversas formas de codificar un recurso, puede mirar este encabezado y enviar la que el cliente prefiera.</p>
<p><a class="p_ident" id="p-1opygtAynh" href="#p-1opygtAynh" tabindex="-1" role="presentation"></a>La URL <a href="https://eloquentjavascript.net/author"><em>https://eloquentjavascript.net/author</em></a> está configurada para responder ya sea con texto sin formato, HTML o JSON, dependiendo de lo que pida el cliente. Estos formatos están identificados por los <em>tipos de medios</em> estandarizados <code>text/plain</code>, <code>text/html</code> y <code>application/json</code>.</p>
<p><a class="p_ident" id="p-Da+AuW4Tul" href="#p-Da+AuW4Tul" tabindex="-1" role="presentation"></a>Envía solicitudes para obtener los tres formatos de este recurso. Utiliza la propiedad <code>headers</code> en el objeto de opciones pasado a <code>fetch</code> para establecer el encabezado llamado <code>Accept</code> en el tipo de medios deseado.</p>
<p><a class="p_ident" id="p-qdApw+ULBF" href="#p-qdApw+ULBF" tabindex="-1" role="presentation"></a>Finalmente, intenta pedir el tipo de medios <code>application/<wbr>rainbows+unicorns</code> y mira qué código de estado produce.</p>
<pre tabindex="0" class="snippet" data-language="javascript" ><a class="c_ident" id="c-ROnwerR8AG" href="#c-ROnwerR8AG" tabindex="-1" role="presentation"></a><span class="tok-comment">// Tu código aquí.</span></pre>
<details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text">
<p><a class="p_ident" id="p-B0WEv7Iz0W" href="#p-B0WEv7Iz0W" tabindex="-1" role="presentation"></a>Básate en los ejemplos de <code>fetch</code> <a href="18_http.html#fetch">que vimos antes en el capítulo</a>.</p>
<p><a class="p_ident" id="p-IDCMvcNVmp" href="#p-IDCMvcNVmp" tabindex="-1" role="presentation"></a>Solicitar un tipo de medio falso devolverá una respuesta con el código 406, “No aceptable”, que es el código que un servidor debería devolver cuando no puede cumplir con el encabezado <code>Accept</code>.</p>
</div></details>
<h3><a class="i_ident" id="i-Wt/lEVLoEd" href="#i-Wt/lEVLoEd" tabindex="-1" role="presentation"></a>Un banco de trabajo de JavaScript</h3>
<p><a class="p_ident" id="p-WrCCh4695B" href="#p-WrCCh4695B" tabindex="-1" role="presentation"></a>Construye una interfaz que permita a las personas escribir y ejecutar fragmentos de código JavaScript.</p>
<p><a class="p_ident" id="p-Cq/MiKUIsw" href="#p-Cq/MiKUIsw" tabindex="-1" role="presentation"></a>Coloca un botón al lado de un campo <code><textarea></code> que, al ser presionado, utilice el constructor <code>Function</code> que vimos en el <a href="10_modules.html#eval">Capítulo 10</a> para envolver el texto en una función y llamarla. Convierte el valor de retorno de la función, o cualquier error que genere, a una cadena y muéstralo debajo del campo de texto.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-ptqjTyjMJH" href="#c-ptqjTyjMJH" tabindex="-1" role="presentation"></a><<span class="tok-typeName">textarea</span> id=<span class="tok-string">"code"</span>>return "hi";</<span class="tok-typeName">textarea</span>>
<<span class="tok-typeName">button</span> id=<span class="tok-string">"button"</span>>Run</<span class="tok-typeName">button</span>>
<<span class="tok-typeName">pre</span> id=<span class="tok-string">"output"</span>></<span class="tok-typeName">pre</span>>
<<span class="tok-typeName">script</span>>
<span class="tok-comment">// Tu código aquí.</span>
</<span class="tok-typeName">script</span>></pre>
<details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text">
<p><a class="p_ident" id="p-AnMiVGe/eL" href="#p-AnMiVGe/eL" tabindex="-1" role="presentation"></a>Utiliza <code>document.<wbr>querySelector</code> o <code>document.<wbr>getElementById</code> para acceder a los elementos definidos en tu HTML. Un manejador de eventos para eventos <code>"click"</code> o <code>"mousedown"</code> en el botón puede obtener la propiedad <code>value</code> del campo de texto y llamar a <code>Function</code> en él.</p>
<p><a class="p_ident" id="p-4e8/QXypu+" href="#p-4e8/QXypu+" tabindex="-1" role="presentation"></a>Asegúrate de envolver tanto la llamada a <code>Function</code> como la llamada a su resultado en un bloque <code>try</code> para poder capturar las excepciones que produce. En este caso, realmente no sabemos qué tipo de excepción estamos buscando, así que captura todo.</p>
<p><a class="p_ident" id="p-yvH5JsBX5j" href="#p-yvH5JsBX5j" tabindex="-1" role="presentation"></a>La propiedad <code>textContent</code> del elemento de salida se puede utilizar para llenarlo con un mensaje de cadena. O, si deseas mantener el contenido anterior, crea un nuevo nodo de texto utilizando <code>document.<wbr>createTextNode</code> y añádelo al elemento. Recuerda agregar un carácter de nueva línea al final para que no aparezca toda la salida en una sola línea.</p>
</div></details>
<h3><a class="i_ident" id="i-lCrQZlCzVi" href="#i-lCrQZlCzVi" tabindex="-1" role="presentation"></a>Juego de la vida de Conway</h3>
<p><a class="p_ident" id="p-VTdXQ4iYhX" href="#p-VTdXQ4iYhX" tabindex="-1" role="presentation"></a>El Juego de la vida de Conway es una simulación simple que crea “vida” artificial en una rejilla, donde cada célula puede estar viva o no. En cada generación (turno), se aplican las siguientes reglas:</p>
<ul>
<li>
<p><a class="p_ident" id="p-COJyQnzFy0" href="#p-COJyQnzFy0" tabindex="-1" role="presentation"></a>Cualquier célula viva con menos de dos o más de tres vecinos vivos muere.</p></li>
<li>
<p><a class="p_ident" id="p-XOqq4wf276" href="#p-XOqq4wf276" tabindex="-1" role="presentation"></a>Cualquier célula viva con dos o tres vecinos vivos sigue viva en la siguiente generación.</p></li>
<li>
<p><a class="p_ident" id="p-lGH+tteGaa" href="#p-lGH+tteGaa" tabindex="-1" role="presentation"></a>Cualquier célula muerta con exactamente tres vecinos vivos se convierte en una célula viva.</p></li></ul>
<p><a class="p_ident" id="p-ioE5xDUdj8" href="#p-ioE5xDUdj8" tabindex="-1" role="presentation"></a>Un <em>vecino</em> se define como cualquier célula adyacente, incluidas las célula adyacentes en diagonal.</p>
<p><a class="p_ident" id="p-9WLpoNjq4C" href="#p-9WLpoNjq4C" tabindex="-1" role="presentation"></a>Ten en cuenta que estas reglas se aplican a toda la rejilla a la vez, no cuadrado por cuadrado. Eso significa que el recuento de vecinos se basa en la situación al comienzo de la generación, y los cambios que ocurran en las células vecinas durante esta generación no deberían influir en el nuevo estado de una célula dada.</p>
<p><a class="p_ident" id="p-DvtuDHOwfl" href="#p-DvtuDHOwfl" tabindex="-1" role="presentation"></a>Implementa este juego utilizando la estructura de datos que consideres apropiada. Utiliza <code>Math.random</code> para poblar la rejilla con un patrón aleatorio inicialmente. Muestra la rejilla como una cuadrícula de campo de verificación campos, con un botón al lado para avanzar a la siguiente generación. Cuando el usuario marque o desmarque los campos de verificación, sus cambios deberían incluirse al calcular la siguiente generación.</p>
<pre tabindex="0" class="snippet" data-language="html" ><a class="c_ident" id="c-8RvsJC/O9D" href="#c-8RvsJC/O9D" tabindex="-1" role="presentation"></a><<span class="tok-typeName">div</span> id=<span class="tok-string">"grid"</span>></<span class="tok-typeName">div</span>>
<<span class="tok-typeName">button</span> id=<span class="tok-string">"next"</span>>Siguiente generación</<span class="tok-typeName">button</span>>
<<span class="tok-typeName">script</span>>
<span class="tok-comment">// Tu código aquí.</span>
</<span class="tok-typeName">script</span>></pre>
<details class="solution"><summary>Mostrar pistas...</summary><div class="solution-text">
<p><a class="p_ident" id="p-j+hMBMOeev" href="#p-j+hMBMOeev" tabindex="-1" role="presentation"></a>Para resolver el problema de que los cambios ocurran conceptualmente al mismo tiempo, intenta ver la computación de una generación como una función pura, la cual toma un grid y produce un nuevo grid que representa el siguiente turno.</p>
<p><a class="p_ident" id="p-Ze9kawBXIS" href="#p-Ze9kawBXIS" tabindex="-1" role="presentation"></a>La representación de la matriz se puede hacer con un solo array de elementos de ancho × alto, almacenando valores fila por fila, por lo que, por ejemplo, el tercer elemento en la quinta fila se almacena en la posición 4 × <em>ancho</em> + 2 (usando indexación basada en cero). Puedes contar los vecinos vivos con dos bucles anidados, recorriendo coordenadas adyacentes en ambas dimensiones. Asegúrate de no contar célula fuera del campo e ignorar la célula en el centro, cuyos vecinos estamos contando.</p>
<p><a class="p_ident" id="p-uQnDbslm5/" href="#p-uQnDbslm5/" tabindex="-1" role="presentation"></a>Asegurarse de que los cambios en los checkbox tengan efecto en la siguiente generación es algo que se puede hacer de dos maneras. Un manejador de eventos podría notar estos cambios y actualizar el grid actual para reflejarlos, o podrías generar un grid nuevo a partir de los valores de los checkboxes antes de calcular el siguiente turno.</p>
<p><a class="p_ident" id="p-DDWZCwFlLn" href="#p-DDWZCwFlLn" tabindex="-1" role="presentation"></a>Si decides utilizar manejadores de eventos, es posible que desees adjuntar atributos que identifiquen la posición a la que corresponde cada checkbox para que sea fácil saber qué célula cambiar.</p>
<p><a class="p_ident" id="p-QGc6rnbVRd" href="#p-QGc6rnbVRd" tabindex="-1" role="presentation"></a>Para dibujar el grid de checkboxes, puedes usar un elemento <code><table></code> (ver <a href="14_dom.html#exercise_table">Capítulo 14</a>) o simplemente colocar todos en el mismo elemento y poner elementos <code><br></code> (salto de línea) entre las filas.</p>
</div></details><nav><a href="17_canvas.html" title="previous chapter" aria-label="previous chapter">◂</a> <a href="index.html" title="cover" aria-label="cover">●</a> <a href="19_paint.html" title="next chapter" aria-label="next chapter">▸</a> <button class=help title="help" aria-label="help"><strong>?</strong></button>
</nav>
</article>
<script src="ejs.js"></script>