Vous êtes sur la page 1sur 60

“Un estudiante preguntó, 'Los programadores de edad utilizan sólo las máquinas simples y no hay

lenguajes de programación, sin embargo, hizo hermosas programas. ¿Por qué utilizamos máquinas
complicadas y lenguajes de programación?'. Fu-Tzu respondió: 'Los constructores de edad utilizados
sólo palos y barro, sin embargo, hacen hermosas cabañas.”'

- El maestro Yuan-Ma, El Libro de Programación

capítulo 20

Node.js

Hasta ahora, hemos utilizado el lenguaje JavaScript en un entorno único: el navegador. Este capítulo y el el
proximo introducirá brevemente Node.js, un pro- grama que le permite aplicar sus habilidades de JavaScript
fuera del navegador. Con él, usted puede construir cualquier cosa, desde pequeñas herramientas de línea de
comandos HTTP a servidores que alimentan sitios web dinámicos.

Estos capítulos tienen como objetivo enseñarle los principales conceptos que Node.js y usos para darle suficiente
información para escribir programas útiles para ello. Ellos no tratan de ser un completo, o incluso una investigación
exhaustiva, el tratamiento de la plataforma.
Si desea seguir adelante y ejecutar el código en este capítulo, tendrá que instalar Node.js versión
10.1 o superior. Para ello, vaya a https://nodejs.org y siga las instrucciones de instalación para su
sistema operativo. También puede encontrar más documentación para Node.js allí.

Antecedentes

Uno de los problemas más difíciles con los sistemas de escritura que se comunican a través de la red es la
gestión de entrada y salida, es decir, la lectura y escritura de datos desde y hacia la red y disco duro. El
movimiento de datos en todo lleva su tiempo, y la programación que hábilmente puede hacer una gran
diferencia en la rapidez con un sistema responde al usuario oa las solicitudes de red.

En este tipo de programas, programación asincrónica es a menudo muy útil. Se permite que el programa
para enviar y recibir datos desde y hacia múltiples dispositivos al mismo tiempo sin administración de
subprocesos complicado y sincronización.
Nodo fue inicialmente concebido con el propósito de hacer programa- ción asíncrona fácil y conveniente.
JavaScript se presta bien a un sistema como el Nodo. Es uno de los pocos lenguajes de programación que
no tienen una forma integrada de hacer la salida dentro y. Por lo tanto, JavaScript podría caber hasta la
aproximación bastante excéntrico del Nodo de entrada y de salida sin acabar con dos interfaces
inconsistentes. En 2009, cuando el nodo se está diseñando, la gente ya estaba DO-Ing programación
basada en la devolución de llamada en el navegador, por lo que la comunidad en torno a la

350
el lenguaje se utilizó para un estilo de programación asíncrona.

El comando nodo

Cuando Node.js está instalado en un sistema, que proporciona un programa llamado nodo, que se utiliza para ejecutar
archivos JavaScript. Digamos que tiene un archivo hello.js, que contiene este código:

dejar que el mensaje = "Hola mundo";


console.log (mensaje);

A continuación, puede ejecutar nodo desde la línea de comandos como este para ejecutar el pro- grama:

$ Nodo hello.js Hola


mundo

los console.log método en el Nodo hace algo similar a lo que hace en el navegador. Se imprime un
trozo de texto. Pero en el Nodo, el texto irá al flujo de salida estándar del proceso, en lugar de a la
consola de JavaScript de un navegador. cuando se ejecuta nodo desde la línea de comandos, lo que
significa que vea los valores registrados en su terminal.

Si tu corres nodo sin darle un archivo, se le proporciona un aviso en el que puede escribir código
JavaScript y ver inmediatamente el resultado.

$ nodo
> 1+12

> [-1, -2, -3] .map (Math.abs) [1, 2, 3]

> process.exit (0) $

los proceso vinculante, al igual que el consola vinculante, está disponible a nivel mundial en el Nodo.
Proporciona diversas maneras para inspeccionar y manipular el programa actual. los salida método finaliza el
proceso y se puede dar un código de estado de salida, que le dice al programa que se inició nodo ( en este
caso, la línea de comandos shell) si el programa ha completado correctamente (código cero) o encontró un
error (cualquier otro código).

Para encontrar los argumentos de línea de comandos dados a la secuencia de comandos, se puede leer

351
process.argv, que es una matriz de cadenas. Tenga en cuenta que también incluye el nombre de la nodo mando

y el nombre del script, por lo que los argumentos reales comienzan en el índice 2. Si showargv.js contiene la
declaración console.log (process.argv),
usted podría funcionar de esta manera:

$ Nodo showargv.js uno -y dos


[ "Nodo", "/tmp/showargv.js", "uno", "--y", "dos"]

Todos los enlaces JavaScript estándares mundiales, tales como Array, Matemáticas, y JSON,
también están presentes en el entorno del nodo. funcionalidad relacionado con el navegador, como por ejemplo

documento o rápido, no es.

módulos

Más allá de los enlaces que he mencionado, tales como consola y proceso, Nodo pone pocas fijaciones
adicionales en el ámbito global. Si desea acceder a la funcionalidad incorporada, usted tiene que pedir el
sistema de módulos para ello.
El sistema de módulos CommonJS, basado en el exigir función, fue trazada de- en Capítulo 10 . Este
sistema está integrado en nodos y se utiliza para cargar cualquier cosa de módulos integrados para los
paquetes descargados a los archivos que forman parte de su propio programa.

Cuando exigir se llama, Nodo tiene que resolver la cadena dada a un archivo real que se pueda cargar.
Nombres de las rutas que comienzan con /, ./, o ../ se resuelven con relación a la trayectoria del módulo
actual, dónde. representa el directorio actual,
. . / para un directorio arriba, y / para la raíz del sistema de archivos. Así que si usted pide" ./ grafico" desde
el archivo / tmp / robot / robot.js, Nodo intentará cargar el archivo

/tmp/robot/graph.js.
Los . js extensión puede ser omitido, y el Nodo añadirá si existe tal archivo. Si la ruta requerida se
refiere a un directorio, el nodo intentará cargar el archivo llamado
index.js en ese directorio.
Cuando se da una cadena que no se ve como una ruta relativa o absoluta exigir, se supone para hacer
referencia a un módulo integrado o un módulo instalado en una node_modules directorio. Por ejemplo, requerir
( "FS") le dará módulo de sistema de archivos integrado del Nodo. Y require ( "robot") podría tratar de cargar la

biblioteca que se encuentra en node_modules / robot /. Una forma común para instalar estas bibliotecas es
mediante el uso de la NGP, que vamos a volver en un momento.

Vamos a crear un pequeño proyecto que consiste en dos archivos. El primero, denominado principal

. js, define una secuencia de comandos que se puede llamar desde la línea de comandos para revertir una cadena.

352
const {invertir} = require ( "./ inversa");

// Índice 2 ocupa el primer argumento actual línea de comandos deja argumento = process.argv
[2];

console.log (inversa (argumento));

El archivo reverse.js define una biblioteca de cadenas de marcha atrás, que pueden ser utilizados tanto por esta
herramienta de línea de comandos y otros scripts que necesitan acceso directo a una función de cadena de reversión.

exports.reverse = función (string) {


volver Array.from (cadena) .reverse () se unen ( ""); };

Recuerde que las propiedades Agregando exportaciones los agrega a la interfaz del módulo. Desde
Node.js trata los archivos como módulos CommonJS, main.js puede tomar la exportado marcha atrás la función
de reverse.js.
Ahora podemos llamar a nuestra herramienta como esta:

$ Nodo main.js JavaScript tpircSavaJ

Instalación con la NGP

NPM, que se introdujo en Capítulo 10 , Es un repositorio en línea de módulos de JavaScript, muchas de las cuales
están escritas específicamente para el nodo. Al instalar el Nodo en su computadora, usted también consigue el NPM
de comandos, que se puede utilizar para interactuar con este repositorio.

El uso principal del MNP es la descarga de paquetes. Vimos el ini paquete de Capítulo 10 . Podemos utilizar
la NGP a buscar e instalar ese paquete en nuestro ordenador.

$ NPM instalar ini


NPM WARN ENOENT ENOENT: No existe el fichero o directorio,
abierta '/tmp/package.json'
+ ini@1.3.5
añadido 1 paquete en 0.552s

$ nodo
> const {de análisis sintáctico} = requieren ( "ini");

353
> analizar ( "x = 1 \ ny = 2"); {X: '1', y: '2'}

despues de correr NPM instalar, NPMwill han creado un directorio llamado node_modules
. Dentro de ese directorio será una ini directorio que contiene la biblioteca. Puede abrirlo y mirar el código.
Cuando llamamos require ( "ini"), esta biblioteca se carga, y podemos llamar a su analizar gramaticalmente propiedad
para analizar un archivo de configuración.
Por defecto NPM instala los paquetes en el directorio actual, en lugar de en un lugar central. Si
estás acostumbrado a otros gestores de paquetes, esto puede parecer inusual, pero tiene ventajas
que pone a cada aplicación en el control total de los paquetes que instala y hace más fácil el manejo
de versiones y limpiar cuando se retira una aplicación.

Los archivos del paquete

En el NPM instalar ejemplo, se podía ver una advertencia sobre el hecho de que la
package.json No existe archivo. Se recomienda crear un archivo de este tipo para cada proyecto, ya sea

manualmente o mediante la ejecución init NPM. Contiene información sobre el proyecto, tales como su nombre
y la versión, y enumera sus dependencias.
La simulación robot de Capítulo 7 , Como modularizado en el ejercicio de
Capítulo 10 , Podría tener una package.json presentar la siguiente manera:

{
"Autor": "Marijn Haverbeke", "name":
"elocuente-javascript-robot",
"Descripción": "Simulación de un robot paquete de entrega", "versión": "1.0.0", "principal": "run.js",
"dependencias": {

"Dijkstrajs": "-artículo al azar" "^ 1.0.1":


"^ 1.0.0"},

"Licencia": "CAI"}

Cuando se ejecuta NPM instalar sin nombrar a un paquete para instalar, NPM instalará las
dependencias enumeradas en el package.json. Al instalar un paquete específico que no esté catalogado
como una dependencia, la NGP se añadirá a paquete
. JSON.

354
versiones

UNA package.json archivo lista tanto propia versión y las versiones del programa para sus dependencias. Las versiones
son una forma de lidiar con el hecho de que los paquetes se desarrollan por separado, y el código escrito para trabajar
con un paquete tal como existía en un momento dado puede no funcionar con una versión posterior, modificada del
paquete.
NPM exige que sus paquetes siguen un esquema denominado versiones semántica,
que codifica una cierta información sobre las versiones que están compatible ( no romper la antigua
interfaz) en el número de versión. Una versión semántica consta de tres números, separados por puntos,
tales como 2.3.0. Cada vez que se añade nueva funcio- nalidad, el número medio tiene que ser
incrementado. Cada compatibilidad del tiempo se rompe, por lo que el código existente que utiliza el
paquete podría no funcionar con la nueva versión, el primer número tiene que ser incrementado.

Un carácter de intercalación (^) delante del número de versión para una dependencia de
package.json indica que cualquier versión compatible con el número dado puede ser instalado. Así, por
ejemplo, "^ 2.3.0" que significaría que se permite cualquier versión superior o igual a 2.3.0 y menos de
3.0.0.
los NPM comando también se utiliza para publicar nuevos paquetes o nuevas versiones de los paquetes. Si tu
corres NPM publicar en un directorio que tiene una package.json archivo, se publicará un paquete con el nombre y la
versión que aparece en el archivo JSON para el registro. Cualquiera puede publicar paquetes de NPM-aunque
sólo bajo un nombre de paquete que no está en uso todavía, ya que sería un poco de miedo si la gente al azar
podrían actualizar los paquetes existentes.

Desde el NPM programa es una pieza de software que se comunica con un proceso abierto sistema-
el paquete de registro, no hay nada único sobre lo que hace. Otro programa, hilo, que se puede instalar
desde el registro MNP, llena el mismo papel que NPM mediante una estrategia de interfaz y la instalación
algo diferente.
Este libro no va a profundizar en los detalles de uso de la NGP. Referirse a
https://npmjs.org de documentación adicional y una manera de buscar paquetes.

El archivo systemmodule

Uno de los módulos incorporados más utilizados en el nodo es el fs módulo, que significa sistema de
archivos. Exporta funciones para trabajar con archivos y directorios.

Por ejemplo, la función llamada readFile lee un archivo y luego llama a una llamada- atrás con el contenido
del archivo.

sea ​{} readFile = require ( "FS");


readFile ( "archivo.txt", "UTF-8", (error, texto) => {

355
si (error) Error de tiro;
console.log ( "El archivo contiene:", texto); });

El segundo argumento readFile indica el codificación de caracteres utilizado para decodificar el archivo en una
cadena. Hay varias formas en las que el texto puede ser codificado en datos binarios, pero la mayoría de los
sistemas modernos utilizan UTF-8. A menos que tenga razones para creer que se utiliza otro sistema de
codificación, pasar " UTF-8" cuando se lee un archivo de texto. Si usted no pasa una codificación, Nodo asumirá que
usted está interesado en los datos binarios y le dará una Buffer objeto en lugar de una cadena. Este es un objeto de
matriz similar que contiene números que representan los bytes (trozos de 8 bits de datos) en los archivos.

const {readFile} = requerir ( "FS"); readFile ( "archivo.txt", (error,


tampón) => {
si (error) Error de tiro;
console.log ( "El archivo contenía", buffer.length, "bytes".,
"El primer byte es:", buffer [0]);
});

Una función similar, writefile, se utiliza para escribir un archivo en el disco.

const {writefile} = requerir ( "FS");


writefile ( "graffiti.txt", "Nodo estaba aquí", err => {
si (err) console.log ( `No se ha podido escribir el archivo: $ {err}`); console.log otra cosa (
"Archivo escrito."); });

En este caso no era necesario especificar el Codif writefile asumirá que cuando se le da una cadena
para escribir, en lugar de una Buffer objeto, debe escribirlo en forma de texto utilizando su codificación de
caracteres por defecto, que es UTF-8.
los fs módulo contiene muchas otras funciones útiles: readdir devolverá los archivos en un directorio como una matriz de
cadenas, stat será recuperar información acerca de un archivo, rebautizar se cambie el nombre de un archivo, desconectar eliminará
uno, y así sucesivamente. Consulte la documentación en https://nodejs.org para más detalles.

La mayor parte de estos toma una función de devolución de llamada como el último parámetro, al que llaman ya
sea con un error (el primer argumento) o con un resultado exitoso (el segundo). Como vimos en el Capítulo 11 , Hay
desventajas a este estilo de programación en el más importante es el control de errores se convierte en verbosa y
propenso a errores.
A pesar de las promesas han sido parte de JavaScript por un tiempo, en el momento de ESCRITO

356
ing su integración en Node.js es todavía un trabajo en progreso. Hay un objeto
promesas exportados fuera de la fs paquete desde la versión 10.1 que contiene la mayor parte de las mismas funciones que fs pero

utiliza promesas en lugar de las funciones de devolución de llamada.

const {readFile} = require ( "FS") promete.; readFile ( "archivo.txt", "utf8")

. a continuación (texto => console.log ( "El archivo contiene:", texto));

A veces no es necesario asincronía, y que sólo se interpone en el camino. Muchas de las


funciones de fs también tienen una variante síncrono, que tiene el mismo nombre con sync añadido al
final. Por ejemplo, la versión sincrónica de
readFile se llama readFileSync.

const {readFileSync} = requerir ( "FS"); console.log ( "El archivo


contiene:",
readFileSync ( "archivo.txt", "utf8"));

Ten en cuenta que mientras se está realizando una operación de este tipo síncrono, el programa
se detiene por completo. Si hay que responde al usuario oa otras máquinas en la red, estar atrapado
en una acción síncrona podría producir retrasos molestos.

El módulo HTTP

Otro módulo central se llama http. Proporciona funcionalidad para el funcionamiento de los servidores HTTP y
hacer peticiones HTTP.
Esto es todo lo que se necesita para iniciar un servidor HTTP:

const {createServer} = require ( "http"); dejar que el servidor = createServer ((petición y


respuesta) => {
response.writeHead (200, { "Content-Type": "text / html"}); response.write ( `

<H1> ¡Hola! </ H1>


<P> Usted pidió <code> $ {} request.url </ code> </ p> '); Response.End (); });

server.listen (8000);
console.log ( "Escuchando (puerto 8000)!");

Si ejecuta este script en su propia máquina, se puede escribir en el navegador web en http: // localhost:
8000 / hola para hacer una petición al servidor. Se responderá

357
con una pequeña página HTML.
La función se pasa como argumento a createServer se llama cada vez que un cliente se conecta al
servidor. los solicitud y respuesta enlaces son objetos que representan los datos entrantes y salientes. La
primera contiene información acerca de la solicitud, tales como su url propiedad, lo cual nos dice que lo
que se hizo la solicitud de URL.

Por lo tanto, cuando se abre la página en el navegador, envía una petición a su propio ordenador. Esto
hace que la función de servidor para ejecutar y enviar una respuesta, que luego se puede ver en el
navegador.
Para enviar algo a cambio, se llama a métodos en el respuesta objeto. El primero,
writeHead, escribirá a cabo las cabeceras de respuesta (véase capítulo 18 ). Se le da el código de estado (200

para “OK” en este caso) y un objeto que contiene valores de cabecera. El ejemplo se establece el Tipo de
contenido cabecera para informar al cliente de que vamos a enviar de vuelta un documento HTML.

A continuación, el cuerpo de la respuesta real (el propio documento) se envía con respuesta
. escribir. Se le permite a llamar a este método varias veces si desea enviar la pieza a pieza respuesta,
por ejemplo, para transmitir datos al cliente en cuanto esté disponible. Finalmente, Response.End señala
el final de la respuesta.
La llamada a server.listen hace que el servidor se inicie la espera de conexiones en el puerto 8000. Esta es la
razón por lo que tiene que conectarse a localhost: 8000 hablar con este servidor, en lugar de sólo localhost, que
pueden utilizar el puerto por defecto 80.
Al ejecutar esta secuencia de comandos, el proceso simplemente se sienta y espera. Cuando una secuencia de
comandos está escuchando para eventos, en este caso, conexiones-red nodo No se saldrá automáticamente cuando se llega
al final de la secuencia de comandos. Para cerrarla, pulse
controlar- C.
Un servidor web real, por lo general hace más que el que está en el ejemplo se ve en el método de la
solicitud (la método propiedad) para ver la acción que el cliente está tratando de llevar a cabo y se ve en la
dirección URL de la solicitud para saber qué recursos se está realizando en esta acción. Veremos un servidor
más avanzado más adelante en este capítulo .

Para actuar como un servidor HTTP cliente, podemos utilizar el solicitud función en el http
módulo.

const {petición} = require ( "http"); dejar que requestStream


= petición ({
nombre de host: "eloquentjavascript.net", ruta:
"/20_node.html", método: "GET",

encabezados: {Aceptar: "text / html"}}, la respuesta


=> {
console.log ( "El servidor respondió con código de estado",

358
response.statusCode);
});
requestStream.end ();

El primer argumento de solicitud configura la petición, diciendo lo Nodo servidor para hablar, cuál es el
camino a pedir a dicho servidor, el método a utilizar, y así sucesivamente. El segundo argumento es la
función que debe ser llamado cuando la respuesta viene en. Se da un objeto que nos permite
inspeccionar la respuesta, por ejemplo, para averiguar su código de estado.

Al igual que el respuesta objeto que vimos en el servidor, el objeto devuelto por
solicitud nos permite flujo de datos en la solicitud con el escribir método y terminar la petición con el fin método.
El ejemplo no utiliza escribir
porque OBTENER solicitudes no deben contener datos en su cuerpo de la petición.
Hay un parecido solicitud función en el https módulo que se puede utilizar para hacer peticiones a https:
URLs.
Hacer peticiones con la funcionalidad prima de Node es bastante detallado. Hay mucho más
conveniente paquetes de envoltura disponibles en la NGP. Por ejemplo,
nodo-fetch ofrece la promesa de base ir a buscar interfaz que sabemos desde el navegador.

corrientes

Hemos visto dos instancias de corrientes de escritura en el HTTP ejemplos- es decir, el objeto
respuesta que el servidor ha podido escribir en el objeto y la solicitud de que se volvió de solicitud.

corrientes de escritura son un concepto ampliamente utilizado en el nodo. Tales objetos tienen una
escribir método que se puede pasar una cadena o una Buffer oponerse a escribir algo a la corriente. Su fin método

cierra la corriente y, opcionalmente, toma un valor a escribir en el flujo antes del cierre. Ambos de estos
métodos también se puede dar una devolución de llamada como un argumento adicional, que van a llamar
cuando la escritura o el cierre ha terminado.

Es posible crear una corriente de escritura que apunta a un archivo con el


createWriteStream función de la fs módulo. A continuación, puede utilizar la escribir
método en el objeto resultante para escribir el archivo de una sola pieza a la vez, en lugar de en una sola toma al igual
que con writefile.
legibles corrientes son un poco más complicado. Ambos solicitud la unión que se ha pasado de devolución de
llamada y el del servidor HTTP respuesta La unión se pasa a devolución de llamada del cliente HTTP son
corrientes -un legibles servidor lee las solicitudes y luego escribe las respuestas, mientras que un cliente escribe
primero una solicitud y luego lee

359
una respuesta. La lectura de una corriente se realiza mediante controladores de eventos, en lugar de los métodos.

Los objetos que emiten eventos en el Nodo tienen un método llamado en que es similar a la addEventListener
método en el navegador. Se le da un nombre de evento y luego una función, y se registrará que la
función que se llamará cuando se produzca el evento dado.

corrientes legibles tienen " datos" y " fin" eventos. El primero se dispara todos los datos en tiempo llega, y la
segunda es llamada cuando la corriente está en su extremo. Este modelo es el más adecuado para transmisión datos
que pueden ser procesados ​inmediatamente, incluso cuando todo el documento aún no está disponible. Un
archivo puede ser leído como una corriente legible mediante el uso de la createReadStream la función de fs.

Este código crea un servidor que lee cuerpos de solicitud y los flujos de vuelta al cliente como
todo-mayúsculas texto:

const {createServer} = require ( "http"); createServer ((solicitud,


respuesta) => {
response.writeHead (200, { "Content-Type": "text / plain"}); request.on ( "datos", trozo =>

response.write (chunk.toString () toUpperCase ().)); request.on ( "fin", () =>


Response.End ()); .}) Escuchar (8000);

los pedazo valor que se pasa al manejador de datos será un binario Buffer. Podemos convertir esto en una cadena
mediante la decodificación como UTF-8 caracteres codificados con su
Encadenar método.
El siguiente fragmento de código, cuando se ejecuta en el servidor uppercasing activa, enviará una
solicitud a ese servidor y escribe la respuesta se obtiene:

const {petición} = require ( "http"); solicitud({

nombre de host: "localhost", el puerto:


8000, método: "POST"}, respuesta => {

response.on ( "datos", trozo =>


process.stdout.write (chunk.toString ())); .}) Final ( "Hola servidor"); // →
HOLA SERVIDOR

El ejemplo se escribe a process.stdout ( la salida del proceso estándar, que es una corriente de escritura)
en lugar de utilizar console.log. No podemos utilizar console.log

360
ya que añade un carácter adicional nueva línea después de cada parte del texto que escribe, que no es
apropiado en este caso ya que la respuesta puede venir en forma de múltiples trozos.

Un servidor de archivos

Vamos a combinar nuestro nuevo conocimiento acerca de los servidores HTTP y el trabajo con el sistema de archivos para
crear un puente entre los dos: un servidor HTTP que permite el acceso remoto a un sistema de archivos. Dicho servidor
tiene todo tipo de usos, que permite que las aplicaciones web para almacenar y compartir datos, o puede dar a un grupo de
personas que comparten el acceso a un montón de archivos.

Cuando tratamos a los archivos como recursos HTTP, los métodos HTTP GET, PUT, y
ELIMINAR se puede utilizar para leer, escribir y borrar los archivos, respectivamente. Interpretaremos la ruta en la

solicitud como la ruta del archivo que se refiere la solicitud unimos así probablemente no quieren compartir

nuestro sistema de archivo completo, así que vamos a interpretar estos caminos como a partir de directorio de

trabajo del servidor, que es el directorio en el que se inició. Si me encontré con el servidor desde / tmp / public / ( o C:

\ tmp \ public \ en Windows), entonces una solicitud de / archivo.txt deben referirse a / / Public / tmp

. TXT ( o C: \ tmp \ public \ archivo.txt).


Vamos a construir la pieza a pieza del programa, utilizando un objeto llamado métodos para almacenar las funciones
que manejan los distintos métodos HTTP. manipuladores método son
asíncrono funciones que reciben la solicitud objeto como argumento y devuelven una promesa que se resuelve
en un objeto que describe la respuesta.

const {createServer} = require ( "http");

métodos const = Object.create (null);

createServer ((solicitud, respuesta) => {


dejar handler = métodos [request.method] || No permitido; Handler (petición)

. captura (error => {


si (error.status! = null) de error de retorno; volver {cuerpo: String (error),
estado: 500}; })

. entonces (({corporal, estado = 200, tipo = "text / plain"}) => {


response.writeHead (estado, { "Content-Type": Tipo}); si (cuerpo && body.pipe) body.pipe
(respuesta); Response.End otro (el cuerpo); });

.}) Escuchar (8000);

361
función async NOTALLOWED (request) {
regreso {
status: 405,
cuerpo: `Método $ {} request.method no allowed.`}; }

Así se inicia un servidor que sólo devuelve las respuestas de error 405, que es el código utilizado para
indicar que el servidor se niega a manejar un método dado.
Cuando se rechaza la promesa de un controlador de solicitudes, la captura llamada traduce el error en un
objeto de respuesta, si no lo es ya, por lo que el servidor puede devolver una respuesta de error para
informar al cliente que no pudo manejar la petición.
los estado campo de la descripción respuesta se puede omitir, en cuyo caso el valor predeterminado es 200
(OK). El tipo de contenido, en el tipo propiedad, también se puede dejar fuera, en cuyo caso se asume que la
respuesta sea texto plano.
Cuando el valor de cuerpo es una corriente de fácil lectura, que tendrá una tubo método que se utiliza para
reenviar todo el contenido de una corriente legible a una corriente de escritura. Si no es así, se supone que sea nulo
( ningún cuerpo), una cadena, o un tampón, y se pasa directamente a la respuesta de fin método.

Para determinar qué ruta del archivo corresponde a una URL de solicitud, el urlPath
función utiliza integrado del Nodo url módulo para analizar la URL. Toma su nombre de camino-, que será
algo así como "/ archivo.txt", decodifica que para deshacerse del% 20- códigos de escape estilo, y lo resuelve
con relación al directorio de trabajo del programa.

const {parse} = require ( "URL"); const {determinación, sep} =


require ( "camino");

const baseDirectory = process.cwd ();

función urlPath (url) {


sea ​{} = ruta de análisis (url);
dejar path = determinación (decodeURIComponent (ruta) .slice (1)); si (la ruta! = baseDirectory &&

! Path.startsWith (baseDirectory + SEP)) {{status tiro: 403, el cuerpo:


"Prohibido"}; }

vía de retorno; }

Tan pronto como se configura un programa para aceptar solicitudes de red, tiene que

362
empezar a preocuparse por la seguridad. En este caso, si no tenemos cuidado, lo más probable es que vamos a exponer

accidentalmente nuestro sistema de archivo completo a la red.

Las rutas de archivos son cadenas de Nodo. Para asignar una cadena como a un archivo real, hay una
cantidad no trivial de interpretación pasando. Caminos pueden, por ejemplo, incluir ../ para referirse a un
directorio padre. Así que una fuente obvia de problemas sería solicitudes de surcos como /../ secret_file.

Para evitar tales problemas, urlPath utiliza el resolver función de la camino


módulo, que resuelve rutas relativas. Se verifica entonces que el resultado es abajo
el directorio de trabajo. los process.cwd función (donde cwd significa “directorio de trabajo actual”) se puede
utilizar para encontrar esta copia de trabajo. los sep unión de la camino paquete es del sistema separador de
una ruta de barra invertida en Windows y una barra inclinada en la mayoría de los otros sistemas. Cuando
el camino no se inicia con el directorio base, la función y lanza un objeto respuesta de error, utilizando el
código de estado HTTP que indica que está prohibido el acceso al recurso.

Vamos a configurar el OBTENER Método para devolver una lista de archivos cuando se lee un directorio y para regresar el

contenido del archivo cuando se lee un archivo normal.

Una pregunta difícil es qué tipo de Tipo de contenido cabecera debemos fijar al devolver el contenido de un archivo. Dado que

estos archivos pueden ser cualquier cosa, nuestro servidor no puede simplemente devolver el mismo tipo de contenido para

todos ellos. NPM nos puede ayudar de nuevo aquí. los mímica indicadores de paquete (de tipo de contenido como Texto sin

formato También se les llama

tipos MIME) conoce el tipo correcto para un gran número de extensiones de archivo.
El seguimiento NPM mando, en el directorio donde reside el script del servidor, se instala una versión

específica de MIME: $ NPM instalar mime@2.2.0

Cuando no existe un archivo solicitado, el código de estado HTTP correcto para volver es 404.
usaremos el stat función, que busca información acerca de un archivo, para averiguar tanto si el archivo
existe y si se trata de un directorio.

const {createReadStream} = require ( "FS"); const {stat, readdir} = require ( "FS")


promesas.; mime const = require ( "mime");

methods.GET = función asíncrono (request) {


dejar path = urlPath (request.url); dejar que las
estadísticas; tratar {

Estadísticas = esperan stat (ruta de acceso);


} Catch (error) {
si (error.code = "ENOENT"!) Error de tiro;

363
o regrese {status: 404, el cuerpo: "Archivo no encontrado"}; }

si (stats.isDirectory ()) {
{retorno cuerpo: (esperar readdir (camino)) join ( "\ n").}; } Else {

volver {corporal: createReadStream (ruta),


Tipo: mime.getType (path)};
}};

Porque tiene que tocar el disco y por lo tanto podría tomar un tiempo, stat es asíncrona. Puesto que estamos
utilizando promesas en lugar de estilo de devolución de llamada, tiene que ser importados de promesas en lugar de
directamente desde fs.
Cuando no existe el archivo, stat lanzará un objeto de error con una código
propiedad de " ENOENT". Estos códigos, inspirados en Unix son un tanto oscuros cómo reconocer los tipos de
error en el Nodo.
los estadísticas objeto devuelto por stat nos dice varias cosas acerca de un archivo, tales como su
tamaño ( Talla propiedad) y su fecha de modificación ( -mtime propiedad). Aquí estamos interesados ​en la
cuestión de si se trata de un directorio o un archivo normal, que la isDirectory método nos dice.

Usamos readdir para leer el conjunto de archivos en un directorio y devolverlo al cliente. Para los
archivos normales, creamos un flujo legible con createReadStream
y volver que a medida que el cuerpo, junto con el tipo de contenido que el mímica paquete nos da para el nombre del
archivo.
El código para manejar ELIMINAR solicitudes es un poco más sencillo.

const {rmdir, desvincular} = require ( "FS") promesas.;

methods.DELETE = función asíncrono (request) {


dejar path = urlPath (request.url); dejar que las
estadísticas; tratar {

Estadísticas = esperan stat (ruta de acceso);


} Catch (error) {
si (error.code = "ENOENT"!) Error de tiro; o regrese {status: 204}; }

si (stats.isDirectory ()) esperan rmdir (ruta de acceso); más esperar unlink


(ruta de acceso); {status de retorno: 204}; };

364
Cuando una respuesta HTTP no contiene ningún dato, el código de estado 204 ( “sin contenido”) se
puede utilizar para indicar esto. Dado que la respuesta a la eliminación no necesita transmitir ninguna
información más allá de si la operación tuvo éxito, esto es una cosa sensible a volver aquí.

Es posible que se pregunte por qué intentar eliminar un archivo que no existe un código de estado
de éxito, en lugar de un error. Cuando el archivo que se va a eliminar no está ahí, se podría decir que
el objetivo de la solicitud ya se ha cumplido. El estándar HTTP nos anima a hacer peticiones idempotente,
lo que significa que hacer la misma petición varias veces produce el mismo resultado que lo que es
una vez. En cierto modo, si intenta eliminar algo que ya se ha ido, el efecto que estaba tratando de
hacer se ha logrado, la cosa ya no está allí.

Este es el controlador para PONER peticiones:

const {createWriteStream} = require ( "FS");

función Pipestream (de, a) {


volver nueva promesa ((resolver, rechazar) => {
from.on ( "error", rechazar); to.on ( "error",
rechazar); to.on ( "acabado", resolución);
from.pipe (a); }); }

methods.PUT = función asíncrono (request) {


dejar path = urlPath (request.url);
esperar Pipestream (request, createWriteStream (ruta de acceso)); {status de retorno:
204}; };

No necesitamos para comprobar si el archivo existe en esta ocasión, si lo hace, sólo tendremos que
sobrescribirlo. Utilizamos de nuevo tubo para mover datos de una corriente legible a una escritura, en este
caso de la solicitud al archivo. Pero desde tubo No está escrito para devolver una promesa, tenemos que
escribir un envoltorio, Pipestream, que crea una promesa en torno al resultado de llamar tubo.

Cuando algo va mal al abrir el archivo, createWriteStream seguirá siendo devolver una secuencia, pero esa
corriente se disparará una " error" evento. La corriente de salida a la solicitud también puede fallar, por
ejemplo, si la red se cae. Por lo que ambas corrientes de alambre hacia arriba " error" eventos para rechazar
la promesa. Cuando tubo se hace, se va a cerrar el flujo de salida, lo que hace que se dispare un " terminar" evento.
Ese es el punto en el que podemos resolver con éxito la promesa (nada de vuelta).

365
El guión completo para el servidor está disponible en https://eloquentjavascript.net/ código / file_server.js. Puede
descargar y que, después de instalar sus dependen- cias, ejecutarlo con el Nodo para iniciar su propio
servidor de archivos. Y, por supuesto, puede modificar y extenderlo a resolver ejercicios de este capítulo o de
experimentar.
La herramienta de línea de comandos rizo, ampliamente disponible en los sistemas (como MacOS y Linux) Unix, se
puede utilizar para realizar peticiones HTTP. La siguiente sesión de prueba brevemente nuestro servidor. Los - X opción se
utiliza para establecer el método de la solicitud, y
- d se utiliza para incluir un cuerpo de la solicitud.

$ Http rizo: // localhost: 8000 / archivo.txt archivo no encontrado

$ Rizo -X PONER -d hola http: // localhost: 8000 / archivo.txt $ rizo http: // localhost: 8000 /
archivo.txt hola

$ Encresparse -X BORRAR http: // localhost: 8000 / archivo.txt $ rizo http: //


localhost: 8000 / archivo.txt archivo no encontrado

La primera solicitud de archivo.txt falla ya que el archivo no existe aún. los PONER
solicitud crea el archivo, y he aquí, la siguiente solicitud recupera con éxito. Después de eliminar con
una ELIMINAR solicitud, el archivo es nuevo falta.

Resumen

Nodo es un pequeño sistema agradable, que nos permite ejecutar JavaScript en un con- texto nonbrowser. Fue
diseñado originalmente para tareas de red para jugar el papel de una nodo
en una red. Sin embargo, se presta a todo tipo de tareas de secuencias de comandos, y si la escritura JavaScript es algo
que disfrute, la automatización de tareas con el Nodo funciona bien.
NPM ofrece paquetes de todo lo que se pueda imaginar (y unas cuantas cosas que probablemente
nunca se le ocurriría), y que le permite a buscar e instalar los paquetes con el NPM programa. Nodo
viene con una serie de módulos integrados, incluyendo el fs módulo para trabajar con el sistema de
archivos y la http
Módulo para el funcionamiento de los servidores HTTP y hacer peticiones HTTP.
Toda la entrada y la salida en el nodo se realiza de forma asíncrona, a menos que utilice explícitamente una
variante sincrónica de una función, como por ejemplo readFileSync. Cuando se llama a las funciones asíncronas,
proporciona funciones de devolución de llamada, y el Nodo los llamará con un valor de error y (si está disponible)
resultado cuando está listo.

366
Ejercicios

Herramienta de búsqueda

En los sistemas Unix, hay una herramienta de línea de comandos llamada grep que se puede utilizar para buscar rápidamente

archivos de una expresión regular.

Escribir un guión de nodos que se pueden ejecutar desde la línea de comandos y actúa como lo algu- grep. Se trata
de su primer argumento de la línea de comandos como una expresión regular y trata a ningún otro argumento como
archivos a buscar. Se debe hacer salir los nombres de cualquier archivo cuyo contenido coincida con la expresión
regular.
Cuando funciona, extenderlo de manera que cuando uno de los argumentos es un directorio, se busca a través de
todos los archivos de ese directorio y sus subdirectorios.
Utilizar las funciones del sistema de archivos asíncronos o síncronos como mejor le parezca. Establecimiento de las
cosas para que varias acciones asíncronos se solicitan al mismo tiempo, podría acelerar las cosas un poco, pero no es una
cantidad enorme, ya que la mayoría de los sistemas de archivos pueden leer una sola cosa a la vez.

de creación del directorio

Aunque el ELIMINAR método en nuestro servidor de archivos es capaz de eliminar directorios (usando

rmdir), el servidor actualmente no proporciona ninguna manera a crear un directorio.


Añadir soporte para el MKCOL método ( “hacer que la colección”), que debe crear un directorio llamando mkdir
desde el fs módulo. MKCOL no es un método ampliamente utilizado HTTP, pero existe para este mismo fin en el WebDAV
, que especifica un conjunto de convenciones en la parte superior de HTTP que lo hacen adecuado para la
creación de documentos.

Un espacio público en la web

Desde el servidor de archivos sirve cualquier tipo de archivo, e incluso incluye la derecha Contenido
- Tipo encabezado, puede utilizarlo para servir a un sitio web. Ya que permite a todo el mundo para eliminar y
reemplazar los archivos, sería un interesante tipo de página web: uno que puede ser modificado, mejorado, y
destrozado por todo el mundo que se toma el tiempo para crear la solicitud HTTP derecha.

Escribir una página HTML básica que incluye un simple archivo JavaScript. Poner los archivos en un
directorio servido por el servidor de archivos y abrirlos en su navegador.
A continuación, como un ejercicio avanzado o incluso un proyecto de fin de semana, se combinan todo el
conocimiento adquirido de este libro para crear una interfaz más fácil de usar para modificar el sitio de dentro
el sitio web.
Utilice un formulario HTML para editar el contenido de los archivos que componen el sitio web, que permite al
usuario actualizar en el servidor mediante el uso de las peticiones HTTP, como

367
descrito en capítulo 18 .
Comience por hacer sólo un único archivo editable. A continuación, hacerlo de modo que el usuario puede seleccionar el archivo

que desea editar. Utilice el hecho de que nuestro servidor de archivos devuelve una lista de archivos cuando se lee un directorio.

No trabajar directamente en el código expuesto por el servidor de archivos ya que si se comete un


error, es probable que dañe los archivos allí. En su lugar, mantener su trabajo fuera del directorio de
acceso público y copiar allí durante la prueba.

368
“Si usted tiene conocimiento, deje otros encender sus velas en ella.”

- Margaret Fuller

capítulo 21

Proyecto: Habilidad-SharingWebsite

UNA habilidad para compartir reunión es un evento donde las personas con un interés común se unen y dan presentaciones

pequeñas, informales acerca de las cosas que conocen. En una reunión de intercambio de habilidades de jardinería, alguien

podría explicar cómo cultivar apio. O en un grupo de compartimiento de conocimientos de programación, que podría pasar por

allá y decirle a la gente acerca de Node.js.

Tales meetups-también a menudo llamadas los grupos de usuarios cuando están a punto Computadoras- son una
gran manera de ampliar su horizonte, aprender sobre los nuevos desarrollos, o simplemente conocer gente con
intereses similares. Muchas ciudades más grandes tienen meetups JavaScript. Por lo general son libres de asistir, y he
encontrado los que he visitado para ser amable y acogedor.

En este capítulo final del proyecto, nuestro objetivo es crear un sitio web para la gestión de charlas dadas en una
reunión de habilidades para compartir. Imagine un pequeño grupo de personas de reunirse regularmente en la
oficina de uno de los miembros para hablar sobre el monociclo. El organizador previo de las reuniones se trasladó a
otra ciudad, y nadie dio un paso adelante para hacerse cargo de esta tarea. Queremos un sistema que permitirá a
los participantes proponen y discuten las conversaciones entre sí, sin un organizador central.

El código completo para el proyecto se puede descargar desde código https://eloquentjavascript.net/ / skillsharing.zip.

Diseño

Hay un servidor parte de este proyecto, escrito para Node.js, y una cliente parte, escrita para el navegador. El
servidor almacena los datos del sistema y proporciona al cliente. También sirve los archivos que implementan
el sistema del lado del cliente.
El servidor mantiene la lista de las conversaciones propuestas para la próxima reunión, y el cliente muestra esta
lista. Cada charla tiene un nombre presentador, un título, un resumen, y una serie de comentarios asociados a ella. El
cliente permite a los usuarios proponen nuevas conversaciones (añadir a la lista), borrar las conversaciones, y
comentar sobre las conversaciones existentes. Cada vez que el usuario realiza un cambio tal, el cliente realiza una
petición HTTP para indicar al servidor de ello.

369
La aplicación se puede configurar para mostrar una En Vivo vista de la actual propone conversaciones y sus
comentarios. Cada vez que alguien, en algún lugar, presente una nueva conversación o añade un comentario, todas las
personas que tienen la página abierta en sus navegadores deben ver inmediatamente el cambio. Esto plantea un poco de
un desafío, no hay manera para que un servidor web para abrir una conexión con un cliente, ni tampoco existe una buena
manera de saber qué clientes están buscando actualmente a un determinado sitio web.

Una solución común a este problema se llama sondeo largo, el cual pasa a ser una de las
motivaciones para el diseño de Nodo.

Sondeo largo

Para poder notificar inmediatamente a un cliente que algo ha cambiado, necesitamos una conexión a ese
cliente. Dado que los navegadores web no aceptan tradicionalmente conexiones y los clientes son a menudo
detrás de los routers que bloquearían este tipo de conexiones de todos modos, que el servidor inicie esta
conexión no es práctico.
Podemos organizar para el cliente para abrir la conexión y mantenerlo alrededor de modo que el servidor puede
utilizar para enviar información cuando se necesita para hacerlo.
Pero una petición HTTP sólo permite un flujo de información sencillo: el cliente envía una solicitud, el
servidor vuelve con una sola respuesta, y eso es todo. Existe una tecnología llamada WebSockets, el apoyo
de los navegadores modernos, que hace posible la apertura de conexiones para el intercambio de datos
arbitraria. pero el uso de

370
de manera adecuada es un tanto complicado.

En este capítulo, se utiliza una simple técnica de sondeo largo, donde los clientes de forma continua pedir al
servidor de nueva información mediante solicitudes HTTP normales, y el servidor atasca su respuesta cuando no
tiene nada nuevo que informar.
Mientras el cliente se asegura de que constantemente tiene una solicitud de sondeo abierto, que va a recibir
información desde el servidor rápidamente después de que esté disponible. Por ejemplo, si Fatma tiene nuestra
aplicación de habilidades para compartir abierta en su navegador, el navegador que se habrá realizado una
petición de cambios y se espera de una respuesta a esa solicitud. Cuando Iman presenta una charla en declive
extrema Monociclo, el servidor se dará cuenta de que Fatma está a la espera de actualizaciones y enviar una
respuesta que contiene la nueva charla a su solicitud pendiente. navegador de Fatma recibirá los datos y actualizar
la pantalla para mostrar la charla.

Para evitar conexiones desde el tiempo de espera (ser abortado debido a la falta de actividad), las técnicas de
votación largos suelen establecer un tiempo máximo para cada solicitud, después de lo cual el servidor responderá
de todos modos, a pesar de que no tiene nada que informar, después de lo cual el cliente iniciar una nueva solicitud.
Periódicamente reiniciar la solicitud también hace que la técnica más robusta, permitiendo a los clientes a
recuperarse de los fallos en la conexión temporal o problemas en el servidor.

Un servidor ocupado que está utilizando sondeo largo puede tener miles de solicitudes en espera, y por lo
tanto las conexiones TCP abiertas. Nodo, lo que hace que sea fácil de manejar muchas conexiones sin crear
un hilo de control separado para cada uno, es una buena opción para un sistema de este tipo.

interfaz HTTP

Antes de empezar a diseñar el servidor o en el cliente, vamos a pensar en el punto donde se tocan: la
interfaz HTTP a través del cual se comunican.
Vamos a utilizar JSON como formato de nuestra petición y cuerpo de la respuesta. Al igual que en el servidor de
archivos desde capítulo 20 , Vamos a tratar de hacer un buen uso de los métodos HTTP y encabezados. La interfaz se
centra alrededor de la / negociaciones camino. Caminos que no comienzan con / negociaciones serán utilizados para servir
archivos estáticos-el código HTML y JavaScript para el sistema del lado del cliente.

UNA OBTENER solicitud de / negociaciones devuelve un documento JSON como esto:

[{ "Title": "Unituning",
"Presentador": "Jamal",
"Resumen": "La modificación de su ciclo para un toque de estilo", "comentarios": []}]

371
La creación de una nueva charla se realiza haciendo una PONER solicitar a una URL como / conversaciones / Unituning, donde
la parte después de la segunda barra es el título de la charla. los PONER
El cuerpo de la solicitud debe contener un objeto JSON que tiene presentador y resumen
propiedades.
Puesto que los títulos de entrevistas pueden contener espacios y otros caracteres que pueden no aparecer normalmente en una

dirección URL, cadenas de título deben ser codificados con el encodeURIComponent

función cuando la construcción de un enlace.

console.log ( "/ charlas /" + encodeURIComponent ( "Cómo Idle")); // → / charlas / Cómo% 20to% 20Idle

Una solicitud para crear una charla sobre ralentí podría ser algo como esto:

PUT / charlas / Cómo% 20to% 20Idle HTTP / 1.1


Content-Type: application / json Content-Length: 92

{ "Presentador": "Maureen",
"Resumen": "Quedarse quieto en un monociclo"}

Tales URLs también apoyan OBTENER peticiones para recuperar la representación JSON de una charla y ELIMINAR Las

solicitudes para eliminar una charla.

Añadir un comentario a una charla se realiza con una ENVIAR solicitar a una URL como /
conversaciones / Unituning / comentarios, con un cuerpo que tiene JSON autor y mensaje
propiedades.

Poste / charlas / Unituning / comentarios HTTP / 1.1 Content-Type:


application / json Content-Length: 72

{ "Autor": "Iman",
"Mensaje": "¿Quieres hablar de aumentar un ciclo?"}

Para apoyar sondeo largo, OBTENER solicitudes a / negociaciones puede incluir cabeceras adicionales que informan al servidor

para retrasar la respuesta si no hay nueva información disponible. Vamos a utilizar un par de colectores utilizados normalmente

para gestionar el almacenamiento en caché: ETag y

If-None-Match.
Los servidores pueden incluir una ETag ( “Etiqueta de entidad”) cabecea en una respuesta. Su valor es una
cadena que identifica la versión actual del recurso. Clientes, cuando más tarde solicitar ese recurso nuevo,
pueden hacer una solicitud condicional mediante la inclusión de una If-None-Match cabecera cuyo valor mantiene la
misma cadena. Si el recurso

372
no ha cambiado, el servidor responderá con el código de estado 304, lo que significa “sin modificar”, decirle al
cliente que su versión en caché sigue siendo actual. Cuando la etiqueta no coincide, el servidor responde con
normalidad.
Necesitamos algo como esto, donde el cliente puede indicar al servidor de la versión de la lista de
conversaciones que tiene, y el servidor responde sólo cuando esa lista ha cambiado. Pero en lugar de
inmediato devolver una respuesta 304, el servidor debe paralizar la respuesta y volver sólo cuando está
disponible algo nuevo o que haya transcurrido un período de tiempo determinado. Para distinguir las
solicitudes de sondeo largos de peticiones condicionales normales, les damos otra cabecera, Prefiero: espera =
90,

que le dice al servidor que el cliente está dispuesto a esperar hasta 90 segundos para la respuesta.

El servidor mantendrá un número de versión que se actualiza cada vez que cambian las conversaciones y usar eso
como el ETag valor. Los clientes pueden hacer demandas como éste para ser notificados cuando cambian las
conversaciones:

GET / conversaciones HTTP / 1.1


If-None-Match: "4" Prefiero:
esperan = 90

(el tiempo pasa)

HTTP / 1.1 200 OK


Content-Type: application / json ETag: "5"

Content-Length: 295

[....]

El protocolo descrito aquí no hace ningún control de acceso. Todo el mundo puede comentar, modificar
conversaciones, e incluso eliminarlos. (Dado que la Internet está llena de gamberros, poner tal sistema en
línea sin protección adicional probablemente no terminaría así.)

El servidor

Vamos a empezar por la construcción de la parte del lado del servidor del programa. El código de esta sección se ejecuta en

Node.js.

373
enrutamiento

Nuestro servidor utilizará createServer iniciar un servidor HTTP. En la función que se encarga de una nueva
solicitud, hay que distinguir entre los distintos tipos de solicitudes (según lo determinado por el método y la ruta
de acceso) que apoyamos. Esto se puede hacer con una larga cadena de Si declaraciones, pero hay una
manera más agradable.
UNA enrutador es un componente que ayuda a despachar una solicitud a la función que puede manejarlo.
Se puede decir que el router, por ejemplo, que PONER solicitudes con un camino que coincide con la expresión
regular / ^ \ / charlas \ / ([^ \ /] +) / ($ / charlas /
seguido de un título hablar) puede ser manejado por una función dada. Además, puede ayudar a extraer las
partes significativas de la trayectoria (en este caso, el título de conversación), envuelto en paréntesis en la
expresión regular, y pasarlos a la función de controlador.

Hay un buen número de paquetes del router en la NGP, pero aquí vamos a escribir uno de nosotros mismos
para ilustrar el principio.
Esto es router.js, que lo hará más adelante exigir de nuestro módulo de servidor:

const {parse} = require ( "URL");

module.exports = Router clase {


constructor () {
this.routes = []; }

añadir (método, URL, manejador) {


this.routes.push ({método, url, manejador}); }

resolver (contexto, la solicitud) {


dejar path = parse (request.url) .pathname;

para (sea {método, URL, manejador de this.routes}) {


deje partido url.exec = (ruta);
if (!! || partido request.method = método) continuará; dejar que urlParts = match.slice (1) .map
(decodeURIComponent); manejador de retorno (contexto, ... urlParts, petición); }

nula regresar; }};

El módulo exporta el Router clase. Un objeto enrutador permite que los nuevos manipuladores a estar
registrados en el añadir método y puede resolver las peticiones con su resolver
método.
Este último devolverá una respuesta cuando se encuentra un controlador, y nulo otro-

374
sabio. Se intenta el uno rutas a la vez (en el orden en el que fueron definidas) hasta que un juego uno se
encuentra.
Las funciones de controlador se denominan con el contexto valor (que será la instancia del servidor en nuestro
caso), las cadenas de coincidencia para ningún grupo se definen en su expresión regular, y el objeto de solicitud.
Las cadenas tienen que ser decodificado URL-desde la URL cruda puede contener% 20- códigos estilo.

servicio de archivos

Cuando una petición coincide con ninguno de los tipos de peticiones definidos en nuestro router, el servidor debe interpretar

como una petición de un archivo en el público directorio. Sería posible utilizar el servidor de archivos definido en capítulo 20 para

servir a este tipo de archivos, pero que no necesitan ni quieren apoyar PONER y ELIMINAR solicitudes de archivos, y nos

gustaría tener características avanzadas tales como soporte para el almacenamiento en caché. Así que vamos a utilizar un

servidor sólido, bien probado estática archivo desde la NGP en su lugar.

Yo he optado por extático. Este no es el único servidor en la NGP, pero funciona bien y se adapta a nuestros
propósitos. los extático paquete exporta una función que se puede llamar con un objeto de configuración para
producir una función de controlador de solicitudes. Usamos el raíz opción para indicar al servidor donde se debe
buscar los archivos. La función de controlador acepta solicitud y respuesta parámetros y se pueden pasar
directamente a createServer para crear un servidor que sirve solamente archivos. Queremos comprobar primero
para las peticiones que debemos manejar especialmente, aunque, por lo que lo envuelve en otra función.

const {createServer} = require ( "http"); const Router = require (


"enrutador ./"); const = éxtasis requieren ( "éxtasis");

enrutador const = new Router ();


defaultHeaders const = { "Content-Type": "text / plain"};

SkillShareServer clase {
constructor (charlas) {
this.talks = conversaciones;
this.version = 0; this.waiting = [];

dejar que servidorArchivos = éxtasis ({root: "./public"}); this.server = createServer


((solicitud, respuesta) => {
dejar resuelto = router.resolve (esto, petición); si (resueltos) {

resolved.catch (error => {


si (error.status! = null) de error de retorno;

375
volver {cuerpo: String (error), estado: 500}; }). Entonces (({cuerpo,

estado = 200,
cabeceras = defaultHeaders}) => {response.writeHead
(situación, cabeceras); Response.End (cuerpo); }); } Else {

servidorArchivos (solicitud, respuesta); }}); }

iniciar (puerto) {
this.server.listen (puerto); }

detener() {
this.server.close (); }}

Este utiliza una convención similar a la del servidor de archivos de la capítulo previo
de las respuestas de los manipuladores de promesas que están asociados a los objetos que describen la respuesta volver. Se

envuelve el servidor en un objeto que también mantiene su estado.

Conversaciones como recursos

Las conversaciones que se han propuesto se almacenan en el negociaciones propiedad del servidor, un objeto cuyos
nombres son propiedad de los títulos de entrevistas. Estos serán expuestos como recursos HTTP bajo / conversaciones /
[Título], así que tenemos que agregar controladores a nuestro router que implementan los diversos métodos que los

clientes pueden utilizar para trabajar con ellos.

El manejador para las peticiones que OBTENER una sola charla debe buscar la charla y responder ya sea con los
datos JSON de la charla o con una respuesta de error 404.

const talkPath = / ^ \ / conversaciones \ / ([^ \ /] +) $ /;

router.add ( "GET", talkPath, asíncrono (servidor, título) => {


si (título en server.talks) {
volver {cuerpo: JSON.stringify (server.talks [title]),
encabezados: { "Content-Type": "application / json"}};
} Else {
{status de retorno: 404, cuerpo: `No hablar '$ {title}' found`}; }

376
});

Eliminación de una conversación se realiza mediante la eliminación de él del negociaciones objeto.

router.add ( "BORRAR", talkPath, asíncrono (servidor, título) => {


si (título en server.talks) {
eliminar server.talks [Título]; server.updated ();
}

{status de retorno: 204}; });

los actualizado método, que definiremos más tarde , Notifica a la espera de sondeo largo
las solicitudes sobre el cambio.
Para recuperar el contenido de un cuerpo de solicitud, se define una función llamada ReadStream
, el que lee todo el contenido de una corriente legible y devuelve una promesa que se resuelve en una cadena.

función ReadStream (corriente) {


volver nueva promesa ((resolver, rechazar) => {
dejar que los datos = "";

stream.on ( "error", rechazar);


stream.on ( "datos", trozo => datos + = chunk.toString ()); stream.on ( "fin", () => resolución
(datos)); }); }

Un controlador que necesita leer cuerpos de solicitud es el PONER manipulador, que se utiliza para crear
nuevas conversaciones. Tiene que comprobar si los datos que se le dio cuenta presentador y resumen propiedades,
que son cadenas. Cualquier dato que vienen de fuera del sistema podrían ser sin sentido, y que no quieren
corromper nuestro modelo interno de datos o un accidente cuando las solicitudes malas vienen en.

Si los datos se parece válido, el controlador almacena un objeto que representa la nueva charla en el negociaciones

objeto, posiblemente sobrescribir una charla existente con este título, y de nuevo las llamadas actualizado. router.add (

"PUT", talkPath,

asíncrono (servidor, título, petición) => {


dejar requestBody = esperan ReadStream (petición); hablemos;

try {charla = JSON.parse (requestBody); }


captura (_) {return {status: 400, el cuerpo: "No válido JSON"}; }

377
if (! hablar ||
typeof talk.presenter! = "cadena" || ! Typeof talk.summary = "cadena")
{return {status: 400, el cuerpo: "Bad datos charla"}; }

server.talks [title] = {título,


presentador: talk.presenter, Resumen:
talk.summary, las observaciones: []};

server.updated (); {status de retorno:


204}; });

Añadir un comentario a una charla funciona de manera similar. Usamos ReadStream para obtener el contenido de la
solicitud, validar los datos resultantes, y almacenarlo como un comentario cuando parece válida.

router.add ( "POST", / ^ \ / charlas \ / ([^ \ /] +) \ / Comentarios $ /,


asíncrono (servidor, título, petición) => {
dejar requestBody = esperan ReadStream (petición); dejar comentarios;

try {comment = JSON.parse (requestBody); }


captura (_) {return {status: 400, el cuerpo: "No válido JSON"}; }

if (! comentar ||
typeof comment.author! = "cadena" || typeof comment.message = "cadena")
{return {status: 400, el cuerpo: "comentario datos malos"}; } Else if (título en
server.talks) {

server.talks [title] .comments.push (comentario); server.updated (); {status


de retorno: 204}; } Else {

{status de retorno: 404, cuerpo: `No hablar '$ {title}' found`}; }});

Se trata de añadir un comentario a una charla inexistente devuelve un error 404.

378
la compatibilidad del sondeo de largo

El aspecto más interesante del servidor es la parte que se encarga de sondeo largo. Cuando una OBTENER petición
llega a / negociaciones, que puede ser o bien una solicitud regular o una solicitud de sondeo de largo.

Habrá múltiples lugares en los que tenemos que enviar una serie de conversaciones para el cliente, por lo que
primero definir un método de ayuda que se acumula una gama tan amplia e incluye una ETag cabecera en la
respuesta.

SkillShareServer.prototype.talkResponse = function () {
dejar que habla = [];
para (dejar título de Object.keys (this.talks)) {
talks.push (this.talks [title]); }

regreso {
cuerpo: JSON.stringify (charlas),
encabezados: { "Content-Type": "application / json",
"ETag": ` "$ {} this.version"`}
}; };

El manejador en sí necesita mirar las cabeceras de petición para ver si IfNone-Match y Preferir cabeceras
están presentes. nodo almacena cabeceras, cuyos nombres son especificados como sensible a mayúsculas,
minúsculas bajo sus nombres.

router.add ( "GET", / ^ \ / habla $ /, asíncrono (servidor, petición) => {


dejar tag = /"(.*)"/.exec(request.headers["if-none-match "]); dejar que espera =
/\bwait=(\d+)/.exec(request.headers["prefer "]); if (! || etiqueta etiqueta [1]! = server.version) {

server.talkResponse retorno (); } Else if (! Espera) {

{status de retorno: 304}; } Else {

server.waitForChanges retorno (Número (esperar [1])); }});

Si se le dio ninguna etiqueta o una etiqueta fue dado que no coincide con la versión actual del servidor, el
controlador responde con la lista de las conversaciones. Si la solicitud es condicional y las conversaciones no
cambió, consultamos la Preferir cabecera para ver si hay que retrasar la respuesta o responder de inmediato.

funciones de devolución de llamada para las solicitudes tardías se almacenan en el servidor de esperando Ar- ray
para que puedan ser notificados cuando sucede algo. los waitForChanges

379
método también establece inmediatamente un cronómetro para responder con un estado 304 cuando la solicitud ha
esperado lo suficiente.

SkillShareServer.prototype.waitForChanges = función (tiempo) {


volver nueva promesa (resolución => {
this.waiting.push (resolver); setTimeout (() => {

si el retorno (this.waiting.includes (resolver)!); this.waiting = this.waiting.filter (r => r =


determinación!); determinación ({status: 304}); }, El tiempo * 1000); }); };

El registro de un cambio con actualizado aumenta la versión propiedad y se despierta todas las solicitudes en
espera.

SkillShareServer.prototype.updated = function () {
this.version ++;
dejar que la respuesta = this.talkResponse ();
this.waiting.forEach (resolver => resolución (respuesta)); this.waiting = []; };

Con esto concluye el código del servidor. Si creamos una instancia de SkillShareServer
e iniciarlo en el puerto 8000, el servidor HTTP resultante sirve archivos de la público
subdirectorio junto con una interfaz de gestión de entrevistas en el directorio / negociaciones URL.

. SkillShareServer nueva (Object.create (nulo)) comienzo (8000);

El cliente

La parte del lado del cliente de la página web de intercambio de habilidad consiste en tres archivos: una pequeña página HTML,

hojas de estilo, y un archivo JavaScript.

HTML

Es una convención ampliamente utilizado para los servidores web para tratar de servir a un archivo llamado

index.html cuando se hace una petición directamente a un camino que corresponde a un directorio. El módulo de

servidor de archivos que utilizamos, extático, apoya esta convención.

380
Cuando se hace una solicitud a la ruta /, el servidor busca el archivo ./ pública / index.html (./public siendo
la raíz que le dimos) y devuelve ese archivo si lo encuentra.
Por lo tanto, si queremos una página para mostrar cuando un navegador se señaló en nuestro servidor, debemos ponerlo

en pública / index.html. Este es nuestro archivo de índice:

<! DOCTYPE html> <meta charset = "UTF-8">


<title> Habilidad Sharing </ title>

<Link rel = "stylesheet" href = "skillsharing.css">

<H1> Habilidad Sharing </ h1>

<Script src = "skillsharing_client.js"> </ script>

Se define el título del documento e incluye una hoja de estilo, que define un par de estilos para, entre otras
cosas, asegurarse de que hay algo de espacio entre conversaciones.
En la parte inferior, se añade un encabezado en la parte superior de la página y carga el script que contiene la
aplicación del lado del cliente.

Comportamiento

El estado de la aplicación consiste en la lista de las conversaciones y el nombre del usuario, y vamos a almacenarla en
un { conversaciones, el usuario} objeto. No permitimos que la interfaz de usuario manipular directamente el estado o expulsar
a las peticiones HTTP. Más bien, se puede emitir
comportamiento que describen lo que el usuario está tratando de hacer.

los handleAction función toma tal acción y hace que suceda. BE- causan nuestras actualizaciones de
estado son tan simples, cambios de estado se manejan de la misma función.

función handleAction (estado, acción) {


si (== action.type "setUser") {
localStorage.setItem ( "nombre de usuario", action.user); volver Object.assign ({}, estado,
{user: action.user}); } Else if (action.type == "") {setTalks

volver Object.assign ({}, {estatales, las conversaciones: action.talks}); } Else if (action.type ==


"NewTalk") {
fetchOK (talkURL (action.title), {
Método: "PUT",
encabezados: { "Content-Type": "application / json"}, cuerpo: JSON.stringify ({

presentador: state.user, Resumen:


action.summary})

381
.}) Coger (ReportError);
} Else if (action.type == "deleteTalk") {
fetchOK (talkURL (action.talk), {método: "DELETE"})
. captura (ReportError);
} Else if (action.type == "newComment") {
fetchOK (talkURL (action.talk) + "/ comentarios", {
Método: "POST",
encabezados: { "Content-Type": "application / json"}, cuerpo: JSON.stringify ({

autor: state.user, mensaje:


action.message})

.}) Coger (ReportError); }

Estado de retorno; }

Almacenaremos el nombre del usuario en almacenamiento local de modo que pueda ser restaurada cuando se carga la página.

Las acciones que es necesario involucrar a las solicitudes de red del servidor fabrica, utilizando
ir a buscar, al HTTP interfaz descrito anteriormente. Utilizamos una función de contenedor,

fetchOK, lo que hace que la promesa de regresar se rechaza cuando el servidor devuelve un código de

error.

funcionar fetchOK (URL, opciones) {


volver Se fetch (url, opciones) .then (respuesta => {
si (Response.Status <400) de respuesta de retorno; otra cosa arrojar nueva
error (response.statusText); }); }

Esta función auxiliar se utiliza para construir una URL para una charla con un título dado.

función talkURL (título) {


retorno "habla /" + encodeURIComponent (título); }

Cuando la solicitud no, no queremos tener nuestra página simplemente sentarse ahí, sin hacer nada sin
explicación. Así se define una función llamada ReportError, los cuales al menos muestra al usuario un cuadro de
diálogo que les dice algo salió mal.

función ReportError (error) {

382
alerta (String (error)); }

componentes de representación

Vamos a utilizar un enfoque similar a la que vimos en capítulo 19 , La división de la aplicación en componentes. Sin
embargo, dado que algunos de los componentes ya sea nunca tiene que actualizar o son siempre presentaban
completamente cuando se actualiza, definiremos los no como clases, sino como funciones que devuelven
directamente a un nodo DOM. Por ejemplo, aquí es un componente que muestra el campo donde el usuario puede
introducir su nombre:

función renderUserField (nombre, expedición) {


elt volver ( "etiqueta", {}, "Su nombre:", ELT ( "entrada", {
Tipo: "texto", valor: nombre,
onchange (evento) {

despacho ({tipo: "setUser", el usuario: event.target.value}); }})); }

los elt función utilizada para construir elementos DOM es la que utilizamos en
capítulo 19 .
Una función similar es usado para generar conversaciones, que incluyen una lista de comentarios y un formulario para
añadir un comentario nuevo.

función renderTalk (hablar, expedición) {


elt volver (
"Sección", {Nombre de clase: "hablar"},
ELT ( "h2", null, talk.title, "", ELT ( "botón", {
escribir: "botón", onclick ()
{
despacho ({type: "deleteTalk", charla: talk.title}); }

}, "Eliminar")),
ELT ( "div", null, "por",
ELT ( "fuerte", null, talk.presenter)), ELT ( "p", null,
talk.summary),
. . . talk.comments.map (renderComment), ELT ( "forma", {

onsubmit (evento) {
event.preventDefault ();

383
dejar que forma = event.target; despacho ({type:
"newComment",
hablar: talk.title,
mensaje: form.elements.comment.value});
form.reset (); }

}, ELT ( "entrada", {type: "texto", nombre: "comentar"}), "",


ELT ( "botón", {type: "enviar"}, "Añadir comentario"))); }

Los " enviar" llamadas de controlador de eventos form.reset para borrar el contenido de la forma después de la creación de un " nuevo

comentario" acción.

Cuando la creación de piezas de complejidad moderada de DOM, este estilo de programa- ción empieza a verse más bien
desordenado. Hay una extensión (no estándar) JavaScript ampliamente utilizada llamada JSX que le permite escribir HTML
directamente en las secuencias de comandos, que pueden hacer dicho código más bonito (dependiendo de lo que se considera
bastante). Antes de que realmente puede ejecutar dicho código, usted tiene que ejecutar un programa en la secuencia de
comandos para convertir el pseudo-HTML en JavaScript función llama al igual que los que utilizamos aquí.

Los comentarios son más fáciles de representar.

función renderComment (comentario) {


ELT ( "p", volver {Nombre de clase: "comentar"},
ELT ( "fuerte", null, comment.author), ":", comment.message);

Por último, la forma que el usuario puede utilizar para crear una nueva charla aparece así:

función renderTalkForm (despacho) {


dejar title = ELT ( "input", {type: "texto"}); Resumen dejar = ELT ( "entrada",
{type: "texto"}); volver "forma" elt (, {

onsubmit (evento) {
event.preventDefault (); despacho ({type:
"NewTalk",
Título: title.value, Resumen:
summary.value});
event.target.reset (); }

}, ELT ( "H3", null, "Enviar un Talk"),


ELT ( "etiqueta", null, "Título:", título),

384
ELT ( "etiqueta", null, "Resumen:", resumen), ELT ( "botón", {type: "enviar"},
"Enviar")); }

Votación

Para iniciar la aplicación que necesitamos la lista actual de las negociaciones. Dado que la carga inicial está estrechamente relacionado

con el sondeo largo proceso de la ETag de la carga debe ser utilizado cuando el sondeo-nosotros escribir una función que mantiene el

sondeo del servidor para / negociaciones y pide una función de devolución de llamada cuando una nueva serie de conversaciones está

disponible.

pollTalks función asíncrona (actualización) {


dejar tag = indefinido; para (;;) {

dejar que la respuesta;

tratar {

= Respuesta esperan fetchOK ( "/ conversaciones", {


encabezados: Etiqueta && { "If-None-Match": etiqueta,
"Prefiero": "espere = 90"}
});
} Catch (e) {
console.log ( "Error en la solicitud:" + e);
la espera de nueva Promise (resolución => setTimeout (resolución, 500)); Hacer continuación; }

si (== Response.Status 304) siguen; tag = response.headers.get


( "ETag"); actualización (esperar response.json ()); }}

Esto es un asíncrono de manera que la función de bucle y esperar a que la solicitud es más fácil. Se ejecuta un bucle
infinito que, en cada iteración, recupera la lista de conversaciones, ya sea normal o, si esto no es la primera petición, con
los encabezados incluidos que hacen que sea una solicitud de sondeo de largo.

Cuando una petición falla, la función espera un momento y luego intenta de nuevo. De esta manera, si
la conexión a la red desaparece por un tiempo y luego regresa, la aplicación puede recuperar y continuar
actualizando. La promesa resuelta a través
setTimeout es una manera de forzar la asíncrono la función de espera.
Cuando el servidor devuelve una respuesta 304, lo que significa una solicitud de sondeo de largo tiempo de espera, por lo

que la función solo debería comenzar inmediatamente la siguiente solicitud. Si

385
la respuesta es una respuesta normal 200, su cuerpo se lee como JSON y se pasa a la devolución de llamada, y su ETag valor
de encabezado se almacena para la siguiente iteración.

La aplicación

El siguiente componente ata toda la interfaz de usuario junto:

SkillShareApp clase {
constructor (estado, expedición) {
this.dispatch = expedición;
this.talkDOM = ELT ( "div", {Nombre de clase: "habla"}); this.dom = ELT ( "div", null,

renderUserField (state.user, expedición), this.talkDOM,

renderTalkForm (despacho));
this.syncState (estado); }

SyncState (estado) {
if (! = state.talks this.talks) {
this.talkDOM.textContent = ""; para (por no hablar de
state.talks) {
this.talkDOM.appendChild (
renderTalk (hablar, this.dispatch)); }

this.talks = state.talks; }}}

Cuando las conversaciones sobre el cambio, este componente se vuelve a dibujar todos ellos. Esto es simple, sino también un

desperdicio. Nos pondremos en contacto a la de los ejercicios.

Podemos iniciar la aplicación de la siguiente manera:

funcionar EjecutarAplicación () {

permitir al usuario = localStorage.getItem ( "nombre de usuario") || "Luego"; deje estado,


aplicación;
función de despacho (acción) {
estado = handleAction (estado, la acción); app.syncState
(estado); }

pollTalks (charlas => {


if (! app) {
STATE = {user, charlas};

386
aplicación = new SkillShareApp (estado, expedición);
document.body.appendChild (app.dom); } Else {

despacho ({type: "setTalks", charlas}); }

.}) Coger (ReportError); }

EjecutarAplicación ();

Si ejecuta el servidor y abrir dos ventanas del navegador para http: // localhost: 8000
uno junto al otro, se puede ver que las acciones que se realizan en una sola ventana son inmediatamente
visibles en la otra.

Ejercicios

Los siguientes ejercicios implicarán la modificación del sistema definido en este Ca- pítulo. Para trabajar en ellos,
asegúrese de descargar el código primero ( https://eloquentjavascript.net/ código / skillsharing.zip), han instalado Nodo https:/
y tienen INSTALADO de dependencia del proyecto con NPM instalar.

la persistencia de disco

El servidor de intercambio de habilidades mantiene sus datos puramente en la memoria. Esto significa que cuando se rompe o se

reinicie por cualquier motivo, todas las conversaciones y los comentarios se pierden.

Extracción del servidor para que almacene los datos de conversación en el disco y vuelve a cargar
automáticamente los datos cuando se reinicie. No se preocupe por la eficiencia en hacer lo más simple que funciona.

restablece campo de comentarios

El nuevo trazado por mayor de conversaciones funciona bastante bien, ya que por lo general no se puede decir la
diferencia entre un nodo DOM y una de repuesto idéntica. Sin embargo, hay excepciones. Si comienza a escribir
algo en el campo de comentarios para una charla en una ventana del navegador y, a continuación, en otra, dejar
un comentario a esa charla, se vuelve a dibujar el campo en la primera ventana, la eliminación de su contenido y
su enfoque.

En una acalorada discusión, donde varias personas están agregando comentarios, al mismo tiempo,
esto sería molesto. Se puede llegar a una forma de solucionarlo?

387
Consejos de ejercicio

Las sugerencias a continuación podrían ayudar cuando le pegan con uno de los ejercicios de este libro.
Ellos no regalan toda la solución, sino que tratan de ayudar a encontrar por sí mismo.

Estructura del programa

Looping un triángulo

Puede comenzar con un programa que imprime los números 1 a 7, en el que se pueden derivar haciendo
algunas modificaciones en el incluso número de ejemplo imprimir
dada anteriormente en este capítulo, en el que el para bucle se introdujo.
Consideremos ahora la equivalencia entre números y cadenas de picadillo tros ticas. Se puede ir de 1
a 2 mediante la adición de 1 (+ = 1). Se puede ir de "#" para
"##" mediante la adición de un carácter (+ = "#"). Por lo tanto, su solución puede seguir de cerca el programa número de

impresión.

FizzBuzz

Repasando los números es claramente un trabajo de bucle, y seleccionar lo que desea imprimir es una
cuestión de ejecución condicional. Recuerde que el truco de usar el (%) restante del operador para
comprobar si un número es divisible por otro número (tiene un resto de cero).

En la primera versión, hay tres resultados posibles para cada número, por lo que tendrá que crear
una if / else if / else cadena.
La segunda versión del programa tiene una solución sencilla y uno inteligente. La solución más sencilla
es añadir otra “rama” condicional para probar precisamente la condición dada. Para la solución inteligente,
construir una cadena que contiene la palabra o palabras para la producción e impresión, ya sea esta
palabra o el número si no hay una palabra, potencialmente haciendo un buen uso de la || operador.

388
Tablero de ajedrez

Usted puede construir la cadena comenzando con una vacía ( "") y en repetidas ocasiones la adición de caracteres.
Un carácter de nueva línea se escribe "\ norte".
Para trabajar con dos dimensiones, necesitará un bucle dentro de un bucle. Ponga los apoyos en torno a los
cuerpos de los dos lazos para que sea fácil de ver dónde empiezan y terminan. Trate de sangrar adecuadamente
estos cuerpos. El orden de los bucles debe seguir el orden en que construimos la cadena (línea a línea, de izquierda
a derecha y de arriba a abajo). Por lo que el bucle externo se encarga de las líneas, y el bucle interno se encarga de
los caracteres en una línea.

Usted necesitará dos fijaciones para seguir su progreso. Para saber si poner un espacio o un símbolo de
sostenido en una posición dada, se puede comprobar si la suma de los dos contadores es par (% 2).

Terminación de una línea añadiendo un carácter de nueva línea debe ocurrir después de la línea se ha construido, por lo que

hacer esto después de que el bucle interno, pero en el interior del bucle externo.

funciones

Mínimo

Si tiene problemas para poner llaves y paréntesis en el lugar correcto para obtener una definición de
función válida, empezar por copiar uno de los ejemplos de este capítulo y modificándolo.

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

recursividad

Su función es probable que mire algo similar a lo interno encontrar función en el recursiva encontrar solución ejemplo
en este capítulo, con una if / else if / else cadena que pone a prueba cuál de los tres casos se aplica. El
final más, correspondiente al tercer caso, hace la llamada recursiva. Cada una de las ramas debe contener
una regreso declaración o de alguna otra manera los arreglos para que se devuelva un valor específico.

Cuando se le dé un número negativo, la función recursiva y otra vez, haciéndose pasar por un
número cada vez más negativa, por lo tanto conseguir más y más lejos de devolver un resultado. Con
el tiempo se queda sin espacio de pila y abortar.

389
conteo de porotos

Su función será necesario un bucle que se ve en cada carácter de la cadena. Se puede ejecutar un índice de
cero a uno por debajo de su longitud (< longitud de la cuerda). Si el carácter en la posición actual es el mismo que
el que la función está buscando, se añade 1 a una variable de contador. Una vez que el bucle ha finalizado,
el contador puede ser devuelto.

El máximo cuidado en todos los enlaces utilizados en la función local a la función adecuadamente por la que
se declara con la dejar o const palabra clave.

Estructuras de datos: Objetos y matrices

La suma de un rango

La construcción de una matriz se hace más fácilmente inicializar una primera unión a [] (una matriz fresca,
vacío) y en varias ocasiones llamando a su empujar método para añadir un valor. No se olvide de devolver la
matriz al final de la función.
Desde el límite final es incluido, tendrá que utilizar el operador <= en lugar de < para comprobar si
hay al final de su ciclo.
El parámetro de paso puede ser un parámetro opcional que los valores predeterminados (usando el operador =) a 1.

Teniendo distancia entender los valores negativos paso es probablemente el mejor hecho ESCRITO ing dos
bucles separados: uno para contar hacia arriba y uno para el recuento de Down- porque la comparación que
comprueba si el bucle se termina necesidades de ser> = en lugar de <= al contar hacia abajo.

También podría ser que vale la pena utilizar un paso diferente por defecto, es decir, -1, cuando el extremo de
la gama es más pequeño que el principio. De esa manera, rango (5, 2) re resulta algo significativo, en lugar de
quedarse atascado en un bucle infinito. Es posible referirse a los parámetros anteriores en el valor
predeterminado de un parámetro.

Inversión de una matriz

Hay dos maneras obvias para poner en práctica reverseArray. El primero es ir simplemente sobre la
matriz de entrada de adelante hacia atrás y el uso de la unshift método en la nueva matriz para insertar
cada elemento en su inicio. El segundo es un bucle sobre la matriz de entrada hacia atrás y el uso de
la empujar método. Iterar sobre una matriz hacia atrás requiere un (algo torpe) para especificación, como
( Sea I = Array.length - 1; i> = 0; yo--).

La inversión de la matriz en lugar es más difícil. Usted tiene que tener cuidado de no sobreescribir los elementos
que más tarde necesitar. Utilizando reverseArray o de otra manera copiar

390
todo el array ( Array.slice (0) es una buena manera de copiar una matriz) funciona, pero es trampa.

El truco consiste en intercambiar los primeros y últimos elementos, entonces la segunda y de segunda a la
última, y ​así sucesivamente. Usted puede hacer esto haciendo un bucle en más de la mitad de la longitud de la
matriz (uso Math.floor para redondear hacia abajo que no es necesario tocar el elemento medio de una matriz con un
número impar de elementos) e intercambiando el elemento en la posición yo con el que está en posición Array.length -
1 - i. Puede utilizar una unión local para mantener brevemente a uno de los elementos, sobrescribir que uno con su

imagen especular, y luego poner el valor de la unión local en el lugar donde la imagen de espejo que solía ser.

Una lista

La creación de una lista es más fácil cuando se hace al revés. Entonces arrayToList ITER podría comieron más de la
matriz hacia atrás (véase el ejercicio anterior) y, para cada elemento, añadir un objeto a la lista. Puede utilizar una
unión local para sostener la parte de la lista que fue construido hasta el momento y utilizar como una asignación list =
{valor: X, resto: lista} para agregar un elemento. Para ejecutar más de una lista (en listToArray y n-ésima), una para especificación

bucle como este puede ser usado:

para (dejar nodo = lista; nodo; nodo = node.rest) {}

¿Puede usted ver cómo funciona? Cada iteración del bucle, nodo apunta a la lista secundaria actual, y
el cuerpo puede leer su valor propiedad para obtener el elemento actual. Al final de una iteración, nodo mueve
a la siguiente lista secundaria. Cuando ese es nulo, hemos llegado al final de la lista, y el bucle se
termina.
La versión recursiva de enésima será, de manera similar, mirar a una parte cada vez más pequeña de la “cola” de
la lista y, al mismo tiempo contar el índice hasta que llegue a cero, momento en el que se puede devolver el valor propiedad
del nodo que está mirando. Para obtener el elemento cero de una lista, sólo tiene que tomar el valor propiedad de su
nodo de cabeza. Para conseguir elemento N + 1, se toma el norte ésimo elemento de la lista que está en esta lista de
descanso propiedad.

comparación de profundidad

Su prueba de si se trata de un objeto real quedará del siguiente modo


typeof x == "objeto" && x! = null. Tenga cuidado al comparar las propiedades sólo cuando ambos argumentos son
objetos. En todos los demás casos, basta con volver inmediatamente el resultado de aplicar ===.

391
Utilizar Object.keys para repasar las propiedades. Es necesario comprobar si los dos objetos tienen el mismo
conjunto de nombres de propiedades y si esas propiedades tienen valores idénticos. Una forma de hacerlo es
asegurarse de que ambos objetos tienen el mismo número de propiedades (las longitudes de las listas de
propiedades son los mismos). Y entonces, cuando un bucle sobre una de las propiedades del objeto de comparar
ellos, siempre en primer lugar asegúrese de que el otro tiene en realidad una propiedad con ese nombre. Si tienen
el mismo número de propiedades y todas las propiedades en un solo existe también en la otra, tienen el mismo
conjunto de nombres de propiedades.

Devolver el valor correcto de la función es el mejor hecho por regresar inmediatamente falsa cuando se
encuentra una falta de coincidencia y devolver cierto al final de la función.

Las funciones de orden superior

Todo

Como el && operador, el cada método puede dejar de evaluar otros elementos tan pronto como se ha
encontrado uno que no coincide. Por lo que la versión basada en bucle puede saltar fuera del bucle con rotura o
regreso -Como pronto como se ejecuta en un elemento para el que la función predicado devuelve falso. Si el

bucle se ejecuta a su fin sin encontrar un elemento tal, sabemos que todos los elementos coincidentes y que
deberíamos volver realidad.

Para construir cada encima de algunos, podemos aplicar las leyes de De Morgan, que establecen que a && b es
igual! (! a || !si). Esto se puede generalizar a arrays, donde todos los elementos en el partido matriz si no hay
un elemento de la matriz que no coincide.

dirección de escritura dominante

Su solución podría parecerse mucho a la primera mitad de la textScripts ejemplo. De nuevo tiene que
contar caracteres por un criterio basado en characterScript
y luego filtrar la parte del resultado que se refiere a los caracteres no interesantes (escritura-menos).

Encontrar la dirección con el más alto recuento de caracteres se puede hacer con
reducir. Si no está claro cómo, consulte el ejemplo anteriormente en este capítulo, en donde
reducir se utilizó para encontrar el guión con el mayor número de caracteres.

392
La vida secreta de los objetos

Un tipo de vector

Mirar hacia atrás a la Conejo ejemplo de la clase si no está seguro de cómo clase declaraciones se ven.

La adición de una propiedad getter al constructor puede hacerse poniendo la palabra


obtener antes de que el nombre del método. Para calcular la distancia a partir de (0, 0) a (x, y), puede
utilizar el teorema de Pitágoras, que dice que el cuadrado de la distancia que estamos buscando es
igual al cuadrado de la coordenada X más el cuadrado de la coordenada y. Así, √ X 2 + y 2 es el número que
desea, y math.sqrt es la forma de calcular una raíz cuadrada en JavaScript.

grupos

La forma más sencilla de hacer esto es para almacenar una serie de miembros del grupo en una propiedad de
instancia. los incluye o índice de métodos se pueden utilizar para comprobar si un valor dado está en el array.

constructor de su clase puede establecer la colección miembro de una matriz vacía. Cuando añadir se
llama, se debe comprobar si el valor dado es de la matriz o agregarlo, por ejemplo, con empujar, de otra
manera.
Eliminación de un elemento de una matriz, en Eliminar, es menos sencillo, pero se puede usar filtrar para
crear una nueva matriz sin valor. No se olvide de sobrescribir la propiedad que sostiene los miembros
con la nueva versión filtrada de la matriz.

los de método puede utilizar una para / de bucle para obtener los valores del objeto iterable y llamada añadir para
ponerlos en un grupo recién creado.

grupos iterables

Es probable que valga la pena para definir una nueva clase GroupIterator. posturas iterador in- deben
tener una propiedad que rastrea la posición actual en el grupo. Cada vez próximo se llama, se
comprueba si se hace y, si no, se mueve más allá del valor actual y lo devuelve.

los Grupo clase en sí consigue un método llamado por Symbol.iterator que, cuando se le llama, devuelve una
nueva instancia de la clase de iterador para ese grupo.

El préstamo de un método

Recuerde que los métodos que existen en los objetos lisos provienen de Object.prototype
.

393
También recuerde que usted puede llamar a una función específica con una esta unión mediante el uso de su llamada método.

Proyecto: un robot

La medición de un robot

Vas a tener que escribir una variante de la RunRobot función que, en lugar de log ging los eventos a la
consola, devuelve el número de pasos que el robot tomó para completar la tarea.

Su función de medición puede entonces, en un bucle, generar nuevos estados y contar los pasos que
cada uno de los robots de toma. Cuando se ha generado suficiente surements Measure, se puede utilizar console.log
a la salida de la media para cada robot, que es el número total de medidas tomadas dividido por el número
de mediciones.

eficiencia del robot

La principal limitación de goalOrientedRobot es que considera sólo una parcela a la vez. A menudo
caminar hacia atrás y adelante a través del pueblo, porque el paquete pasa a estar mirando pasa a
ser en el otro lado del mapa, incluso si hay otros mucho más cerca.

Una posible solución sería la de calcular rutas para todos los paquetes y luego tomar la más corta.
Incluso mejores resultados se pueden obtener, si hay múltiples rutas más cortas, prefiriendo los que
van a recoger un paquete en vez de entregar un paquete.

grupo persistente

La forma más conveniente de representar el conjunto de valores de los miembros sigue siendo como una matriz desde las matrices

son fáciles de copiar.

Cuando se añade un valor al grupo, puede crear un nuevo grupo con una copia de la matriz
original que tiene el valor añadido (por ejemplo, usando concat).
Cuando se elimina un valor, que se filtran desde la matriz.
constructor de la clase puede tener una matriz como argumento y almacenarlo como (sólo) la propiedad
de la instancia. Esta matriz no se actualiza.
Para añadir un alojamiento ( vacío) a un constructor que no es un método, hay que añadirlo al constructor
después de la definición de clase, como una propiedad regular.
Sólo necesita una vacío ejemplo, porque todos los grupos vacíos son los mismos y las instancias de
la clase no cambie. Se pueden crear muchos grupos diferentes de ese grupo vacía sola sin afectarlo.

394
Fallos y errores

Procesar de nuevo

La llamada a primitiveMultiply definitivamente debe suceder en una tratar bloquear. El correspondiente captura
bloque debe volver a lanzar la excepción cuando no es una instancia de MultiplicatorUnitFailure y asegurar
la llamada se reintenta cuando es.
Para hacer el volver a intentar, se puede utilizar un bucle que deja sólo cuando una llamada se realiza correctamente,
como en el Mira ejemplo anteriormente en este capítulo, o el uso de recursividad y esperamos que no recibe una serie de
fracasos tan largo que desborda la pila (que es una apuesta bastante segura).

La caja cerrada

Este ejercicio exige una finalmente bloquear. Su función debe primero abrir la caja y luego llamar a la
función desde dentro de un argumento tratar cuerpo. los finalmente
bloquear después de que se debería cerrar la caja de nuevo.

Para asegurarse de que no se bloqueen la caja cuando no estaba ya cerrada, comprobar su bloqueo en el
inicio de la función y bloquear y desbloquear sólo cuando se comenzó bloqueado.

Expresiones regulares

citando estilo

La solución más obvia es la de reemplazar sólo las citas con un carácter de otro tipo en al menos un
lado, algo así como / \ W '|' \ W /. Pero también hay que tomar el inicio y el final de la línea en cuenta.

Además, debe asegurarse de que la sustitución también incluye los tros terísticas que fueron
agrupados por el \ W patrón de modo que aquellos que no se dejan caer. Esto se puede hacer
envolviéndolos en paréntesis, y que incluye sus grupos en la cadena de reemplazo ($ 1, $ 2). Grupos
que no coinciden serán reemplazados por nada.

números de nuevo

En primer lugar, no se olvide de la barra invertida delante del período.


Que coincide con el signo opcional delante del número, así como delante del exponente, se puede
hacer con [+ \ -]? o (\ + | - |) (Más, menos, o nada).
La parte más complicada del ejercicio es el problema de hacer coincidir tanto
"5." y". 5" sin también hacer juego "". Para ello, una buena solución es utilizar

395
el | operador para separar los dos casos-uno o más dígitos seguido opcionalmente por un punto y cero
o más dígitos o un punto seguido por uno o más dígitos.

Por último, para que la mi caso insensible, o bien añadir una yo opción para la expresión regular o el uso [ eE].

módulos

Un robot modular

Esto es lo que habría hecho (pero de nuevo, no hay una sola derecho manera de diseñar un módulo determinado):

El código utilizado para construir la vida en el gráfico de ruta grafico módulo. Porque yo prefiero usar dijkstrajs
de la NGP que nuestro propio código de búsqueda de caminos, vamos a hacer esto construir el tipo de datos
del gráfico que dijkstrajs espera. Este módulo exporta una sola función, buildGraph. Tendría buildGraph aceptar una
serie de matrices de dos elementos, en lugar de cadenas que contienen guiones, para que el módulo de
menos dependiente del formato de entrada.

los carreteras módulo contiene los datos de carreteras primas (el carreteras array) y la
roadGraph Unión. Este módulo depende de ./ grafico y exporta el gráfico de ruta.

los VillageState clase vive en el estado módulo. Depende de ./


carreteras módulo, ya que tiene que ser capaz de verificar que existe un camino determinado. También

necesita randomPick. Ya que es una función de tres líneas, podríamos ponerlo en el estado como un módulo
de función auxiliar interna. Pero randomRobot
lo necesita también. Por lo que tendríamos que sea duplicarlo o ponerlo en su propio módulo. Dado que
esta función pasa a existir en la NGP en el -Artículo al azar paquete, una buena solución es hacer sólo dos
módulos dependen de eso. Podemos añadir el
RunRobot funcionar a este módulo, así, ya que es pequeño y está estrechamente relacionado con la administración del
estado. Las exportaciones de módulos tanto el VillageState clase y la
RunRobot función.
Finalmente, los robots, junto con los valores que dependen de tales como mailRoute
, podría entrar en una ejemplo-robots módulo, que depende de ./ carreteras y exporta las funciones del
robot. Para hacer posible goalOrientedRobot hacer la ruta de los hechos, este módulo también depende
de dijkstrajs.
Al descargar algo de trabajo para los módulos de la NGP, el código se convirtió en un poco más pequeño.
Cada módulo individual hace algo bastante simple y se puede leer por sí mismo. La división de código en
módulos también a menudo sugiere nuevas mejoras en el diseño del programa. En este caso, parece un poco
extraño que la VillageState
y los robots dependen de un gráfico específico carretera. Podría ser una idea mejor

396
hacer que el gráfico de un argumento para el constructor del estado y hacer que los robots leen desde el
estado del objeto-esto reduce dependencias (que siempre es bueno) y hace que sea posible ejecutar
simulaciones en diferentes mapas (que es incluso mejor).

¿Es una buena idea utilizar módulos de la NGP para las cosas que podríamos haber escrito nosotros mismos? En
principio, sí, por cosas triviales como la función de búsqueda de caminos que es probable que cometer errores y
perder el tiempo a la escritura sí mismo. Para pequeñas funciones como random-elemento, escrito por sí mismo es
bastante fácil. Sin embargo, la adición de ellos siempre que se necesite de ellos tiende a saturar sus módulos.

Sin embargo, debe también hay que subestimar el trabajo que implica hallazgo un paquete de NPM
apropiado. E incluso si usted encuentra uno, no puede funcionar bien o faltar por alguna característica que
necesita. Además de eso, dependiendo de paquetes NGP significa que tiene que asegurarse de que están
instalados, hay que distribuirlos con su programa, y ​es posible que tenga que actualizar periódicamente.

Así que de nuevo, esta es una solución de compromiso, y se puede decidir en cualquier dirección dependiendo de la cantidad de

los paquetes que ayudan.

módulo de carreteras

Dado que este es un módulo CommonJS, usted tiene que utilizar exigir importar el módulo gráfico. Que fue
descrito como una exportación buildGraph función, que se puede recoger de su objeto de interfaz con una
desestructuración const declaración.
Exportar roadGraph, se agrega una propiedad a la exportaciones objeto. Porque
buildGraph tiene una estructura de datos que no coincide, precisamente, carreteras, la dividida de recorte de las cadenas

de carretera debe pasar en su módulo.

dependencias circulares

El truco es que exigir añade módulos a su caché antes de se empieza a cargar el módulo. De esta
manera, si los hay exigir llame hecho mientras se está ejecutando intenta cargarse, ya se sabe, y se
devolverá la interfaz actual, en lugar de comenzar a cargar el módulo, una vez más (lo que
eventualmente desbordar la pila).

Si un módulo sobrescribe su module.exports valor, cualquier otro módulo que ha recibido su valor
interfaz antes de que termine la carga se habrá conseguido controlar la interfaz de objeto
predeterminado (que es probable vacío), en lugar del valor interfaz previsto.

397
Programación asíncrona

El seguimiento del bisturí

Esto se puede hacer con un solo bucle que busca a través de los nidos, avanzar a la siguiente cuando
encuentra un valor que no coincide con el nombre de la jerarquía actual y devolver el nombre cuando se
encuentra un valor coincidente. En el asíncrono
función, un habitual para o mientras bucle puede ser utilizado.
Para hacer lo mismo en una función normal, tendrá que construir su bucle que utiliza una función
recursiva. La forma más sencilla de hacer esto es tener esa función re-encender una promesa llamando entonces
con la promesa de que recupera el valor de almacenamiento. Dependiendo de si ese valor coincide con el
nombre de la jerarquía actual, el controlador devuelve ese valor o una promesa más creado por llamar a la
función de bucle de nuevo.

No se olvide de iniciar el bucle llamando a la función recursiva una vez de la función principal.

En el asíncrono función, rechazaron las promesas se convierten en excepciones por esperar


. Cuando un asíncrono función emite una excepción, se rechaza su promesa. Así que eso funciona.

Si implementa la no asíncrono funcionales según lo indicado anteriormente, el camino entonces


También funciona automáticamente causa una falta de terminar en la promesa de regresar. Si una solicitud
falla, el controlador pasa a entonces No se llama, y ​la promesa que regresa es rechazado con la misma razón.

edificio Promise.all

La función pasa a la Promesa constructor tendrá que llamar entonces en cada una de las promesas de la
matriz dada. Cuando uno de ellos tiene éxito, dos cosas tienen que suceder. El valor resultante tiene que
ser almacenado en la posición correcta de una matriz de resultado, y hay que comprobar si esta fue la
última promesa pendiente y terminar nuestra propia promesa si lo fuera.

El último se puede hacer con un contador que se inicializa a la longitud de la matriz de entrada y de la
que se resta 1 cada vez que una promesa tiene éxito. Cuando se llega a 0, hemos terminado. Asegúrese
de tomar en cuenta la situación en la que la matriz de entrada está vacía (y por lo tanto hay promesa
alguna vez resolver).
El control de fallos requiere un poco de pensamiento, pero resulta ser extremadamente simple. Sólo tiene que pasar
la rechazar función de la promesa de envolver a cada una de las promesas de la matriz como una captura controlador o
como un segundo argumento para entonces de modo que un fallo en uno de ellos provoca el rechazo de toda la promesa
envoltura.

398
Proyecto: Un lenguaje de programación

Las matrices

La forma más sencilla de hacer esto es para representar matrices de huevo con matrices de JavaScript.

Los valores añadidos al ámbito superior deben ser funciones. Mediante el uso de un argumento resto ción (con la
notación de triple punto), la definición de formación puede ser muy sencillo.

Cierre

Una vez más, estamos montando a lo largo de un mecanismo JavaScript para obtener la función equivalente
en huevo. Las formas especiales se pasan del ámbito local en el que se evalúan, para que puedan evaluar
sus subformularios en ese ámbito. La función que devuelve divertido tiene acceso a la alcance argumento dado
a su función de cerramiento y la usa para crear ámbito local de la función cuando se le llama.

Esto significa que el prototipo del ámbito local será el ámbito en el que se creó la función, lo que hace
posible el acceso a las consolidaciones en ese ámbito de la función. Esto es todo lo que hay a la
implementación de cierre (aunque para compilarlo en una forma que es realmente eficiente, que había
necesidad de trabajar un poco más).

comentarios

Asegúrese de que su solución se encarga de múltiples comentarios en una fila, con posibles espacios en blanco entre o
después de ellos.
Una expresión regular es probablemente la forma más fácil de solucionar esto. Escribir algu- cosa que coincide
con “espacio en blanco o un comentario, cero o más veces”. Utilizar el
exec o partido método y vistazo a la longitud del primer elemento de la matriz devuelta (todo el partido)
para averiguar el número de caracteres para rebanar.

La fijación de alcance

Usted tendrá que recorrer un ámbito a la vez, usando Object.getPrototypeOf


ir a la siguiente alcance exterior. Para cada alcance, el uso hasOwnProperty para averiguar si la unión,
indicado por el nombre propiedad del primer argumento
conjunto, existe en ese ámbito. Si lo hace, configurarlo para que el resultado de evaluar el segundo argumento conjunto y

luego regresar a ese valor.


Si se alcanza el alcance más externa ( Object.getPrototypeOf vuelve nulo) y no hemos encontrado la
unión, sin embargo, que no existe, y un error debe ser desechado.

399
El Document Object Model

Construir una tabla

Puedes usar document.createElement para crear nuevos nodos de elemento, documento. createTextNode para
crear nodos de texto, y el añadir Niño método para poner nodos en otros nodos.

Usted querrá bucle sobre los nombres de las teclas una vez para llenar la fila superior y luego otra vez para
cada objeto de la matriz para la construcción de las filas de datos. Para obtener una matriz de nombres clave del
primer objeto, Object.keys será útil.
Para agregar la tabla al nodo padre correcta, puede utilizar document.getElementById
o document.querySelector para encontrar el nodo con el buen carné de identidad atributo.

Elementos de nombre de etiqueta

La solución se expresa más fácilmente con una función recursiva, similar a la


habla sobre función definido anteriormente en este capítulo.
Se le podría llamar byTagname sí de forma recursiva, la concatenación de las matrices resultantes para
producir la salida. O bien, podría crear una función interna que se llama de forma recursiva y que tiene acceso a
una serie de unión definido en la función externa, a la que se puede añadir los elementos coincidentes que
encuentra. No se olvide de llamar a la función interna una vez de la función externa para iniciar el proceso.

La función recursiva debe comprobar el tipo de nodo. Aquí sólo nos interesa en el nodo de tipo 1 ( Node.ELEMENT_NODE).
Para este tipo de nodos, debemos bucle sobre sus hijos y, para cada niño, ver si el niño coincide con la
consulta y al mismo tiempo hacer una llamada recursiva en él para inspeccionar sus propios hijos.

El sombrero del gato

math.cos y math.sin medir ángulos en radianes, donde un círculo completo es 2 π. Para un ángulo dado, se

puede obtener el ángulo opuesto al añadir la mitad de esto, que es


Math.PI. Esto puede ser útil para poner el sombrero en el lado opuesto de la órbita.

Gestión de eventos

Globo

Usted querrá registrar un controlador para el " keydown" evento y ver event.key
averiguar si la flecha arriba o abajo ha pulsado la tecla.

400
El tamaño actual puede mantenerse en una unión para que pueda basar el nuevo tamaño en él. Será útil
para definir una función que actualiza el tamaño, tanto la unión y el estilo del globo en el DOM-de manera que
se puede llamar desde su controlador de eventos, y posiblemente también una vez al iniciar, para establecer
el tamaño inicial .
Se puede cambiar el globo a una explosión mediante la sustitución del nodo de texto con otro (usando replaceChild)
o estableciendo la contenido del texto propiedad de su nodo padre a una nueva cadena.

rastro del ratón

La creación de los elementos se realiza mejor con un bucle. Anexar al documento para hacerlos aparecer.
Para poder acceder a ellos más tarde para cambiar su posición, tendrá que almacenar los elementos de una
matriz.
Ciclismo a través de ellos se puede hacer manteniendo una variable de contador y añadiendo 1 a cada vez que
el " movimiento del ratón" desencadena el evento. El operador resto (%
elements.length) a continuación, se puede utilizar para obtener un índice de matriz válida para recoger el elemento que
desea colocar en un evento determinado.
Otro efecto interesante se puede lograr mediante el modelado de un sistema simple física. Utilizar el " movimiento
del ratón" evento sólo para actualizar un par de fijaciones que rastrean la posición del ratón. A continuación,

utilice requestAnimationFrame para simular los elementos que se arrastran de ser atraído a la posición del puntero
del ratón. A cada paso animación, actualizar su posición en función de su posición en relación con el puntero
(y, opcionalmente, una velocidad que se almacena para cada elemento). Averiguar una buena manera de
hacer esto es a usted.

Pestañas

Una trampa que podría encontrarse es que no se puede utilizar directamente la década de los ganglios childNodes

propiedad como un conjunto de nodos de la ficha. Por un lado, al agregar los botones, ellos también se convierten en
nodos secundarios y terminan en este objeto, ya que es una estructura de datos en tiempo real. Por otra parte, los nodos
de texto creados por el espacio en blanco entre los nodos son también en childNodes pero no se debe tener en sus propias
pestañas. Puedes usar niños en lugar de childNodes hacer caso omiso de los nodos de texto.

Se podría empezar por la construcción de una serie de pestañas para que tenga fácil acceso a ellos. Para
poner en práctica el estilo de los botones, se puede almacenar objetos que contienen tanto el panel de pestañas y
su botón.
Recomiendo escribir una función separada para el cambio de pestañas. Usted puede almacenar la ficha seleccionada
previamente y cambiar sólo los estilos necesarios para ocultar eso y mostrar el nuevo, o simplemente puede actualizar el
estilo de todas las pestañas cada vez que se selecciona una nueva pestaña.

401
Es posible que desee llamar a esta función inmediata para que el inicio de interfaz con la primera pestaña
visible.

Proyecto: Un PlatformGame

Pausar el juego

Una animación puede ser interrumpido mediante la devolución falso de la función dada a runAnimation. Se
puede continuar llamando runAnimation de nuevo.
Así que tenemos que comunicar el hecho de que estamos pausar el juego para la función dada a runAnimation.
Para ello, se puede utilizar una unión que tanto el controlador de eventos y que tengan acceso a la
función.
Al encontrar una manera de anular el registro de los controladores registrados por trackKeys, miembro de re- que
la exacto mismo valor de la función que se pasó a addEventListener
debe pasar a removeEventListener para eliminar con éxito un manejador. Por lo tanto, la manipulador valor de la
función creada en trackKeys debe estar disponible para el código que anula el registro de los manipuladores.

Puede agregar una propiedad al objeto devuelto por trackKeys, que contiene o bien que el valor de función
o un método que controla la anulación del registro directamente.

Un monstruo

Si se desea implementar un tipo de movimiento que es estado, como bounc- ing, asegúrese de guardar
el estado necesario en el actor objeto incluirlo como argumento del constructor y añadirlo como una
propiedad.
Recuerda eso actualizar devuelve una nuevo oponerse, en lugar de cambiar el antiguo.

Al manipular la colisión, el jugador encontrará en state.actors y para comparar su posición con la


posición del monstruo. Para obtener el fondo del jugador, hay que añadir su tamaño vertical a su
posición vertical. La creación de un estado actualizado se parecerá a cualquiera Moneda 's chocar método
(eliminando el actor) o Lava
'S (cambiar el estado a " perdido"), dependiendo de la posición del jugador.

Sobre la base de la lona

formas

El trapecio (1) es más fácil dibujar utilizando una ruta. Escoja el centro Apropiado nalgas coordi- y añadir cada una
de las cuatro esquinas alrededor del centro.

402
El diamante (2) se puede trazar el camino sencillo, con una ruta, o la forma interesante, con una girar
transformación. Para utilizar la rotación, que tendrá que aplicar un truco similar a lo que hicimos en el voltear
horizontalmente función. Debido a que desea girar alrededor del centro de su rectángulo y no alrededor
del punto (0,0), primero debe traducir de ahí, a continuación, girar, y luego traducir espalda.

Asegúrese de restablecer la transformación después de dibujar cualquier forma que crea uno.

Para el zigzag (3) se convierte en impracticable para escribir una nueva llamada a lineTo para cada segmento de
línea. En su lugar, se debe utilizar un bucle. Puede tener cada iteración dibujar o bien dos segmentos de línea (a la
derecha y luego a la izquierda) o uno, en cuyo caso debe utilizar la uniformidad (% 2) del índice de bucle para
determinar si ir a la izquierda o derecha.

Usted también necesitará un bucle de la espiral (4). Si se traza una serie de puntos, con cada punto que
se mueve más a lo largo de un círculo alrededor del centro de la espiral, se obtiene un círculo. Si, durante el
bucle, se varía el radio del círculo en el que está poniendo el punto actual y dar la vuelta más de una vez, el
resultado es una espiral.
La estrella (5) representado se construye fuera de quadraticCurveTo líneas. También podría elaborar una
con líneas rectas. Dividir un círculo en ocho pedazos para una estrella de ocho puntas, o sin embargo
muchas de las piezas que desee. Dibujar líneas ser- Tween estos puntos, por lo que la curva hacia el
centro de la estrella. Con
quadraticCurveTo, se puede utilizar el centro como punto de control.

El gráfico de sectores

Tendrá que llamar fillText y establecer la década de contexto Alineación texto y TextBaseline
propiedades de tal manera que el texto termina donde lo desee. Una forma sensata de colocar las
etiquetas sería poner el texto en la línea que va desde el centro del pastel por el centro de la rodaja.
Usted no quiere poner el texto directamente contra el lado del pastel, sino más bien mover el texto
hacia un lado de la tarta por un número determinado de píxeles.

El ángulo de esta línea es currentAngle + 0,5 * sliceAngle. El código siguiente busca una posición en
esta línea 120 píxeles desde el centro:

dejar que middleAngle = currentAngle + 0,5 * sliceAngle; dejar que textX = Math.cos
(middleAngle) * 120 + centerx; dejar que Texty = Math.sin (middleAngle) * 120 +
centery;

por TextBaseline, el valor " medio" es probablemente correspondiente al utilizar este enfoque. Lo que
se utilizará para Alineación texto depende de qué lado del círculo en que estamos. A la izquierda, debe
ser " derecho", ya la derecha, debe ser

403
"izquierda", de modo que el texto se coloca lejos de la tarta.
Si no está seguro de cómo averiguar qué lado del círculo es un ángulo dado, mira a la explicación de math.cos
en capítulo 14 . El coseno de un ángulo nos dice lo que la coordenada x que corresponde a, que a su
vez nos dice exactamente qué lado del círculo en que estamos.

Una pelota que rebota

Una caja es fácil de dibujar con strokeRect. Definir una unión que mantiene su tamaño o definir dos fijaciones
si el ancho y la altura de su cuadro difieren. Para crear una bola redonda, iniciar un camino y llamar arco (x,
y, radio, 0, 7), lo que crea un arco que va desde cero a más de un círculo completo. Luego llene el camino.

Para modelar la posición de la pelota y la velocidad, se puede utilizar el vec la clase de


capítulo 16 . Dale una velocidad de arranque, preferiblemente uno que no es puramente vertical u horizontal, y para
cada trama multiplicar esta velocidad por la cantidad de tiempo que ha transcurrido. Cuando la pelota se acerca
demasiado a una pared vertical, invertir la componente x de la velocidad. Del mismo modo, invertir la componente y
cuando golpea una pared horizontal.
Después de encontrar nueva posición y la velocidad de la pelota, el uso clearRect para borrar la escena y volver a
dibujar usando la nueva posición.

reflejo precomputed

La clave para la solución es el hecho de que podemos utilizar un elemento de tela una imagen de origen
como cuando se usa drawImage. Es posible crear un extra < canvas> elemento, sin agregarlo al documento,
y sacar nuestras sprites invertidos a ella, una vez. Al dibujar un marco real, simplemente copiar los
sprites ya invertidos en el lienzo principal.

Algún tipo de atención sería necesaria ya que las imágenes no se cargan al instante. Hacemos el dibujo invertido
sólo una vez, y si lo hacemos antes de que la imagen se carga, no va a dibujar cualquier cosa. UNA " carga" manejador
en la imagen puede ser usado para dibujar las imágenes invertidas a la lona adicional. Esta tela se puede utilizar
como una fuente de dibujo inmediatamente (que va a ser simplemente en blanco hasta que se dibuja el carácter en
él).

HTTP y las formas

negociación de contenido

Base su código en el ir a buscar ejemplos anteriormente en este capítulo . Pidiendo un tipo de medio falsa
devolverá una respuesta con código 406, “No es aceptable”, que es el código de un servidor debería devolver
cuando no puede cumplir con la

404
Aceptar encabezamiento.

Un banco de trabajo JavaScript

Utilizar document.querySelector o document.getElementById para obtener acceso a los elementos definidos en el código
HTML. Un controlador de eventos para " hacer clic" o " ratón hacia abajo " eventos en el botón pueden obtener la valor propiedad
del campo de texto y llamadas
Función en eso.
Asegúrese de embalar tanto la llamada a Función y la llamada a su resultado en una
tratar bloque para que pueda detectar las excepciones que produce. En este caso, realmente no sabemos qué
tipo de excepción que estamos buscando, así que coge todo.
los contenido del texto propiedad del elemento de salida se puede utilizar para llenarlo con un mensaje de
cadena. O bien, si desea mantener el contenido de edad alrededor, crear un nuevo nodo de texto usando document.createTextNode
y añadirlo al elemento. No olvide añadir un carácter de nueva línea al final de manera que parece que no toda la
producción en una sola línea.

Juego de la vida

Para resolver el problema de tener los cambios conceptualmente ocurren al mismo tiempo, tratar de ver el
cálculo de una generación como una función pura, que tiene una rejilla y produce una nueva cuadrícula que
representa el siguiente turno.
En representación de la matriz se puede hacer de la manera mostrada en Capítulo 6 . Usted puede

contar vecinos en vivo con dos bucles anidados, de enlace sobre las coordenadas adyacentes en ambas
dimensiones. Tenga cuidado de no contar las células fuera del campo y hacer caso omiso de la celda en el
centro, cuyos vecinos estamos contando.
Asegurar que los cambios surtan efecto a casillas de verificación en la próxima generación se puede hacer de dos
maneras. Un controlador de eventos podría notar estos cambios y actualizar la cuadrícula actual para reflejar ellos, o
que podría generar una rejilla fresca de los valores en las casillas de verificación antes de calcular el siguiente turno.

Si usted decide ir con los controladores de eventos, es posible que desee adjuntar atributos que
identifican la posición que cada casilla corresponde a por lo que es fácil averiguar qué celda para cambiar.

Para dibujar la cuadrícula de casillas de verificación, se puede utilizar un < table> elemento (ver
capítulo 14 ) O, simplemente, poner a todos en el mismo elemento y poner < br> ( salto de línea)
elementos entre las filas.

405
Proyecto: Un Editor Pixel Art

las asociaciones de teclas

los llave propiedad de los acontecimientos para teclas de letras será la misma letra minúscula, si
cambio No se encuentra detenido. No estamos interesados ​en eventos clave con cambio aquí.
UNA " keydown" manejador puede inspeccionar su objeto evento para ver si coincide con cualquiera de los accesos
directos. Usted puede obtener automáticamente la lista de primeras letras de la
herramientas objeto de modo que usted no tiene que escribir a cabo.
Cuando el evento clave coincide con un acceso directo, llamada preventDefault en él y dis- parchear la acción
apropiada.

dibujo eficiente

Este ejercicio es un buen ejemplo de cómo inmutables estructuras de datos pueden hacer que el código Más rápido. Debido
a que tenemos la antigua y de la nueva imagen, podemos compararlas y volver a dibujar sólo los píxeles que
cambiaban de color, ahorro de más del 99 por ciento del trabajo de dibujo en la mayoría de los casos.

o bien se puede escribir una nueva función updatePicture o tienen dibujar la imagen tomar un argumento
adicional, que puede ser indefinido o de la imagen anterior. Para cada píxel, la función comprueba si un
cuadro anterior se hizo pasar con el mismo color en esta posición y se salta el píxel cuando ese es el
caso.
Debido a que el lienzo se limpia cuando cambiamos su tamaño, también se debe evitar el contacto con su anchura
y altura propiedades cuando la imagen antigua y la nueva imagen tienen el mismo tamaño. Si son diferentes, lo
que va a pasar cuando una nueva imagen ha sido cargado, se puede establecer la unión sosteniendo la vieja
imagen de nula después de cambiar el tamaño del lienzo, ya que no debe saltarse ningún píxel después de
haber cambiado el tamaño del lienzo.

círculos

Puede tomar un poco de inspiración de la rectángulo herramienta. Al igual que la herramienta, usted querrá
seguir aprovechando la comenzando imagen, en lugar de la imagen actual, al pasar el puntero.

Para averiguar qué píxeles a color, se puede usar el teorema de Pitágoras. En primer lugar calcular
la distancia entre la posición actual del puntero y la posición de inicio tomando la raíz cuadrada ( math.sqrt)
de la suma del cuadrado ( Matemáticas
. pow (x, 2)) de la diferencia de coordenadas x y el cuadrado de la diferencia de coordenadas y. Entonces
bucle sobre un cuadrado de píxeles alrededor de la posición de inicio, cuyos lados son al menos dos veces
el radio, y el color los que están dentro de la

406
el radio del círculo, de nuevo utilizando la fórmula de Pitágoras para averiguar su distancia desde el centro.

Asegúrese de que no trate de píxeles de color que están fuera de los límites de la imagen.

líneas adecuadas

Lo que pasa con el problema de trazar una línea pixelada es que es realmente cuatro problemas similares pero
ligeramente diferentes. Dibujando una línea horizontal desde la izquierda a la derecha es fácil que un bucle sobre
las coordenadas x y el color de un píxel en cada paso. Si la línea tiene una ligera pendiente (menos de 45 grados
o ¼ π radianes), se puede interpolar la coordenada a lo largo de la pendiente. Todavía es necesario un píxel por
cada
X posición, con el y posición de los píxeles determinados por la pendiente.
Pero tan pronto como su pendiente va a través de 45 grados, es necesario cambiar la forma en que trata las
coordenadas. Ahora se necesita un píxel por cada y posición desde la línea sube más de lo que va a la izquierda.
Y luego, cuando se cruza 135 grados, lo que tiene que volver a un bucle sobre las coordenadas x, pero de
derecha a izquierda.
En realidad no tiene que escribir cuatro bucles. Desde dibujando una línea desde UNA a
si es el mismo que dibujando una línea desde si a UNA, usted puede intercambiar las posiciones de inicio y final de las líneas que
van de derecha a izquierda y tratarlos como ir a la izquierda a la derecha.

Así que hay dos bucles diferentes. La primera cosa que su función de dibujo de línea debe hacer
es comprobar si la diferencia entre las coordenadas x es mayor que la diferencia entre las
coordenadas y. Si es así, esta es una línea horizontal-ish, y si no, una vertical-ish.

Asegúrese de comparar el absoluto valores de la X y y diferencia, que se puede obtener con Math.abs.

Una vez que sepa lo largo de dicho eje que comenzará a dar vueltas, se puede comprobar si el punto de inicio
tiene un mayor coordenadas a lo largo de ese eje que el punto final e intercambiarlos si es necesario. Una manera
sucinta para intercambiar los valores de dos fijaciones en JavaScript utiliza la asignación desestructurada como esto:

[Inicio, final] = [final, iniciar];

A continuación, puede calcular la pendiente de la línea, lo que determina la cantidad de la coordenada de


los otros cambios de ejes para cada paso que das a lo largo de su eje principal. Con esto, puede ejecutar un
bucle a lo largo del eje principal al mismo tiempo que el seguimiento de la posición correspondiente en el otro
eje, y se puede dibujar píxeles en cada iteración. Asegúrese de que alrededor del eje principal no coordina, ya
que son propensos a ser fraccionada y la dibujar método no responde bien a las coordenadas fraccionarias.

407
Node.js

Herramienta de búsqueda

Su primer argumento de la línea de comandos, la expresión regular, se puede encontrar en


process.argv [2]. Los archivos de entrada vienen después de eso. Se puede utilizar el RegExp

constructor para pasar de una cadena a un objeto de expresión regular.


Hacer esto de forma sincrónica, con readFileSync, es más sencillo, pero si se utiliza fs.promises de nuevo
para obtener funciones promesa de regresar y escribir una
asíncrono función, el código es similar.
Para averiguar si algo es un directorio, se puede utilizar de nuevo stat ( o
statSync) y las estadísticas objeto de isDirectory método.
Exploración de un directorio es un proceso de ramificación. Puede hacerlo ya sea mediante el uso de una función
recursiva o manteniendo una serie de trabajos (archivos que aún deben ser explorados). Para encontrar los archivos
en un directorio, puede llamar readdir o readdirSync.
la función del sistema de nombres de archivos del extraño capitalización-nodo se basa libremente en las funciones
estándar de Unix, tales como readdir, que son minúsculas, pero luego se agrega sync con una letra mayúscula.

Para pasar de un nombre de archivo con la lectura readdir a un nombre de ruta completo, hay que combinarlo
con el nombre del directorio, poniendo un carácter de barra (/) entre ellos.

de creación del directorio

Puede utilizar la función que implementa el ELIMINAR método como un modelo para el MKCOL método.
Cuando no se encuentra un archivo, intente crear un directorio con
mkdir. Cuando existe un directorio en ese camino, puede devolver una respuesta 204 de modo que las solicitudes
de creación de directorios son idempotente. Si un archivo nondirectory existe aquí, un código de error. Código
400 ( “solicitud incorrecta”) sería apropiado.

Un espacio público en la web

Se puede crear un < textarea> elemento para mantener el contenido del archivo que se está editando. UNA OBTENER solicitud,
el uso ir a buscar, puede recuperar el contenido actual del archivo. Se puede utilizar como URL relativos index.html, en
lugar de http: // localhost: 8000 / index.html, para referirse a los archivos en el mismo servidor que el script en
ejecución.
Entonces, cuando el usuario hace clic en un botón (se puede utilizar un < form> elemento y "
enviar" evento), hacer una PONER solicitar a la misma URL, con el contenido de la
<Textarea> como petición del cuerpo, para guardar el archivo.

A continuación, puede añadir un < seleccione> elemento que contiene todos los archivos en el directorio superior del

servidor agregando < option> elementos que contienen las líneas devueltos por una

408
OBTENER solicitar a la URL /. Cuando el usuario selecciona otro archivo (un " cambio" evento en el campo), el guión debe
obtener y mostrar ese archivo. Al guardar un archivo, utilice el nombre del archivo seleccionado en ese momento.

Proyecto: Habilidad-SharingWebsite

la persistencia de disco

La solución más simple que puedo llegar a es codificar el conjunto negociaciones objetar como JSON y lo
descarga en un archivo con writefile. Ya existe un método ( actualizado
) que se llama cada vez que cambian los datos del servidor. Se puede extender a escribir los nuevos datos en el
disco.
Escoja un nombre de archivo, por ejemplo ./ talks.json. Cuando se inicia el servidor, se puede tratar de leer ese
archivo con readFile, y si esto tiene éxito, el servidor puede utilizar el contenido del archivo como sus datos de partida.

Tenga cuidado, sin embargo. los negociaciones objeto comenzó como un objeto prototipo a menos que el en operador
de forma fiable podría ser utilizado. JSON.parse devolverá los objetos regulares con Object.prototype como su
prototipo. Si utiliza JSON como formato de archivo, tendrá que copiar las propiedades del objeto devuelto por JSON.parse
en un nuevo objeto prototipo-menos.

restablece campo de comentarios

La mejor manera de hacer esto es probablemente para hacer que los objetos componentes conversaciones, con una

SyncState método, de modo que se puede actualizar para mostrar una versión modificada de la charla. Durante el
funcionamiento normal, la única manera de una charla se puede cambiar es mediante la adición de más comentarios, por lo
que la SyncState método puede ser relativamente simple.
La parte difícil es que, cuando una lista modificada de conversaciones entra, tenemos que reconciliar a la lista existente
de componentes DOM con las conversaciones sobre los nuevos componentes de la lista, el restablecimiento cuya charla ha
sido eliminada y la actualización de los componentes cuya charla cambiado.

Para ello, podría ser útil tener una estructura de datos que almacena los componentes de entrevistas con los
títulos de conversación para que pueda fácilmente averiguar si existe un componente de una charla dada. A
continuación, puede bucle sobre la nueva serie de conversaciones, y para cada uno de ellos, ya sea sincronizar un
componente existente o crear una nueva. Para eliminar componentes para conversaciones eliminadas, tendrá
también un bucle sobre los componentes y comprobar si subsisten las conversaciones correspondientes.

409

Vous aimerez peut-être aussi