Como crear un buscador de criptomonedas con Redux, parte  1

Como crear un buscador de criptomonedas con Redux, parte 1

Introducción

En esta publicación veremos como utilizar redux para obtener datos de una API. Con estos datos obtenidos vamos a llenar una lista de criptomonedas y agregar un buscador.

Para comprender el contenido debes de saber lo que es redux y sus principios. Si quieres más información fundamental sobre como usar redux, puedes revisar estas publicaciones:

¿Qué queremos lograr?

Buscador parte 1 terminada
Buscador parte 1 terminada

En la imagen tenemos un campo de búsqueda y una lista con todos los mercados disponibles. Puedes crear un proyecto de React en codesandbox.io.

Aquí puedes ver como debe funcionar el buscador en esta primera parte.

https://b8lxc.csb.app/

Primera iteración, datos en duro en los componentes

La idea de empezar con los datos en duro es conocer el formato de lo que responde la API y como esos datos los queremos encajar en los componentes visuales.

¿Cómo son los datos de la respuesta del API?

Primero revisemos la forma de los datos que regresa la API. Estos datos son tomados de aquí. Más adelante simularemos la petición al API para facilitar el desarrollo.

{
  "success": true,
  "payload": [
    {
      "high": "1031000.00",
      "last": "1007500.03",
      "created_at": "2021-12-22T18:51:21+00:00",
      "book": "btc_mxn",
      "volume": "85.76293860",
      "vwap": "1015844.0131800048",
      "low": "1003978.08",
      "ask": "1007766.40",
      "bid": "1007500.03",
      "change_24": "-1808.15"
    },
    ...
  }
}

Vamos a crear la interfaz pensando en los datos mostrados y componentes, de momento se me ocurre una tabla donde estarán listados los detalles del mercado y un campo de búsqueda.

Componente CryptoMarkets

Entonces agregamos un componente contenedor /src/CrytoMarkets/CryptoMarkets.js. En este componente contenedor vivirán los dos componentes antes mencionados, uno para hacer la búsqueda y otro para la tabla donde se listaran los mercados y el resultado de la búsqueda.

export default function CryptoMarkets() {
  return (
    <>
      <SearchField />
      <Table />
    </>
  );
}

Componente SearchField

Creamos el componente SearchField en /src/CryptoMarkets/SearchField.js

export default function SearchField({ label }) {
  return (
    <label>
      {label}
      <input type="search" />
    </label>
  );
}

Componente Table

Y el componente Table en /src/CryptoMarkets/Table.js

import styles from "./Table.module.scss";
export default function Table() {
  return (
    <div>
      <div className={styles.row}>
        <p>Mercado</p>
        <p>Moneda</p>
        <p>Último precio</p>
        <p>Volumen</p>
        <p>Precio más alto</p>
        <p>Precio más bajo</p>
        <p>Variación 24hrs</p>
        <p>Cambio 24hrs</p>
      </div>
      <div className={styles.row}>
        <p>btc/mxn</p>
        <p>btc</p>
        <p>1007500.03</p>
        <p>85.76293860</p>
        <p>1031000.00</p>
        <p>1003978.08</p>
        <p>1015844.01</p>
        <p>-1808.15</p>
      </div>
      <div>
        <p>eth/btc</p>
        <p>eth</p>
        <p>0.08</p>
        <p>52.02866824</p>
        <p>0.08</p>
        <p>0.08</p>
        <p>0.08</p>
        <p>-0.00040000</p></div>
    </div>
  );
}

Y para que se vea como una tabla le agregamos los siguientes estilos en /src/CrytoMarkets/Table.module.scss. Los estilos no es tema de esta publicación así que no le tomes mucha importancia por el momento, en caso de que se te compliquen.

Lo único es que si no estás haciendo el buscador en codesandbox y te sale algún problema con los estilos en sass, instala la librería de la siguiente forma.

yarn add sass
.row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  border-bottom: 1px solid #eee;
  &:hover {
    background-color: #efefef;
  }
  &:last-child  {
    border-bottom: none;
  }
}
.header:hover {
  background-color: initial;
}
.item {
  word-wrap: break-word;
  padding: 8px;
  &:first-child {
    text-transform: uppercase;
  }
}
.highlight {
  font-weight: bold;
}

En la tabla podemos notar que los divs que contiene los datos y el div del encabezado son muy similares, dentro se encuentran varios párrafos conteniendo cada pedazo de información. Así que crearemos un componente que hago eso, lo que contiene cada fila de la tabla.

Componente TableRow

/src/CryptoMarkets/TableRow.js

export default function TableRow({ items, className}) {
  return (
    <div className={className}>
      {items.map((item, index) =>
        <p key={index} className={item.className}>
          {item.value}
        </p>
      )}
    </div>
  );
}

Usando datos dummies.js

Dentro del componente Table agregamos dos arreglos, uno para el header de la tabla y otro para una fila de la tabla. Como mencionamos antes, el header y las filas son muy similares, así que reutilizamos el mismo componente TableRow para generarlos.

import styles from "./Table.module.scss";
import TableRow from "./TableRow";
const headers = [
  { value: "MERCADO", className: `${styles.item} ${styles.highlight}` },
  { value: "Moneda", className: `${styles.item} ${styles.highlight}` },
  { value: "Último precio", className: `${styles.item} ${styles.highlight}` },
  { value: "Volumen", className: `${styles.item} ${styles.highlight}` },
  { value: "Precio más alto", className: `${styles.item} ${styles.highlight}` },
  { value: "Precio más bajo", className: `${styles.item} ${styles.highlight}` },
  { value: "Variación 24hrs", className: `${styles.item} ${styles.highlight}` },
  { value: "Cambio 24hrs", className: `${styles.item} ${styles.highlight}` }
];
const row = [
  { value: "BTC/MXN", className: styles.item },
  { value: "btc", className: styles.item },
  { value: "1007500.03", className: styles.item },
  { value: "85.76293860", className: styles.item },
  { value: "1031000.00", className: styles.item },
  { value: "1003978.08", className: styles.item },
  { value: "1015844.01", className: styles.item },
  { value: "-1808.15", className: styles.item }
];
export default function Table() {
  return (
    <div>
      <TableRow items={headers} className={`${styles.row} ${styles.header}`} />
      <TableRow items={row} className={styles.row} />
    </div>
  );
}

Como te puedes dar cuenta el componente TableRow recibe items y className como propiedades, para que el header de la table no tenga el efecto del estilo :hover agregamos la clase .header.

Generando filas dinámicamente y dummies separados.

Primero separamos los dummies y les damos forma.

import styles from "./Table.module.scss";
export const headers = [
  { value: "MERCADO", className: `${styles.item} ${styles.highlight}` },
  { value: "Moneda", className: `${styles.item} ${styles.highlight}` },
  { value: "Último precio", className: `${styles.item} ${styles.highlight}` },
  { value: "Volumen", className: `${styles.item} ${styles.highlight}` },
  { value: "Precio más alto", className: `${styles.item} ${styles.highlight}` },
  { value: "Precio más bajo", className: `${styles.item} ${styles.highlight}` },
  { value: "Variación 24hrs", className: `${styles.item} ${styles.highlight}` },
  { value: "Cambio 24hrs", className: `${styles.item} ${styles.highlight}` }
];
export const rows = [
  {
    id: "btc/mxn",
    className: styles.row,
    items: [
      { value: "btc/mxn", className: styles.item },
      { value: "btc", className: styles.item },
      { value: "1007500.03", className: styles.item },
      { value: "85.76293860", className: styles.item },
      { value: "1031000.00", className: styles.item },
      { value: "1003978.08", className: styles.item },
      { value: "1015844.01", className: styles.item },
      { value: "-1808.15", className: styles.item }
    ]
  },
  {
    id: 2,
    className: styles.row,
    items: [
      { value: "eth/btc", className: styles.item },
      { value: "eth", className: styles.item },
      { value: "0.08", className: styles.item },
      { value: "52.02866824", className: styles.item },
      { value: "0.08", className: styles.item },
      { value: "0.08", className: styles.item },
      { value: "0.08", className: styles.item },
      { value: "-0.00040000", className: styles.item }
    ]
  }
];

Luego utilizamos esos dummies en instancias del componente TableRow.

import styles from "./Table.module.scss";
import TableRow from "./TableRow";
import { headers, rows } from "./dummies";
export default function Table() {
  return (
    <div>
      <TableRow items={headers} className={`${styles.row} ${styles.header}`} />
      {rows.map((row) => (
        <TableRow key={row.id} items={row.items} className={row.className} />
      ))}
    </div>
  );
}

Usamos el nombre del mercado como el id porque es un dato que no se puede repetir. En este punto debemos tener algo así:

Buscador con dummies
Buscador con dummies

Segunda Iteración, agregando Redux

Para usar redux, necesitamos instalar la librería redux y para facilitar su uso en React instalamos también react-redux. Desde condesandbox puedes agregar estas dependencias en la parte inferior izquierda del editor donde dice “Dependencies“.

En tu computadora local corre el siguiente comando.

yarn add redux react-redux

En este punto ya sabemos la forma en que necesitamos los datos, la tabla necesita unos headers y una lista de filas con valores (rows).

Además, necesitamos crear un store que sea accesible desde cualquier parte de nuestra aplicación. El principal insumo que necesita el store es un reducer. Aún no obtendremos los datos de los mercados de la API, utilizaremos de momento los datos del archivo /src/CryptoMarkets/dummies.js

Configurando inicialmente el store

Crearemos un archivo nuevo /configureStore.js.

import { createStore } from "redux";
import { headers, rows as markets } from "./CryptoMarkets/dummies";
const initialState = {
  headers,
  markets
};
export default function configureStore() {
  return createStore((state, action) => state, initialState);
}

Importamos la función createStore que recibe una función reducer y opcionalmente un estado inicial. La función reducer de momento solo regresará el estado inicial tal cual, como sabemos, en redux las funciones reducers reciben un estado y una acción. Como segundo parámetro opcional le pasamos los datos dummies.

La función configureStore la utilizaremos a continuación.

Hacer al store accesible

Para que cualquier componente pueda acceder al store es necesario crear un contexto global, esto normalmente se realiza con React.createContext() y usando su componente Context.Provider. Por suerte la librería react-redux ya cuenta con este funcionamiento y podemos utilizar su propio componente Provider.

Si tienes curiosidad como usar el Context de React, puedes revisar esta publicación:

useContext y useReducer, ¿Cómo replicar redux?

Aplicamos el Provider de react-redux en nuestra aplicación de la siguiente forma. En nuestro archivo /index.js

import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import App from "./App";
import configureStore from "./configureStore";
const store = configureStore();
const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </StrictMode>,
  rootElement
);

Al componente Provider debemos pasarle una propiedad store. Esa propiedad es el store generado por la función configureStore que creamos en la sección anterior.

Acceder al store desde el componente contenedor CryptoMarkets

Ahora que ya tenemos el store listo para usarse, vamos a obtener el estado para mostrar el listado de markets en el componente Table.

import Table from "./Table";
import SearchField from "./SearchField";
import { useSelector } from "react-redux";
export default function CryptoMarkets() {
  const headers = useSelector((state) => state.headers);
  const rows = useSelector((state) => state.markets);
  return (
    <>
      <SearchField></SearchField>
      <Table headers={headers} rows={rows}></Table>
    </>
  );
}

Usamos lo que en redux llaman selectors, los cuales son funciones que acceden a una parte especifica del estado para facilitar su acceso. Se usa el “custom hook” useSelector de react-redux para invocar las funciones selectoras.

Componente Table recibe headers y rows como propiedades

Finalmente, el componente Table debe poder recibir las propiedades headers y rows en lugar de importarlos del dummies.js

import styles from "./Table.module.scss";
import TableRow from "./TableRow";
export default function Table({ rows, headers }) {
  return (
    <div>
      <TableRow items={headers} className={`${styles.row} ${styles.header}`} />
      {rows.map((row) => (
        <TableRow key={row.id} items={row.items} className={row.className} />
      ))}
    </div>
  );
}

El resultado deberá ser el mismo de la primera iteración, pero con los datos obtenidos del store

Buscador con dummies
Buscador con dummies

Implementar la busqueda de mercados

La búsqueda que vamos a implementar será por el nombre del mercado. Así que basándonos en los datos del archivo /src/CrytoMarkets/dummies.js, vamos a modificar nuestra función reducer para agregar la acción de filtrado.

Creamos el archivo /src/CryptyoMarkets/cryptoMarketsState.js donde ahora vivirá nuestro reducer, separándolo del archivo /configureStore.js.

// state = { headers [], all = [], filtered = []}
export default function cryptoMarkets(state, action) {
  switch (action.type) {
    case "FILTER_MARKETS":
      return {
        ...state,
        filtered: state.all.filter(
          (market) => market.id.includes(action.payload)
        )
      };
    default:
      return state;
  }
}

El comentario muestra la estructura del estado. La estructura sé cambio porque necesitamos guardar la lista original de mercados para ser utilizado en cada búsqueda, de lo contrario filtraríamos mercados sobre los últimos filtrados y así sucesivamente eliminando todos los mercados.

Una acción es un objeto literal que contiene un tipo y demás datos necesarios para cambiar el estado del store, estos datos los vamos a guardar en una propiedad llamada payload.

La acción 'FILTERED_MARKETS', filtra todos los markets que en su propiedad id incluyan el texto contenido en payload.

 state.all.filter(
   (market) => market.id.includes(action.payload)
 )

Luego en /configureStore.js reflejamos esa nueva estructura en el initialState e importamos nuestra nueva función reducer.

import { createStore } from "redux";
import { headers, rows as markets } from "./CryptoMarkets/dummies";
import reducer from "./CryptoMarkets/cryptoMarketsState";
const initialState = {
  headers,
  all: markets,
  filtered: markets
};
export default function configureStore() {
  return createStore(reducer, initialState);
}

Disparar acción de filtrado desde el componente CryptoMarkets

Aunque ya creamos la acción, debemos dispararla cuando el usuario escribe en el campo de búsqueda, primero vamos a modificar el componente SearchField. Ahora además de recibir el label como propiedad, también la función que se ejecuta cada vez que el valor de búsqueda cambia.

export default function SearchField({ label, onSearchChange }) {
  return (
    <label>
      {label}
      <input type="search" onChange={onSearchChange} />
    </label>
  );
}

De esta manera dentro del componente CryptoMarkets podemos disparar la acción FILTER_MARKETS como sigue.

import Table from "./Table";
import SearchField from "./SearchField";
import { useSelector, useDispatch } from "react-redux";
export default function CryptoMarkets() {
  const dispatch = useDispatch();
  const headers = useSelector((state) => state.headers);
  const rows = useSelector((state) => state.filtered);
  function onSearchMarket(e) {
    dispatch({ type: "FILTER_MARKETS", payload: e.target.value });
  }
  return (
    <>
      <SearchField onSearchChange={onSearchMarket} />
      <Table headers={headers} rows={rows}></Table>
    </>
  );
}

Se usa el “custom hook” useDispatch de react-redux para disparar acciones desde cualquier componente funcional. Este hook regresa una función dispatch que se utiliza para disparar cualquier acción.

Al componente SearchField se le pasa la propiedad onSearchChange donde recibe la función que invoca la acción cuando el valor del campo de búsqueda cambia.

El resultado de esta iteración debe ser algo como lo siguiente:

Buscador implementado
Buscador implementado

Refactor, action creator para disparar búsqueda

Los action creators no son más que simples funciones que regresan una acción, y una acción es simplemente un objeto literal con un tipo y datos necesarios para la acción. En esta ocasión vamos a crear el action creator para la acción FILTER_MARKETS. En el archivo /src/CryptoMarkets/cryptoMarketsState.js agregamos al final lo siguiente

export const searchMarket = (filter) => ({ type: 'FILTER_MARKETS', payload: filter });

Y ahora en nuestro componente /src/CryptoMarkets/CryptoMarkets.js

import Table from "./Table";
import SearchField from "./SearchField";
import { useSelector, useDispatch } from "react-redux";
import { searchMarket } from "./cryptoMarketsState";
export default function CryptoMarkets() {
  const dispatch = useDispatch();
  const headers = useSelector((state) => state.headers);
  const rows = useSelector((state) => state.filtered);
  function onSearchMarket(e) {
    dispatch(searchMarket());
  }
  return (
    <>
      <SearchField onSearchChange={onSearchMarket} />
      <Table headers={headers} rows={rows}></Table>
    </>
  );
}

Tercera iteración, obteniendo datos de la API simulada

En esta iteración vamos a simular las peticiones del API para dejar de usar los rows del archivo /src/CryptoMarkets/dummies.js.

Respuesta json de bitso

Simplemente en tu navegador pega la siguiente url:

https://bitso.com/api/v3/ticker?book=all

Mercados de bitso
Mercados de bitso

Ahora copia el json que te respondió y pégalo en un nuevo archivo llamado /api/bitso-markets.json.

Luego crea un archivo llamado /api/index.js, ahí vamos a agregar la función que simule la petición.

import markets from "./bitso-markets.json";
export function fetchMarkets() {
  return new Promise((resolve) => {
    setTimeout(() => resolve(markets), 1000);
  });
}

En el código anterior se importa el json en una variable llamada markets y se crea una promesa con un temporizador de un segundo, después del segundo la promesa se resuelve retornando los datos de los mercados (contenidos en la variable markets).

Usar redux-thunk para obtener los markets

¿Qué es un thunk?

Su definición en el ámbito de programación en general:

Los thunks se utilizan principalmente para retrasar un cálculo hasta que se necesite su resultado.

https://en.wikipedia.org/wiki/Thunk

En react se utiliza para escribir funciones en redux con lógica avanzada relacionadas con el estado de la aplicación. Donde se puede trabajar con operaciones tanto asíncronas como síncronas. Estas funciones thunk tienen acceso a las funciones dispatch() y getState() de redux, por esta razón un thunk internamente puede disparar cualquier numera de acciones y obtener los datos actuales del estado por si son necesarios en su lógica de programación.

Crear un Thunk action creator

En realidad un thunk se usa como se usan los action creators, por ese se les llama Thunk action creator.

// actions
export function getMarkets() {
  return async (dispatch) => {
    const rawMarkets = await fetchMarkets();
    dispatch({ type: "RECEIVE_MARKETS", payload: rawMarkets.payload });
  };
}

En el codigo de arriba, el action creator regresa una función asíncrona que recibe solo la función dispatch (no vamos a necesitar la función getState()). La función comienza pidiendo los markets y una vez obtenidos, dispara la acción RECEIVE_MARKETS.

Actualizar reducer para entender la acción RECEIVE_MARKETS

En el archivo /CryptoMarkets/cryptoMarketsState.js agregamos él case para la acción RECEIVE_MARKETS. El código de este archivo debe quedar como el de abajo.

import { fetchMarkets } from "../api";
export default function cryptoMarkets(state, action) {
  switch (action.type) {
    case "FILTER_MARKETS":
      return {
        ...state,
        filtered: state.all.filter((market) =>
          market.id.includes(action.payload)
        )
      };
    case "RECEIVE_MARKETS":
      return {
        ...state,
        all: action.payload,
        filtered: action.payload,
      };
    default:
      return state;
  }
}
// actions
export const searchMarket = (filter) => ({ type: 'FILTER_MARKETS', payload: filter });
export function getMarkets() {
  return async (dispatch) => {
    const rawMarkets = await fetchMarkets();
    dispatch({ type: "RECEIVE_MARKETS", payload: rawMarkets.payload });
  };
}

Hacer que el store entienda las funciones thunk

Para que el thunk se ejecute correctamente es necesario indicarle al store como tratar este tipo de funciones, para eso necesitamos agregar el middleware de react-thunk. Un middleware extiende o mejora la funcionalidad en un punto medio del software o comunica diferentes puntos de la aplicación, y también puede comunicar aplicaciones diferentes. De hecho en redux también se les llama enhancers.

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducer from "./CryptoMarkets/cryptoMarketsState";
import { headers } from "./CryptoMarkets/dummies";
const initialState = {
  headers,
  all: [],
  filtered: [],
};
export default function configureStore() {
  return createStore(reducer, initialState, applyMiddleware(thunk));
}

Primero importamos la función applyMiddleware, luego importamos el middleware llamado thunk y finalmente aplicamos el middleware en el terecer parametro de createStore.

También cambiamos el initialState, los header los dejamos en el archivo dummies.js. Las propiedades all y filtered las cambiamos a array vacíos.

Invocando el thunk desde el componente CryptoMarkets

Por último, necesitamos invocar nuestro thunk para obtener los markets de nuestro servicio simulado. El archivo /CrytoMarkets/CryptoMarkets.js donde vive el componente padre CryptoMarkets debe quedar como el de abajo.

import Table from "./Table";
import SearchField from "./SearchField";
import { useSelector, useDispatch } from "react-redux";
import { useEffect } from "react";
import { getMarkets, searchMarket } from "./cryptoMarketsState";
export default function CryptoMarkets() {
  const dispatch = useDispatch();
  const headers = useSelector((state) => state.headers);
  const rows = useSelector((state) => state.filtered);
  useEffect(() => {
    dispatch(getMarkets());
  }, [dispatch]);
  function onSearchMarket(e) {
    dispatch(searchMarket(e.target.value));
  }
  return (
    <>
      <SearchField onSearchChange={onSearchMarket} />
      <Table headers={headers} rows={rows}></Table>
    </>
  );
}

Importamos el thunk getMarkets y ocupamos el hook useEffect. Dentro invocamos a getMarkets() para pedir el listado de markets al api simulado.

useEffect(() => {
    dispatch(getMarkets());
  }, [dispatch]);

En este punto nuestra aplicación nos debe mostrar un error porque el formato de las propiedades de los componentes no son iguales a la respuesta del api, pero podemos agregar un console.log(action) en el reducer para comprobar que el api simulado está respondiendo con los datos.

case "RECEIVE_MARKETS":
  console.log(action);
  return {
    ...state,
    all: action.payload,
    filtered: action.payload
  };

El error debe mostrarse asi:

Error, falta de mapeo
Error falta de mapeo

En la imagen de arriba vemos en la consola que recibimos los datos del API simulado. En la siguiente sección eliminaremos este error mapeando correctamente los datos al formato de como los necesita el componente.

Mapeando datos a UI

Para que el error desaparezca, necesitamos mapear los datos obtenidos del servicio a datos que entienda los componentes, para esto creamos el archivo /mappers.js y vamos a crear las funciones de mapeo:

import styles from "./Table.module.scss";
export function mapMarketsProps(markets) {
  return markets.map((market) => {
    const { book, last, high, low, change_24, vwap, volume } = market;
    const vwapNum = Number(vwap);
    const change24Num = Number(change_24);
    return {
      currency: book.split("_")[0],
      market: book.replace("_", "/"),
      variation24hrs: vwapNum > 0 ? vwapNum.toFixed(2) : vwap,
      change24hrs: change24Num > 0 ? change24Num.toFixed(2) : change_24,
      last: Number(last).toFixed(2),
      high: Number(high).toFixed(2),
      low: Number(low).toFixed(2),
      volume: Number(volume).toFixed(2),
    };
  });
}
export function mapMarketsToUi(rawMarkets) {
  const mappedMarkets = mapMarketsProps(rawMarkets.payload);
  return mappedMarkets.map((ticker) => {
    return {
      id: ticker.market,
      className: styles.row,
      items: [
        { value: ticker.market, className: styles.item },
        { value: ticker.currency, className: styles.item },
        { value: ticker.last, className: styles.item },
        { value: ticker.volume, className: styles.item },
        { value: ticker.high, className: styles.item },
        { value: ticker.low, className: styles.item },
        { value: ticker.variation24hrs, className: styles.item },
        { value: ticker.change24hrs, className: styles.item }
      ]
    };
  });
}

Aquí ya podemos usar esas funciones de mapeos en nuestro componente contenedor:

import Table from "./Table";
import SearchField from "./SearchField";
import { useSelector, useDispatch } from "react-redux";
import { useEffect } from "react";
import { getMarkets, searchMarket } from "./cryptoMarketsState";
import { mapMarketsToUi } from "../mapppers";
export default function CryptoMarkets() {
  const dispatch = useDispatch();
  const headers = useSelector((state) => state.headers);
  const rows = useSelector((state) => state.filtered);
  useEffect(() => {
    dispatch(getMarkets(mapMarketsToUi));
  }, [dispatch]);
  function onSearchMarket(e) {
    dispatch(searchMarket(e.target.value));
  }
  return (
    <>
      <SearchField onSearchChange={onSearchMarket} />
      <Table headers={headers} rows={rows}></Table>
    </>
  );
}

Y modificamos nuestro thunk de la siguiente forma.

export function getMarkets(mapToUi) {
  return async (dispatch) => {
    const rawMarkets = await fetchMarkets();
    const uiMappedMarkets = mapToUi(rawMarkets);
    dispatch({ type: "RECEIVE_MARKETS", payload: uiMappedMarkets });
  };
}

Ahora la función de mapeo es recibida por nuestro thunk, de esta manera, podemos utilizar diferentes mapeos para diferentes tipos de componentes. Imagínate que tenemos otra pantalla donde necesitamos mostrar la misma información en una lista de cards, ¿Verdad que el mapeo es diferente? ´Todo lo demás se deja intacto y solo cambiamos el mapeo.

Para esta pantalla donde tenemos una tabla, el resultado es el siguiente:

Resultado final parte 1
Error arreglado

Estilos en campo de búsqueda

Un toque final, vamos a mejorar el campo de búsqueda creando estos estilos:

.label {
  display: inline-flex;
  flex-direction: column;
  font-weight: bold;
  font-size: 1.2rem;
  margin-bottom: 1rem;
}
.input {
  padding: 8px;
  font-size: 1.2rem;
}

En el componente importamos y aplicamos estilos:

import styles from "./SearchField.module.scss";
export default function SearchField({ label, onSearchChange }) {
  return (
    <label className={styles.label}>
      {label}
      <input type="search" onChange={onSearchChange} className={styles.input} />
    </label>
  );
}

Ahora si tenemos el resultado final:

Buscador parte 1 terminada
Buscador parte 1 terminada

Conclusión. Más iteraciones

Todavía quedan más cosas por hacer y mejorar, al igual que hicimos en esta publicación incrementando en iteraciones pequeñas hasta conseguir que funcione el buscador, así tendremos más publicaciones para continuar mejorando e incrementando aún más las funcionalidades.

React Hooks, useState y useEffect

React Hooks, useState y useEffect

¿Qué son los React Hooks?

Los React Hooks, son funciones que se ejecutan en un determinado punto en la vida de un componente funcional. Permiten usar características de React, sin la necesidad de usar clases. Por ejemplo te permite agregar state a un componente funcional.

También permite controlar efectos colaterales en caso de ser necesarios. Por ejemplo, peticiones a servicios, subscribirse a eventos, modificar el DOM, logging o cualquier otro código imperativo.

¿Por qué funciones y componentes funcionales?

Si necesitas más detalles sobre como son las funciones en Javascript, puedes revisar estas dos publicaciones:

  1. Funciones en Node.js y JavaScript. Lo realmente importante
  2. Funciones en Node.js y Javascript. Más detalles

Organización y reutilización

Se sabe que las funciones son la unida esencial para la organización y reutilización de código, es decir, las funcionalidades de cualquier software.

Al utilizar funciones se elimina la complejidad de usar clases. No necesitas usar this, constructores, ni separar funcionalidades estrechamente relacionadas en varios métodos del ciclo de vida de un componente en React.

Si has creado componentes en React, de seguro has aprendido ver a los componentes como una función y sus propiedades como sus parámetros. ¿Por qué no hacerlo más transparentes al usar solamente funciones?

De hecho las clases, surgieron de funciones. Como se explica aquí y aquí. No necesitamos clases y nos ahorramos muchos dolores de cabeza.

Composición

Es mucho más simple y flexible utilizar funciones en lugar de clases para la composición, tanto de componentes como de cualquier funcionalidad en general.

Mejor composición de objetos sobre herencia de clases

Design Patterns: Elements of Reusable Object-Oriented Software

Simplicidad

Normalmente con las clases, el flujo del código de los efectos colaterales necesita brincar de un método del ciclo de vida a otro, con React Hooks esto es más lineal y fácil de leer. También la definición del estado de un componente es mucho más simple, sin necesidad de definirlo en el constructor.  En el siguiente punto viene un ejemplo de como los React Hooks son más simples.

Se elimina el uso de componentes de alto nivel

Aunque los componentes de alto nivel (HOC) se basan en las funciones de alto nivel, la naturaleza del código HTML y clases hace que esto sea complejo. El código se vuelve difícil de leer y también provoca que los componentes envolventes se aniden demasiado.

Los React Hooks resuelven el famoso “HOC hell”, muy parecido a como las promesas y funciones asíncronas resuelven el “Callback hell”.

HOCs, complejo
HOCs, complejo

Ahora, si se utilizan React Hooks, esos HOC complejos se convierte en un código más simple y lineal.

React hooks, simple y lineal
React hooks, simple y lineal

Más rápido

Es un hecho que las funciones tienen mayor rendimiento que las clases. En el caso de React, un componente de función es más rápido que un componente de alto nivel. Además, usando los React hooks adecuadamente, los componentes con React hooks suelen ser más rápidos que los componentes de clases en React.

Fácil de probar

Haciendo tus pruebas correctas evitando detalles de implementación, tu react hook debería estar cubierto con tus pruebas del componente funcional. En otras publicaciones veremos cómo hacer esto. En caso de que tengas un React Hook complejo, existen herramientas que te facilitan estas pruebas aisladas o también podemos hacer a mano nuestro wrapper que use nuestro React Hook para cada uno de sus casos.

Las dos reglas importantes de React Hooks

Solo ejecuta React Hooks en el nivel superior de la función

La clave de esta regla es que React depende del orden en que se ejecutan los React Hooks.

No ejecutes React Hooks dentro de condiciones, ciclos o funciones anidadas porque se necesita asegurar el orden correcto de los hooks cada vez que el componente se renderiza. Esto permite que React controle correctamente el estado entre multiples useState y useEffect.

Si existe alguna condición en el código, y permite que a veces un hook se ejecute y otras no, el orden se pierde al igual que los datos correctos del estado. Hay que aclarar que las condiciones, ciclos y funciones anidadas dentro de los hooks como en el caso de useEffect si son posibles.

// No hagos esto!!
if (nombre) {
  useEffect(() => {
    document.title = `Algo con ${nombre}`;
  }, [nombre]);
}
// Esto si lo puedes hacer
useEffect(() => {
  if (nombre !== '') {
    document.title = `Algo con ${nombre}`;
  }
}, [nombre]);

Solo ejecuta React Hooks dentro de funciones de React

  1. Ejecuta React Hooks dentro de componentes de función.
    1. En las clases los React Hooks no funcionan, además en clases ya existen los métodos del ciclo de vida de un componente en React.
  2. Ejecuta React Hooks dentro de otros React Hooks (puedes hacer tus propios hooks).
    1. Para garantizar el orden y controlar correctamente el estado entre múltiples Hooks. Te permite ejecutar react hooks al inicio de otro hook creado por ti.

Reac Hooks, useState(initialState)

Empecemos con el hook más utilizado, useState. Este hook proporciona la misma capacidad de los componentes de clases para tener un estado interno. Vamos a hacer un formulario de registro de un usuario nuevo, algo sencillo y genérico, nada de detalles de implementación específicos. Probablemente con el campo nombre será suficiente.

Vamos a utilizar la herramienta de codesandbox.io, usando la plantilla de React. Agregamos un archivo RegistroDeUsuario.js

import { useState } from "react";
export default function RegistroDeUsuario() {
  const [nombre, setNombre] = useState("");
  return (
    <form>
      <label htmlFor="nombre">Nombre</label>
      <input
        type="text"
        value={nombre}
        id="nombre"
        onChange={(e) => setNombre(e.target.value)}
      />
      <button type="submit">Enviar</button>
      <section>{nombre}</section>
    </form>
  );
}

Y en el archivo App.js importamos el componente RegistroDeUsuario

import RegistroDeUsuario from "./RegistroDeUsuario";
export default function App() {
  return (
    <div className="App">
      <RegistroDeUsuario />
    </div>
  );
}

Desctructuring assignment

Primero, la función useState(initialState), regresa un arreglo de dos elementos, el valor del estado (inicializado) que queremos controlar y un método para modificar ese estado. Este par de valores se asignaron a las constantes nombre y setNombre con la sintaxis de destructuring assignment.

El desctructuring assignment de la línea 4 se traduce a lo siguiente.

const statePair = useState('');
const nombre = statePair[0];
const setNombre = statePair[1];

Invocaciones y renderizados

Cada vez que se escribe sobre el campo Nombre, se vuelve a renderizar el componente. Pero React guarda el estado entre renderizados. Se puede notar como la línea 5 se ejecuta al primer rénder (Montar, con texto vacío) y también cada vez que se actualiza el valor de nombre (Actualizar, revisa la consola de la parte derecha de la imagen de abajo).

El resultado debe ser algo como lo siguiente.

Renderizados por useState input Nombre
Renderizados por useState input Nombre

El flujo de useState es el siguiente. Modificado de este original.

Flujo useState
Flujo useState

El valor de nombre se actualiza a través del evento onChange y el método setNombre. Al modificar este estado interno, provoca la ejecución de la función RegistroDeUsuarios y un “re-renderizado”. Si el componente se renderiza debido a un cambio a otro estado u otra propiedad, el estado de nombre permanece con la última actualización.

useState(() => { return initialState; })

useState(initialState) puede recibir una función que regrese el estado inicial usado en el primer render. Un ejemplo de su uso es el que sige, ¿Qué podemos hacer si queremos guardar y obtener el estado de localStorage?

import { useState } from "react";
export default function RegistroDeUsuario() {
  const [nombre, setNombre] = useState(() => {
    console.log("Solo una vez");
    return localStorage.getItem("nombre") || "";
  });
  console.log("Más invocaciones");
  function actualizarNombre(e) {
    setNombre(e.target.value);
    localStorage.setItem("nombre", e.target.value);
  }
  return (
    <form>
      <label htmlFor="nombre">Nombre</label>
      <input
        type="text"
        value={nombre}
        id="nombre"
        onChange={actualizarNombre}
      />
      <button type="submit">Enviar</button>
      <section>{nombre}</section>
    </form>
  );
}

Ahora usamos una función para definir el estado, le agregamos un console.log('Solo una vez') para demostrar que la función solo se ejecuta una vez. Y un console.log('Más invocaciones') para demostrar que en los siguientes invocaciones ya no se ejecuta la función de nuestro useState(initialState), pero si el de Más invocaciones.

En el resultado de abajo, escribí Jaime en el campo nombre, luego recargue la página, revisa el lado derecho en la vista y la consola.

useState, campo nombre en localStorage
useState, campo nombre en localStorage

Al recargar se imprime Sola una vez y al empezar a escribir Cervantes se imprime Más invocaciones un total de 11 veces. Mi nombre jaime, lo obtuvo del localStorage al primer renderizado.

setState(prevState => {})

El método para actualizar el estado setNombre también puede recibir una función, cuyo parámetro es el valor anterior. Veamos un ejemplo modificando la función actualizarNombre.

function actualizarNombre(e) {
  setNombre(nombreAnterior => {
    console.log(nombreAnterior); // '', 'J', 'Ja', 'Jai', 'Jaim'
    return e.target.value;
  });
  localStorage.setItem("nombre", e.target.value);
}

La función setNombre obtenida de useState recibe como parámetro el nombreAnterior, y al imprimir en la consola nos damos cuenta de que siempre imprimirá el valor anterior del estado nombre.

Actualizar state pasando una función
Actualizar el estado pasando una función

useEffect(effectFn, [deps])

Este React hook, useEffect, nos permite ejecutar y controlar efectos colaterales, como pueden ser peticiones a servicios, subscribirse a eventos, modificar el DOM o cualquier funcionalidad que no pueda ejecutarse en el cuerpo de nuestro componente función porque no pertenece al flujo lineal del mismo.

Flujo de useEffect

La función effectFn se ejecuta después de que el navegador ya ha pintado el componente en pantalla por primera vez (montar). También por defecto después de cada posterior repintado (actualizar). Este comportamiento descrito tiene el mismo propósito que los métodos componentDidMount y componentDidUpdate.

El tercer propósito en useEffect se le llama limpieza, el cual lo podemos comparar con componentDidUnmount. En el primer pintado (montar) la función de limpieza no se ejecuta, solo se ejecuta en la fase de actualizar. Es decir, se ejecuta después de cada repintando, pero antes del que el cuerpo de useEffect se ejecute. Este caso en específico se explica mejor con ejemplos en esta publicación.

El flujo de useEffect es el siguiente. Modificado de este original.

Flujo useEffect
Flujo useEffect

useEffect recibe un segundo parámetro, deps, el cual es un Array con la lista de dependencias que permiten decidir si ejecutar el efecto colateral o no, después de cada repintado. Sirve bastante para mejorar el rendimiento si no queremos que después de cada repintado se ejecute effectFn.

Si te das cuenta, he usado la palabra pintado en lugar de renderizado. Esto se debe a que efectivamente el efecto se ejecuta después de que los cambios ya estén pintados en el navegador web. El renderizado involucra al Virtual DOM, y React decide en que momento es conveniente actualizar el DOM, pero el pintado sucede un poco después de lo anterior. Aclaremos que aunque se actualice el DOM, el navegador debe calcular estilos y el layout de los elementos para posteriormente realizar el pintado de los pixeles.

useEffect(effectFn)

Veamos un ejemplo, aquí la idea es tener un input de búsqueda y queremos que lo que tengamos en el input se imprima en título de la pestaña de nuestro navegador web. Para poder realizar esto necesitamos un efecto utilizando la API del DOM.

De nuevo, con la herramienta codesandbox.io y su plantilla de react, creamos un nuevo proyecto. Agregamos un archivo llamado OldNewsPapers.js donde vivirá nuestra funcionalidad en forma de un componente funcional.

import { useEffect, useState } from "react";
export default function OldNewsPapers() {
  const [query, setQuery] = useState("");
  useEffect(() => {
    console.log("document.title");
    document.title = `Periodicos viejos ${query}`;
  });
  console.log('Invocación);
  return (
    <>
      <h1>Periódicos viejos que contienen {query}</h1>
      <form>
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
        />
      </form>
    </>
  );
}

Este efecto se ejecuta después de la primera vez que se pinta el componente, esto es el primer propósito que se puede comparar con componentDidMount. ¿Cómo es el flujo?

  1. Se ejecuta la función, es decir, el componente funcional.
    1. Lo cual inicializa el estado de query y el efecto colateral.
  2. Se renderiza y se pinta el elemento de react en el navegador con el valor inicial de query = ''.
    1. Texto “Periódicos viejos que contienen” en el <h1>.
  3. Se ejecuta el efecto colateral después del primer pintado.
    1. Texto “Periódicos viejos que” en el título de la pestaña.
useEffect flujo al montar
useEffect flujo al montar

Si escribimos “texas” en el input, ahora el flujo es de la siguiente manera

  1. Cada vez que se introduce una letra en el input (cinco veces más, por las cinco letras de “texas”)
    1. El estado de query cambia debido al setQuery en el onChange, provocando un nuevo pintado (invocación del componente función, renderizado y finalmente pintado).
    2. Después del pintado se actualiza document.title, cambiando el título de la pestaña del navegador web.
useEffect, flujo actualizar document.title
useEffect, flujo actualizar document.title

En la imagen de arriba vemos seis “document.title”, como describimos al principio, por defecto el useEffect se invoca después de cada pintado en el navegador web.

Puedes ver el código completo en aquí.

useEffect(effectFn, [deps])

En el último ejemplo nuestro efecto se va a ejecutar después de cada pintado, incluso si el estado de query no ha cambiado. Para comprobar esto vamos a agregar otro estado para llevar la cuenta del número de invocaciones de nuestro componente funcional, que se traduce en el número de renderizados realizados. Estas invocaciones las haremos a través de un botón.

import { useEffect, useState } from "react";
export default function OldNewsPapers() {
  const [query, setQuery] = useState("");
  const [invocations, setInvocations] = useState(1);
  useEffect(() => {
    console.log("document.title");
    document.title = `Periódicos viejos ${query}`;
  });
  return (
    <>
      <h1>Periódicos viejos que contienen {query}</h1>
      <p>{invocations} Invocaciones</p>
      <form>
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
        />
        <button
          onClick={(e) => {
            e.preventDefault();
            setInvocations((prev) => prev + 1);
          }}
        >
          Invocar
        </button>
      </form>
    </>
  );
}

Inicializamos invocations con 1, porque la primera vez que se renderiza será el estado actual. Luego si oprimimos el botón Invocar, se cambia el valor de invocations, se hace otro re-renderizado, y luego se vuelve a ejecutar nuestro efecto, incluso cuando query no ha cambiado.

useEffect por defecto siempre se ejecuta después de cada pintado
useEffect por defecto siempre se ejecuta después de cada pintado

Para evitar que se ejecute demasiadas veces nuestro efecto, podemos indicarle que corra solo cuando una de sus dependencias ha cambiado. En este caso para evitar que se le asigne a cada rato el valor a documen.title, le indicamos que solo lo haga cuando query cambia.

import { useEffect, useState } from "react";
export default function OldNewsPapers() {
  const [query, setQuery] = useState("");
  const [invocations, setInvocations] = useState(1);
  useEffect(() => {
    console.log("document.title");
    document.title = `Periódicos viejos ${query}`;
  }, [query]);
  return ( ... );
}

Ahora podemos ver que aunque hicimos muchas invocaciones con el botón “invocar”, document.title solo se ejecutó la primera vez.

useEffect ejecutar solo cuando alguna "dep" cambie
useEffect se ejecuta solo cuando alguna “deps” cambie

useEffect(effectFn, [])

Cuando se especifica un Array vacío en las deps, effectFn solo se ejecuta una sola vez, después del primer pintado.

import { useEffect, useState } from "react";
export default function OldNewsPapers() {
  const [query, setQuery] = useState("");
  const [invocations, setInvocations] = useState(1);
  useEffect(() => {
    console.log("document.title");
    document.title = `Periódicos viejos ${query}`;
  }, []);
  return ( ... );
}

Las siguientes veces que se actualice query escribiendo en el input, el título de la pestaña ya no se actualiza.

useEffect con deps vacío
useEffect con deps vacío

Conclusión sobre

Las funciones han existido desde mucho antes de la programación, gracias al cálculo lambda de Alonzo Church. No es extraño que en los últimos años el desarrollo de software ha volteado hacia la programación funcional debido a la simplicidad y el poder expresivo. Resolviendo varios problemas en el camino.

Y bueno, con el uso de React Hooks, se ha dado un paso muy importante debido a los beneficios que es programar de esta manera, desde hace años que se utilizaban componentes funcionales, y ahora con esto creo que React tiene más futuro prometedor por delante.

Hemos entendido, con suficiente profundidad (para comenzar con hooks), como funcionan los flujos de los Hooks useState y useEffect. De useEffect aún quedan temas por ver, así como también los React Hooks personalizados. Aquí pondré el enlace con los temas pendientes, cuando estos estén publicados. Cualquier duda, no dudes en escribirla en los comentarios, ¡Estaremos contentos de ayudarte!.

Referencias

https://reactjs.org/docs/hooks-intro.html

https://reactjs.org/docs/hooks-state.html

https://reactjs.org/docs/hooks-effect.html

Foto de fondo de portada por Artem Sapegin en Unsplash

Funciones en Node.js y Javascript. Más detalles

Funciones en Node.js y Javascript. Más detalles

“Funciones en node.js y Javascript. Más detalles”, es la segunda parte de lo más importante de las funciones en node.js y Javascript. El tema de funciones es muy importante, y también muy extenso. Aprenderás sobre el prototipo de una función, el comportamiento de la referencia a this, propiedad especial prototype, elevado de funciones y sus beneficios, variable adicional arguments y métodos útiles como call y apply. Y como extra muy importante, si llegas al final, las mejores recomendaciones de comunicación a través del código.

El prototipo de una función en Node.js y Javascript

En la primera parte de Funciones en Node.js y Javascript. Pudimos entender que las funciones son objetos, y el porqué las funciones son objetos. Una función tiene un prototipo, si no sabes que es un prototipo en Javascript, esto explica en esta publicación.

Las funciones tienen como prototipo a Function.prototype. Aunque a diferencia de los objetos literales no se ven tan clara la relación, podemos ejecutar la siguiente prueba.

function saludar() {
  return 'Hola';
}
saludar.__proto__ = Function.prototype; // true

El prototipo de Function.prototype es Object.prototype. Con esta última sentencia comprobamos lo que hemos dicho de los objetos en Javascript, tienen como objeto base a Object.prototype.

saludar.__proto__.proto === Object.prototype; // true;

Aquí el diagrama.

Cadena de prototipos de una función
Cadena de prototipos de una función

Tipos de invocaciones y referencia this

Las funciones en Node.js y Javascript se comportan de manera extraña al ser invocadas. Este comportamiento cambia según la forma en como se invoque e influye en como se trata la referencia this. En las siguientes cuatro secciones se explican estos tipos de invocaciones.

Invocación de funciones en Node.js y Javascript como método

Cuando una función se asigna a una propiedad de un objeto entonces decimos que es un método. Y cuando un método es invocado se puede hacer referencia al objeto al que pertenece a través de la palabra reservada this.

Como vimos en el tema de objetos, se puede acceder a una propiedad a través de corchetes, así ['nombreDePropiedad'].

const jaime = {
  nombre: 'Jaime',
  saludar: function() {
    return `Hola soy ${this.nombre}`;
  }
};
jaime.saludar(); // 'Hola soy Jaime'
jaime['saludar'](); // 'Hola soy Jaime'

Invocación de funciones en Node.js y Javascript como función

Cuando una función se invoca de esta manera y quiere hacer referencia a this, este debería obtener null o undefined, pero en lugar de eso, hace referencia al objeto global del ambiente donde se está ejecutando el código, en el caso del navegador web hace referencia a window. En el caso de node.js es el objeto global.

function saludar() {
  return this;
}
saludar(); // En el navegador es window y en node.js es global

Dentro del navegador web, es algo como lo siguiente.

Window {
  alert: ƒ alert()
  console: console {},
  fetch: ƒ fetch(),
  isNaN: ƒ isNaN(),
  clearInterval: ƒ clearInterval(),
  clearTimeout: ƒ clearTimeout(),
  setInterval: ƒ setInterval(),
  setTimeout: ƒ setTimeout()
  ...
}

Por otro lado en node.js es algo como esto.

<ref *1> Object [global] {
  global: [Circular *1],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  },
  queueMicrotask: [Function: queueMicrotask],
  performance: [Getter/Setter],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  }
  ...
}

Ahora, si una función interna hace referencia a this.

function saludar() {
  function saludarInternamente() {
  	return this;
  }
  return saludarInternamente();
}
saludar(); // En el navegador web es window, en Node.js es global

Esta función interna sigue haciendo referencia al objeto window o global. Y lamentablemente este mismo comportamiento sucede aun si la función saludar es el método de un objeto.

const jaime = {
  nombre: 'Jaime',
  saludar: function() {
    function saludarInternamente() {
      return this;
    }
    return saludarInternamente();
  }
};
jaime.saludar();  // En el navegador web es window, en Node.js es global

Cuando el objeto se crea a través de una clase, la referencia a this es undefined, esto es una mejora, sin embargo, ¿Tendría más sentido que si hiciera referencia al objeto en cuestión? Yo opino que si, pero ese ejemplo vamos a guardarlo para cuando toquemos las clases.

Invocación de funciones en Node.js y Javascript como constructor

Javascript es un lenguaje de programación orientado a objetos basado en prototipos. Originalmente no usa clases, pero intenta simular una sintaxis basada en clases. Estamos hablando del operador new.

Las funciones constructoras son funciones que se invocan con el operador new para crear nuevos objetos. Como el ejemplo de abajo.

function Persona(nombre, edad) {
  this.nombre = nombre;
  this.edad = edad;
}
const jaime = new Persona('Jaime', 33);

Esta forma de crear objetos con una función constructora permite que la referencia a this esté correctamente enlazada al nuevo objeto. Lo podemos comprobar cuando ejecutamos las siguientes líneas.

jaime.nombre; // 'Jaime'
jaime.edad; // 33

Propiedad especial prototype

Existe una propiedad especial llamada prototype en las funciones, esta no es la misma propiedad oculta que enlaza a Function.prototype.

Esta propiedad especial prototype permite usar a una función constructora para simular la herencia clásica. Así cuando se crea un nuevo objeto usando la función constructora, este nuevo objeto pueda usar las propiedades de prototype. Esto propiedad básicamente funciona igual que las propiedades Object.prototype, Function.prototype, Array.prototype, y demás prototipos de objetos en Javascript. Con la diferencia, que nosotros agregamos el contenido manualmente a prototype. A continuación un ejemplo.

function Persona(nombre, edad) {
  this.nombre = nombre;
  this.edad = edad;
}
Persona.prototype.saludar = function () {
  return `Hola soy ${this.nombre} y tengo ${this.edad} años`;
};
Cadena de prototipos de una función y propiedad especial prototype
Cadena de prototipos de una función y propiedad especial prototype

Y con este ejemplo comprobamos que la función saludar invocada desde el objeto jaime, efectivamente tiene la referencia a this enlazada correctamente. Y puede acceder al nombre y edad del nuevo objeto jaime.

const jaime = new Persona('Jaime', 33);
jaime.saludar(); // Hola soy Jaime y tengo 33 años

Visualmente es como el siguiente diagrama.

Cadena de prototipos de un objeto creado con una función constructora
Cadena de prototipos de un objeto creado con una función constructora

Por último te preguntarás, ¿Por qué no agregamos los métodos dentro de la función constructora Persona? Muy parecido a lo que hicimos cuando vimos el tema de closure.

function Persona(nombre, edad) {
  this.nombre = nombre;
  this.edad = edad;
  this.saludar = function () {
    return `Hola soy ${this.nombre} y tengo ${this.edad} años`;
  };
}
const jaime = new Persona('Jaime', 33);
jaime.saludar(); // Hola soy Jaime y tengo 33 años

Pues la respuesta es que también se puede hacer, pero la desventaja es que estaríamos creando nuevas funciones cada vez que creamos una instancia de Persona. Con el uso de la propiedad prototype no se crean nuevas funciones, solo se hacen referencia a ellas, es decir, cada nuevo objeto debe ir a buscar en sus prototipos a la función saludar.

Nota importante sobre las funciones constructoras

Las funciones constructoras siempre empiezan con una letra mayúscula, no porque la sintaxis del lenguaje lo necesite, sino para que los programadores identifiquen a una función constructora de una que no lo es y se use adecuadamente el operador new.

Invocación de funciones en Node.js y Javascript con apply y call

Como ya hemos hecho mucho hincapié, las funciones en Node.js y Javascript son objetos. Por lo tanto una función también tiene métodos. Los métodos del prototipo Function.prototype.apply y Function.prototype.call, permiten definir explícitamente la referencia a this al ejecutarse el cuerpo de la función.

Invocación con Function.prototype.apply

const jaime = {
  nombre: 'Jaime',
  saludar: function(saludo) {
    return `${saludo}, soy ${this.nombre}`;
  }
};
jaime.saludar('Hola'); // Hola, soy Jaime
jaime.saludar.apply({ nombre: 'Pepito' }, ['Buenos días']); // Buenos días, soy Pepito

El método apply recibe como primer parámetro el objeto al que se va a enlazar la función y podrá hacer referencia usando this y como segundo parámetro un arreglo de valores que son los parámetros de la función. En este caso la función saludar solo recibe una cadena de caracteres a través del parámetro saludo. Es por eso que solo necesita un arreglo con un solo elemento.

Aquí un ejemplo con una función de dos parámetros.

function sumar(a, b) {
  return a + b;
}
sumar.apply(null, [1, 2]); // 3

En este ejemplo el primer parámetro del método apply es null porque la función no se enlaza a un objeto y no necesita usar la referencia this.

Invocación con Function.prototype.call

Este método es una simplificación del método apply, igualmente recibe dos parámetros, el objeto al cual se hará referencia con this y los argumentos de la función que va a invocar. La única diferencia es que en lugar de recibir un array de parámetros para la función que se va a invocar, recibe cualquier número de parámetros separados con comas. A continuación un ejemplo.

function sumar(a, b) {
  return a + b;
}
sumar.call(null, 1, 2); // 3

Parámetro extra arguments de las funciones en Node.js y javascript

Cuando una función es invocada, adicional a la lista de parámetros, tiene acceso a un parámetro llamado argument. Este parámetro extra contiene la lista de todos los parámetros pasados en la invocación de una función.

function saludar(complemento) {
  return arguments;
}
saludar('Hola');
/*
Arguments [
  0: "Hola",
  callee: ƒ saludar(saludo),
  length: 1,
  Symbol(Symbol.iterator): ƒ values(),
  [[Prototype]]: Object
]
*/

arguments es un objeto muy parecido a un Array, pero no lo es. En el ejemplo anterior vemos que su prototipo es Object.prototype y para ser un Array debería ser Array.prototype.

Debido a lo anterior, arguments no tiene todos los métodos útiles de un Array. Por ejemplo, que tal si queremos recorrer la lista de todos los parámetros, en un arreglo se podría usar el método Array.prototype.forEach, pero eso no es posible.

Una forma de mitigar este inconveniente es convertir el objeto arguments a un arreglo, usando el método Array.from. Y entonces si usar los métodos de Array.prototype que necesites. Ejemplo.

function saludar(complemento) {
  const args = Array.from(arguments);
  args.forEach(actual => console.log(actual));
}
saludar('Hola', 'Soy Jaime', 'Tengo 33 años');
/*
Hola
Soy Jaime
Tengo 33 años
*/

Aunque la función saludar solo recibe un parámetro, aun así todos los parámetros que se pasen en la invocación, se guardan en arguments.

Elevado de funciones en Node.js y JavaScript

El elevado se refiere a que la creación de función sube hasta el inicio del ámbito en donde se encuentre. Solo la declaración normal de función puede ser elevada. Ejemplo.

function saludar(complemento) {
  return agregarComplemento(complemento);
  function agregarComplemento(complemento) {
    return `Hola, ${complemento}`;
  }
}
saludar('soy Jaime'); // 'Hola, soy Jaime'

Como podemos ver la función agregarComplemento se puede utilizar antes de su definición, esto es porque javascript antes de ejecutar el código, crea la función al inicio del ámbito local donde se encuentra, el código anterior se traduce antes de ser ejecutado a lo siguiente.

function saludar(complemento) {
  function agregarComplemento(complemento) {
    return `Hola, ${complemento}`;
  }
  return agregarComplemento(complemento);
}
saludar('soy Jaime'); // 'Hola, soy Jaime'

Las funciones en forma de expresión y funciones flecha no cuentan con este elevado de funciones, así que es importante definirlas antes de ser usadas.

Beneficio del elevado de funciones

Este comportamiento parece confuso, pero cuando estás programando con funciones es muy útil. Cuando se programa se tiene funciones principales y funciones secundarias. Para la persona que lee nuestro código muy a menudo es suficiente con visualizar las funciones principales para entender el código, no tiene que perder el tiempo en ver los detalles de las funciones secundarias. Imaginate un archivo con cien líneas de código y compara encontrar las funciones principales en las líneas de inicio contra encontrar las funciones principales al final.

Veamos un ejemplo. Aquí al lector solo le interesa a alto nivel lo que renderiza renderVista.

function renderVista() {
  renderHeader();
  renderMain();
  renderFooter();
}
function renderHeader() {
  renderMenu();
  renderBanner();
  renderSearchForm()
  // ....
}
  function renderMenu() {
    // ...
  }
  function renderBanner()  {
    // ...
  }
  function renderSearchForm()  {
    // ...
  }
function renderMain() {
  renderContent();
  renderAside();
  // ...
}
  function renderContent() {
    // ...
  }
  function renderAside() {
    // ...
  }
  function renderFooter()  {
    // ...
  }

Es facil, ¿Verdad?, solo leemos la primera función e ignoramos el resto del código.

¿Pero qué pasa cuando ahora la función principal se encuentra al final? Ejemplo.

function renderMenu() {
  // ...
}
function renderBanner()  {
  // ...
}
function renderSearchForm()  {
  // ...
}
function renderHeader() {
  renderMenu();
  renderBanner();
  renderSearchForm()
  // ....
}
function renderMain() {
  renderContent();
  renderAside();
  // ...
}
function renderContent() {
  // ...
}
function renderAside() {
  // ...
}
function renderFooter()  {
  // ...
}
function renderVista() {
  renderHeader();
  renderMain();
  renderFooter();
}

Además de ir hasta el final para encontrar la función principal que nos interesa usar, se pierde el sentido de este archivo. O al menos tardamos más en entender el objetivo de este archivo, el cual es renderizar la vista.

Por último, si quisieras, puedes hacer que todo viva dentro de una sola función, utilizando funciones anidadas. Haciendo más claro la relación entre las funciones. Como el siguiente ejemplo.

function renderVista() {
  renderHeader();
  renderMain();
  renderFooter();
  function renderHeader() {
    renderMenu();
    renderBanner();
    renderSearchForm()
    // ....
      function renderMenu() {
      	// ...
      }
      function renderBanner()  {
        // ...
      }
      function renderSearchForm()  {
        // ...
      }
  }
  function renderMain() {
    renderContent();
    renderAside();
    // ...
      function renderContent() {
          // ...
      }
      function renderAside() {
        // ...
      }
  }
  function renderFooter()  {
    // ...
  }
}

¿Las funciones son referencia o valor?

Como ya vimos en la publicación sobre objetos, dado que la función es un objeto, entonces una función siempre será una referencia.

Diferencias importantes entre funciones flecha y tradicionales

Las funciones flechas tienen ciertas limitaciones si las comparamos con las funciones tradicionales, aunque hay que decir que si tu código está enfocado a la programación funcional, este tipo de funciones son muy convenientes.

Sus limitaciones en comparacion con las funciones tradicionales son:

  • No tienen el enlace correcto a this y no puede usar super().
    • Debido al punto anterior no funciona bien con los métodos call, apply y bind.
    • No pude usarse como método de un objeto.
  • No tiene la propiedad especial prototype.
    • Consecuencia del punto anterior no es posible usar una función flecha como función constructora.
  • No tiene acceso a parámetro extra arguments.

Veremos estas limitaciones en una futura publicación sobre las funciones flecha.

Comunicación con funciones en node.js y Javascript

Como ya lo hemos mencionado en otras publicaciones, el código es una forma de comunicación entre lo que creamos ahora con nuestro yo del futuro y nuestro equipo. Sabemos que la programación es una actividad social de mucha comunicación y ser eficientes en esta comunicación ahorra tiempo y dinero a programadores y no programadores.

Recordemos que las funciones son las unidades más pequeñas de organización de código. Además sin las funciones, ¿Cómo definiríamos el comportamiento de objetos y de funciones más grandes? Las funciones nos sirven para:

  • Organizar el código
  • Reusar código
  • Se usa para la composición de objetos, agregando comportamientos.
  • Se usa para la composición de funciones más complicadas

Teniendo en cuenta lo anterior, he aquí algunas recomendaciones a la hora de crear funciones.

Un muy, muy buen nombre

En esta publicación encontrarás las recomendaciones de nombres.

Lo más simple que se pueda

Una función debe ser lo más simple posible, y para eso debe tener una meta pequeña y específica, lo que conlleva a no complicar las cosas y hacer una cosa a la vez, paso a pasito, algo sencillo y bien hecho.

Cuando se resuelve un problema complicado, la forma recomendada de solucionarlo es como si imaginaras que eres un bebe, y tu problema es que no puedes correr. ¿Suena complicado no crees? Probablemente pudiste correr bien sin caerte alrededor de los tres años. Sin darte cuenta dividiste un problema grande en partes más pequeñas. Para llegar a correr, primero aprendiste a gatear, luego a caminar y finalmente intentaste correr con muchas caídas en el camino. Y si dividimos aún más estas tres fases encontraremos objetivos aún más pequeños, ejemplo, dar el primer paso. Así son las funciones, solucionan el problema pedacito por pedacito.

Siguiendo el enfoque anterior, una función simple es la que cumple con lo siguiente.

  • Se enfoca en resolver una sola cosa sencilla pero bien hecha.
  • Tendrá pocas líneas de código
  • Cuando mucho una o dos condiciones if o un switch
  • Cuando mucho uno o dos ciclos for, while, do while, etc.
  • Muy poquita indentación debido a los tres puntos anteriores
  • Por mucho tres parámetros, un humano solo puede recordar cuatro cosas a la vez, hagámosle la vida más fácil a nuestro compañero. Si de verdad necesitas muchos parámetros, puedes utilizar un objeto literal como único parámetro, así al menos no tenemos que recordar el orden en que deben de ir.
  • Todos los puntos anteriores lograrán que la función sea muy fácil de leer y comunicar.

En una futura publicación haremos una aplicación pequeña donde aplicaremos estas recomendaciones

Conclusiones

El tema de funciones es muy grande, aún nos faltan temas por ver. Como nos seguimos dando cuenta, las funciones son una pieza fundamental en Javascript y en el desarrollo de software en general. Según donde se invoque una función, podrá enlazarse al objeto this correcto. El elevado de funciones es beneficioso para la comunicación de las intenciones de nuestras funciones.

No olvidemos que las funciones son objetos. Las recomendaciones expuestas reflejan el principio ágil de simplicidad. Manteniendo las cosas simples, somos más productivos. En el caso de nuestro código, aumentamos el poder de flexibilidad y mantenimiento del mismo a través de una comunicación clara y código fácil de comprender.

Funciones en Node.js y JavaScript. Lo realmente importante

Funciones en Node.js y JavaScript. Lo realmente importante

Al igual que Objetos en Node.js y Javascript. Lo realmente importante. “Funciones en Node.js y JavaScript. Lo realmente importante” se refiere a los principios detrás de las funciones, lo que es realmente importante para continuar aprendiendo, mejorar el entendimiento de funciones en programación, Node.js y en Javascript.

Antes de hablar de funciones en Node.js y JavaScript, vale la pena recordar o definir lo que es una función en programación.

¿Qué son las funciones?

Las funciones son un elemento muy importante en cualquier lenguaje de programación. Sabemos que la programación funcional se inventó incluso antes de cualquier lenguaje de programación basándose en el cálculo lambda de Alonzo Church.

Sintaxis calculo lambda, programación funcional
Sintaxis calculo lambda, programación funcional. Imagen tomada de https://www.slideshare.net/FASTPresentations/introduction-to-lambda-calculus-using-smalltalk-by-facundo-javier-gelatti

En los primeros lenguajes de programación se usaban subrutinas, procedimientos y funciones. Las subrutinas, procedimientos y funciones tienen en común que agrupan un conjunto de operaciones con la finalidad de reutilizarlas muchas veces y solo escribirlas una vez.

Las funciones, a diferencia de los procedimientos y subrutinas, aún se usan en los lenguajes de programación modernos, y son la unida más pequeña de organización de código. Se usan para definir el comportamiento de los objetos, componiéndolos de funcionalidades específicas. Recordemos de otra publicación que los objetos son un grupo de funcionalidades y que contribuyen a la comunicación con otros objetos.

Sin las funciones, un objeto no serviría de mucho. Las funciones definen el comportamiento de objetos. Y también forma funciones más grandes. En conclusión, las funciones nos sirven para.

  • Organizar el código
  • Reusar código
  • Se usa para la composición de objetos, agregando comportamientos.
  • Se usa para la composición de funciones más complicadas

¿Qué son las funciones en Node.js y JavaScript?

En Node.js y JavaScript, y de hecho en cualquier ambiente donde se ejecute JavaScript, las funciones son todo lo descrito en la sección anterior. Y además son objetos.

  • Al ser objetos, pueden tratarse como cualquier otro valor:
    • Pueden ser asignadas a variables y propiedades de otros objetos
    • Crearlas dinámicamente durante la ejecución del código JavaScript
    • Tener sus propiedades y métodos
    • Ser parámetros para otra función
    • Ser el valor de retorno de una función
  • Adicionalmente el cuerpo de una función proporciona ámbito local a las variables y parámetros

Aunque no es el tema de esta publicación, todas las características listadas hacen que JavaScript pueda usarse como un lenguaje de programación funcional.

¿Cómo crear funciones en Node.js y JavaScript?

Las tres formas recomendadas para crear funciones son las siguientes.

  • Declaración normal
  • Función como expresión
  • Funciones flechas

Declaración de función

Este es el método más común, muy similar en otros lenguajes de programación. Se usa la palabra reservada function, seguida del nombre de la función, luego una lista de argumentos entre paréntesis, los cuales se separan por comas. Esta lista de argumentos es opcional.

Por último el cuerpo de la función usando llaves { }. El cuerpo de la función contiene las sentencias que necesites.

function nombre(argumento1, argumento2, ...argumentoN) {
  // sentencias
}

Ejemplo concreto:

function sumar(a, b) {
  return a + b;
}
sumar(1, 2); // 3

Para ejecutar el código de la función es necesario invocarla. La invocación se hace con un par de paréntesis y dentro los argumentos necesarios, separados por comas. Así como el ejemplo anterior.

Una función siempre regresa algún valor, incluso si no se define explícitamente. Si no defines lo que retorna una función, por defecto el valor de retorno será undefined.

function sumarSinReturn(a, b) {
  const resultado = a + b;
}
sumarSinReturn(1, 2); // undefined

Si ejecutamos la función, nos regresa undefined porque no le indicamos explícitamente que regrese algún valor

Función en forma de expresión

Esta forma de crear funciones es mucho más flexible, su definición puede aparecer donde sea que se pueda definir una expresión. Eso le da la habilidad de ser asignada a una variable o a una propiedad de un objeto.

Su sintaxis es igual que la declaración de una función que vimos anteriormente, pero al ser asignada a una variable o propiedad, su nombre es opcional.

function [nombreOpcional](argumento1, argumento2, ...argumentoN) {
  // sentencias
}

A continuación vamos a ver algunos ejemplos.

// Sin nombre, tambie  conocida como funcion anonima
const sumar = function(a, b) {
 return a + b;
};
// Con nombre
const sumar = function sumar(a, b) {
  return a + b;
};
const calculadora = {
  sumar: function(a, b) {
    return a + b;
  }
};
const persona = {
  // como propiedad de un objeto
  comer: function() {
    return 'Comiendo...';
  }
};

Funciones flecha

Las funciones flecha es la forma más nueva de crear funciones, mucho más parecido a las funciones matemáticas del álgebra. Se sienten muy convenientes porque su sintaxis es mucho más reducida. Son una alternativa a las funciones en forma de expresión, es más rápido escribirlas. Sin embargo tiene muchas limitantes en comparación a las otras dos formas de crear funciones. Aunque si las usas para programación funcional son bastante efectivas.

Para ser honesto a mí me parece que si su uso no es enfocado a la programación funcional, sí que le agrega más complejidad al uso de funciones en JavaScript, de por sí las funciones en Node.js y Javascript pueden ser muy diferentes en comparación con otros lenguajes.

Pero bueno, veamos su sintaxis.

argumento => expresión;
// Com mas de un arguento es necesario parentesis
(argumento1, argumentN) => expresión;
// Con varias lineas de sentencias, es necesario las llaves {}
argumento => {
 // sentencias
};
// Con mas de un argumento y con varias lineas se sentencias
(argumento1, argumentoN) => {
  // sentencias
};
// Sin argumentos es necesario los parentesis
() => expresión

Cuando se usan expresiones, no es necesario definir explícitamente el return. El resultado de la expresión es el valor de retorno.

const calcularCuadrado = a => a * a;
const sumar = (a, b) => a + b;
const saludar = () => 'Hola';
// invocaciones
calcularCuadrado(5); // 25
sumar(1, 2); // 3
saludar(); // 'Hola'

Cuando queremos que el cuerpo de la función tenga varias líneas de sentencias, se usan llaves. Además si queremos que la función regrese algún valor, entonces explícitamente usamos la sintaxis del return.

Ejemplos.

const calcularCuadrado = a => {
  const result = a * a;
  return result;
};
const sumar = (a, b) = > {
  const result = a + b;
  return result;
};
// invocaciones
calcularCuadrado(5); // 25
sumar(1, 2); // 3

Funciones anidadas o internas

Una función se puede definir dentro de otra función, es decir, dinámicamente podemos crear funciones internas dentro de otra función principal e invocarlas.

function saludar() {
  function saludarInterna() {
    return 'Hola';
  }
  const saludo = saludarInterna();
  console.log(saludo);
}
saludar(); // 'Hola'

En la siguiente sección veremos otras funciones anidadas.

Ámbito local de funciones

Las funciones en Node.js y JavaScript proporcionan un ámbito de valores, local al cuerpo de la función, es decir, lo que está definido en el cuerpo de la función solo puede ser referenciado dentro de la misma.

function abuelo() {
  const nombre = 'Jaime';
  const apellido = 'Cervantes'
  function padre() {
    const apellido = 'Buendía';
    function hijo() {
      const apellido = 'Pérez';
      const nombreCompleto = `${nombre} ${apellido}`;
      console.log(nombreCompleto); // Jaime Pérez
    }
    hijo();
    const nombreCompleto = `${nombre} ${apellido}`;
    console.log(nombreCompleto); // Jaime Buendía
  }
  padre();
  const nombreCompleto = `${nombre} ${apellido}`;
  console.log(nombreCompleto); // Jaime Cervantes
}
abuelo();

Las funciones internas pueden acceder a las variables de su función padre (por nombrarlas de alguna manera). En el ejemplo anterior se puede notar que la función hijo puede hacer referencia a la constante nombre de la función abuelo. Esto nos produce Jaime Pérez. En la siguiente sección lo explicamos mejor

Closures o cierres

El anidado de funciones permite que las funciones hijas tengan su propio ámbito local, oculto de las funciones padres. Al mismo tiempo estas funciones internas tienen acceso a los valores definidos en las funciones padres. Este encapsulamiento de información y al mismo tiempo acceso a información externa, se le llama closure o cierre.

Continuemos con el ejemplo de la sección anterior, las funciones abuelo, padre e hijo

function abuelo() {
  const nombre = 'Jaime';
  const apellido = 'Cervantes'
  function padre() {
    const apellido = 'Buendía';
    function hijo() {
      const apellido = 'Pérez';
      const nombreCompleto = `${nombre} ${apellido}`;
      console.log(nombreCompleto); // Jaime Pérez
    }
    hijo();
    const nombreCompleto = `${nombre} ${apellido}`;
    console.log(nombreCompleto); // Jaime Buendía
  }
  padre();
  const nombreCompleto = `${nombre} ${apellido}`;
  console.log(nombreCompleto); // Jaime Cervantes
}
abuelo();

El resultado de la invocación de la función abuelo es:

Jaime Pérez --> Desde función hijo
Jaime Buendía --> Desde función padre
Jaime Cervantes --> Desde funcion abuelo

Entre más interna la función, más alcance a todos los ámbitos de las demás funciones “externas padres” tiene. Como la imagen de abajo, es como si los ámbitos de las funciones abuelo y padre estuvieran dentro del ámbito de la función hijo.

Closure, ámbito de funciones en js
Closure, ámbito de funciones en js

Siempre una función tomará el valor de la variable que este más cerca de su propio ámbito local. Las variables dentro de su propio ámbito local son las de mayor relevancia. Esto permite que los nombres de variables y constantes no colisionen entre ámbitos anidados.

La función hijo, tiene acceso a las constantes nombre y apellido de la función abuelo. También tiene acceso a la constante apellido de la función padre. Pero la constante apellido dentro de la propia función hijo está más cerca que lo definido en padre y abuelo, tiene mayor relevancia. Entonces el nombre completo que se imprime en la consola es Jaime Pérez en lugar de Jaime Buendía o Jaime Cervantes.

La función padre si tiene acceso a las constantes nombre y apellido de la función abuelo. En su propio ámbito tiene una constante apellido igual a Buendía. Al estar más cerca este valor, no toma el apellido de la función abuelo que está más lejos. Por eso en la consola el nombre completo que se imprime es Jaime Buendía. Luego la función padre NO tiene acceso a la constante apellido de la función hijo.

Finalmente se imprime en la consola Jaime Cervantes Velasco porque las constantes nombre y apellido están definidas en el propio ámbito local de la función abuelo. La función abuelo NO tiene acceso a las constantes apellido de sus funciones internas padre e hijo.

POO surgio de funciones

Ya que vimos un poco de las funciones anidadas y closures, podemos hablar de como se descubrió la programación orientada a objetos. Esto refleja la importancia de las funciones en los lenguajes de programación.

Ole Johan Dahl y Kristen Nygaard se dieron cuenta de que la pila de llamadas de las funciones en ALGOL, podía ser movida a un Heap. Esto permite que las variables declaradas por una función puedan existir incluso después de que la función termine su ejecución y retorne algún valor.

De esta manera la función se convirtió en el constructor de la clase, las variables locales en propiedades de la instancia de clase y las funciones internas en sus métodos. Y así en 1966 la programación orientada a objetos fue descubierta.

Este comportamiento lo podemos implementar usando funciones en Node.js y JavaScript y aprovechando su habilidad de crear closures.

function crearPersona(nombre, apellido) {
  function saludar() {
    return `Hola soy ${nombre}...`;
  }
  function comer() {
    return 'Comiendo...';
  }
  function getNombre() {
    return `${nombre} ${apellido}`;
  }
  const instancia = {};
  instancia.saludar = saludar;
  instancia.comer = comer;
  instancia.getNombre = getNombre;
  return instancia
}
const jaime = crearPersona('Jaime', 'Cervantes');
jaime.comer(); // Comiendo...
jaime.saludar(); // Hola soy Jaime
jaime.getNombre(); // Jaime Cervantes

Los parámetros nombre y apellido, están dentro del ámbito local de la función crearPersona, así que funcionan igual que variables dentro del cuerpo de la función. Las funciones internas continúan teniendo acceso a esos parámetros incluso después de que la función padre regrese su valor, un objeto literal que es la instancia de una persona.

Luego cuando la instancia jaime invoca a su método getNombre, esta propiedad hace referencia a la función interna getNombre de la función crearPersona. Debido al closure de la función interna getNombre, tenemos acceso a los parámetros nombre y apellido incluso mucho después de que la función padre crearPersona haya regresado su valor.

Nombres de funciones en Node.js y Javascript

Tenemos que estar conscientes de que la programación y el desarrollo de software es una actividad social, de mucha comunicación. Y entre más eficiente sea esta comunicación, mayor es el éxito del software. Esto nos permite ahorrar el tiempo y el recurso económico de todos los involucrados. Estoy hablando de programadores y no programadores, inversionistas, clientes y usuarios.

Una de las formas de comunicación entre compañeros programadores y muchas veces uno mismo en el futuro, es a través de un código fácil de comprender, y para contribuir a esta fácil comprensión debemos de escoger con mucho cuidado el nombre de nuestras funciones.

Toma en cuenta las siguientes recomendaciones. Pero teniendo en mente que solo son ejemplos, y a la hora de escribir tus funciones reales y con el contexto adecuado, muy probable puedas idear mejores nombres de funciones a los aquí mostrados.

Dedica el tiempo suficiente para nombrar tu función.

Igual de importante que nombrar las variables, así las funciones. Las funciones son las unidades más pequeñas que nos permiten definir comportamientos en aplicaciones. El tiempo invertido en nombrar tus funciones es mucho menor que el tiempo que tienes que invertir tu mismo en el futuro y tus compañeros al tratar de descifrar lo que una función realmente hace. Es como organizar tu cuarto, entre más ordenado, más rápido encontraras las cosas que necesitas, más rápido te cambiaras, o más rápido encontraras tus calcetines, etcétera.

El nombre debe ser muy semántico, describir su objetivo

El nombre de una función debe describir con la mayor claridad posible lo que hace. Es importante que sea un verbo porque una función siempre realiza una o más operaciones enfocadas a una tarea en concreto.

Por ejemplo, si una funcion regresa el nombre completo de una persona ¿Cual de los siguientes nombres encaja mejor?

fullName()
getName();
getFullName()

El nombre que describe mejor el objetivo de la función es getFullName.

Si la función regresa un booleano, el nombre de la función debe indicar que el resultado puede ser true o false. Tal cual el resultado de una condición lógica. Es como hacer una pregunta cuyas posibles respuestas solo pueden ser si o no.

hasChildren(person) {
  return Boolean(person.childre.length);
}
if (hasChildren(currentPerson)) {
  // Haz algo
}

Si hasChildren fuera un método, quedaría de la siguiente forma.

if (currentPerson.hasChildren()) {
  // Haz algo
}

Te das cuenta como la condición se lee como una frase muy entendible. Si currentPerson tiene hijos, entonces...haz algo.

Evitar suposiciones erróneas

Si el nombre describe cosas que en realidad no hace, entonces debemos de renombrar esa función. Por ejemplo, si una función forma el nombre completo de un cliente y regresa ese nombre. ¿Qué función evita mejor las suposiciones erróneas?

function setClientName() {} // Se entiende que el nombre del cliente va a ser modificado
function getFullClientName() {} // Aquí esta claro que solo consigue el nomnbre completo del cliente

setClienteName nos indica que el nombre del cliente será modificado, es un mal nombre. Entonces el nombre que mejor evita las suposiciones erróneas es getFullClientName. No dice que forma el nombre, pero sí que va a regresar un nombre completo, para fines prácticos no nos interesa saber como forma ese nombre completo, solo que no los regrese.

Acuerdos con los programadores

Es muy importante establecer acuerdos para el nombramiento de las funciones. En los ejemplos anteriores he estado utilizando el prefijo get que indica que mando a obtener algo. Pero sería confuso que algunos programadores utilizaran la palabra fetch y otros la palabra retrieve, y otros collect o bring.

Usa el contexto adecuado

Es importante que se entienda el contexto de la función, en anteriores ejemplos utilizamos la funcion getFullClientName, pero dependiendo del contexto de la aplicación, pudiera ser mejor getFullUserName o getFullEmployeeName.

Aunque estos nombres tiene que ver con el contexto del negocio o problema, también hay términos técnicos que los programadores ya están muy acostumbrados y no se deben mezclar con el dominio del problema.

Por ejemplo, el patrón de diseño observador contiene métodos como update, subscribe, publish, notify. Si estamos trabajando con la aplicación de una revista que usa notificaciones nativas del celular, sms, y realiza publicaciones periódicas. Esto también puede crear confusiones, así que se deben de nombrar las funciones con mucho cuidado de tal manera que se distingan entre funciones o métodos del patrón de diseño y las otras relacionadas con el negocio.

El alcance de la función ayuda a la longitud del nombre

El nombre de las funciones pude ser largo o corto dependiendo del alcance que tiene en el software. Por ejemplo, una función que se usa mucho en diferentes archivos, vale la pena que su nombre se a corto. Porque si muchas personas lo utilizan, es importante que sea fácil de escribir y pronunciar.

Por otro lado, si es una función que solo se utiliza en un archivo, su nombre puede ser largo, este tipo de funciones normalmente las utilizan internamente las otras funciones con nombre corto. Entonces las funciones de nombres largos son una forma de explicar lo que hace la función de nombre corto. Esto no quiere decir que no pueda ser corta, pero si necesitas más palabras para describir mejor la función, adelante.

Como ejemplo imaginemos una función que regresa el total de tus ganancias a la fecha actual de todo tu portafolio de inversiones. Donde las ganancias son la suma de los intereses de tus inversiones más las contribuciones que has hecho a la fecha.

// funcion corta, reutilizable en otros archivos o aplicaciones
function getEarnings() {
  return calculateEarnings();
}
// funciones con nombre más largo que describen a la funcion corta
function calculateEarnings() {
 const earnings = calculateCurrentTotalInterest();
 const aditionals = calculateCurrentTotalAdditionalContributions();
  return earnings + aditionals;
}
function calculateCurrentTotalInterest() {}
function calculateCurrentAdditionalContributions() {}

No te preocupes tanto por estos ejemplos, el objetivo es que tengas una idea. En una futura publicación haremos una pequeña aplicación donde veremos como aplicar estas recomendaciones.

Conclusiones

Las funciones en Node.js y Javascript es un tema bastante amplio, es una de las cosas mejor hechas en JavaScript que revelan la potencia del lenguaje. Se nota que Javascript está influenciado por LISP y Scheme.

Así mismo no olvidemos de nombrar correctamente nuestras funciones, ellas son las unidades más pequeñas que permiten organizar el software en comportamientos bien definidos.

La programación funcional es el primer paradigma de programación inventado, de ahí la importancia de las funciones, porque es un paradigma del cual la programación orientada a objetos tomó bases.

No olvidemos que las funciones en Node.js y JavaScript son objetos y por lo tanto pueden tratarse como cualquier valor.

Aún nos faltan varios temas importantes sobre las funciones. Estos son abordados en esta publicación. Cualquier duda, no dudes en escribirla en los comentarios, ¡Estaremos contentos de ayudarte!.

Si quieres, como ejercicio, puedes traducir todos los ejemplos a funciones en forma de expresión y funciones flechas. ¡Diviértete!

Objetos en Node.js y Javascript. Lo realmente importante

Objetos en Node.js y Javascript. Lo realmente importante

Introducción

Objetos en Node.js y Javascript. Lo realmente importante” se refiere a los principios detrás de los objetos, lo que es realmente importante para continuar aprendiendo, mejorar el entendimiento de objetos en programación y en Node.js y Javascript.

Empecemos con el entendimiento de lo que es un objeto en programación.

¿Qué son los objetos en programación?

Se ha dicho mucho que la programación orientada a objetos representa el mundo real. La verdad es que la programación en general trata de representar el mundo real en algo digital. Así que la programación orientada a objetos también trata de representar digitalmente el mundo real, solo que con un poco más de énfasis en el uso de objetos. Pero como hemos establecido en esta publicación, lo importante NO son los objetos, lo importante es:

El paso de mensajes para la comunicación entre objetos

En programación un objeto es una forma digital de agrupar funcionalidades y que algunas de ellas pueden ser análogas a las de un objeto en la vida real, pero que es mucho más limitado y no se debe esperar que sean iguales, porque solo es una representación digital.

Cuando desarrollas software, y necesitas agregar una funcionalidad, modificar o arreglar un error, tu pensamiento lógico está basado en la representación de objetos digitales, sus relaciones y comunicaciones con otras entidades digitales. Nunca se debe pensar que como el objeto real se comporta de cierta manera, su representación también. Es de mucha ayuda conceptualmente, pero en la implementación muy a menudo son diferentes.

Recordemos lo que nos dice Alan Kay:

Lamento que hace tiempo haya acuñado el término Objetos para la programación porque hizo que las personas se enfocaran en la parte menos importante. La gran idea es «Envío de mensajes«

Alan Kay

También un objeto sirve para almacenar datos útiles que otros objetos pueden utilizar. Entonces un objeto nos sirve para:

  • Crear comunicaciones entre representaciones digitales de objetos.
  • Permite agrupar funcionalidades relacionadas con la representación digital del objeto.
  • Almacena datos que otro objeto puede usar. (Funciona como algunas estructuras de datos).

¿Qué son los objetos en Node.js y JavaScript?

Los objetos en Node.js y Javascript, son una colección de pares nombre: valor, parecido a los «arrays asociativos» de PHP. Estos pares de nombre/valor se les llama propiedades, una propiedad es como una variable y puede contener cualquier tipo de valor.

La clave del párrafo anterior es que puede contener cualquier tipo de valor, incluyendo funciones. Y con esto se identifican dos tipos de objetos.

  1. Objetos con funcionalidades y comunicaciones
    1. Que en su mayor parte tienen métodos para comunicarse con otros objetos y resolver problemas.
  2. Objetos con datos. También llamado estructura de datos
    1. Que en su mayor parte contiene datos y que nos sirven para almacenar y enviar información a través de mensajes. Estos objetos son utilizados por los objetos con funcionalidades y comunicaciones.

Recordemos que todo lo relacionado con objetos también funciona en cualquier lugar donde se ejecute JavaScript, node.js incluido.

¿Cómo crear objetos en Node.js y Javascript?

La forma más sencilla de crear un objeto es a través de un objeto literal. Un objeto literal es aquel que se crea con llaves {}, agregando sus propiedades y métodos. Como el siguiente ejemplo.

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  }
};
jaime.getNombre(); // Jaime Cervantes Velasco

Tenemos un objeto con cinco propiedades, nombre, apellidoPaterno y apellidoMaterno son de tipo string, edad es de tipo number y la última propiedad, getNombre() es de tipo function. Aquí tienes más información sobre los tipos de datos.

Aunque más adelante veremos a detalle las funciones, de momento, podemos decir que las funciones nos permiten encapsular y ejecutar un conjunto de operaciones que hacemos muy a menudo. En lugar de repetir el código de estas operaciones muchas veces, mejor ponemos estas operaciones dentro de una función.

Cada vez que necesitemos ejecutar estas operaciones, simplemente ejecutamos la función, a esta ejecución de la función se le llama invocación. Ejemplo de invocación es en la última línea, donde utilizamos paréntesis, jaime.getNombre();.

¿Cómo obtengo el valor de una propiedad?

Para obtener alguna propiedad de un objeto, podemos hacer de dos maneras:

  • Notación de puntos
  • Usando corchetes, muy parecido a como se acceden a los datos de un Array

La primera forma, notación de puntos, es muy sencilla, ejemplo:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  }
};
jaime.nombre // 'Jaime'
jaime.apellidoPaterno; // 'Cervantes'
jaime.apellidoMaterno; // 'Velasco'
jaime.edad; // 33
jaime.getNombre // function ()  { return 'Jaime Cervantes Velasco'; }

La segunda forma, usando corchetes [], ejemplo:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  }
};
jaime['nombre'] // 'Jaime'
jaime['apellidoPaterno']; // 'Cervantes'
jaime['apellidoMaterno']; // 'Velasco'
jaime['edad']; // 33
jaime['getNombre'] // function ()  { return 'Jaime Cervantes Velasco'; }
jaime['getNombre']() // 'Jaime Cervantes Velasco'

Aquí accedimos a las propiedades con corchetes usando el nombre de las propiedades, la cuales son strings.

Destaca como invocamos a la función getNombre. Estas dos sentencias hacen lo mismo, invocar a la función getNombre:

jaime.getNombre();
jaime['getNombre']();

El nombre de propiedades son cadenas de caracteres

Cuando usamos la notación de corchetes, usamos una cadena de caracteres para obtener el valor. Lo que mencionamos antes sobre que Un objeto es una colección de pares nombre: valor. El nombre de todas las propiedades son cadena de caracteres (string).

Como los nombres de las propiedades son cadenas de caracteres, entonces podemos definir propiedades encerradas por comillas, como el siguiente ejemplo:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  },
  'nueva-propiedad-con-guion-medio': 'Mi propiedad con guión medio encerrada por comillas'
};
jaime['nueva-propiedad-con-guion-medio']; // 'Mi propiedad con guión medio encerrada por comillas'

Y obtenerlos con notación de corchetes así, jaime['nueva-propiedad-con-guion-medio'].

Una propiedad como esta, no puede ser accedida con una notación de puntos, así jaime.nueva-propoiedad-con-guion.medio. Esta sentencia te dará un error en Javascript porque no es nombre de propiedad válido.

De hecho en Javascript el nombre de variables o propiedades válidos deben:

  • Empezar con una letra, incluido $ y _.
  • No puede empezar con números o caracteres usados en el lenguaje para otros propósitos, ejemplo -, %, /, +, &.
  • Después del la letra de inicio puedes ocupar números, otras letras válidas, $ y _.

¿Objetos en Node.js y Javascript que contienen otros objetos?

Un objeto puede contener cualquier tipo de dato, así que puede contener otros objetos. Veamos un ejemplo:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  },
  direccion: {
    calle: 'Melchor Ocampo',
    numero: 2,
    colonia: 'Las Flores',
    municipio: 'Tezonapa',
    estado: 'Veracruz'
  }
};

Aquí agregamos un objeto direccion al objeto jaime. Simplemente usando las llaves para crear objetos literales.

¿Cómo agrego, modifico y elimino propiedades?

Una vez que creas un objeto, puedes agregar más propiedades, modificarlas y eliminarlas.

Agregar propiedades a los objetos en Node.js y Javascript

Se utiliza la asignación para agregar nuevas propiedades que no existen previamente.

const jaime = {
  nombre: 'Jaime',
};
jaime.apellidoPaterno = 'Cervantes';
jaime.appelidoMaterno = 'Velasco';
console.log(jaime ); // { nombre: 'Jaime', apellidoPaterno: 'Cervantes', apellidoMaterno: 'Velasco }

Actualizar propiedades a los objetos en Node.js y Javascript

Se utiliza también la asignación para actualizar el valor de las propiedades que ya existen.

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
};
jaime.apellidoPaterno = 'Perez'
jaime.apellidoMaterno = 'Moreno';
console.log(jaime ); // { nombre: 'Jaime', apellidoPaterno: 'Perez', apellidoMaterno: 'Moreno' }

Eliminar propiedades a los objetos en Node.js y Javascript

Para eliminar propiedades se usa el operador delete. Ejemplo:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
};
delete jaime.apellidoPaterno;
// o tambien así:
delete jaime['apellidoMaterno];
console.log(jaime); // { nombre: 'Jaime' }

Las propiedades apellidoPaterno y apellidoMaterno, ya no existe en el objeto jaime.

¿Objetos en Node.js y Javascript son una referencia o un valor?

Los objetos en Node.js y JavaScript siempre serán una referencia, es decir:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
};
const pedro = jaime;
pedro.nombre = 'Pedro';
jaime.nombre === pedro.nombre; // true;
jaime.nombre; // 'Pedro'
pedro.nombre; // 'Pedro'

Las constantes jaime y pedro hacen referencia al mismo objeto en memoria.

¿Cómo recorro las propiedades de un objeto?

Existen varias formas, la más simple de recorrer las propiedades de un objeto es la siguiente.

Tenemos el objeto jaime:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  }
};

Lo primero es obtener el nombre de sus propiedades con el método Object.keys, el cual genera un Array con los nombres de las propiedades.

const nombresProps = Object.keys(jaime);
console.log(nombresProps); // ["nombre", "apellidoPaterno", "apellidoMaterno", "edad", "getNombre"]

Ahora recorremos el arreglo nombresProps para obtener los valores.

nombresProps.forEach(nombreProp => {
  console.log(jaime[nombreProp]); // imprime el valor del nombre de propiedad actual
});

El resultado del código anterior es algo como lo siguiente.

'Jaime'
'Cervantes'
'Velasco'
33
function () {
  return 'Jaime Cervantes Velasco';
}

¿El prototipo de un objeto?

Javascript es un lenguaje de programación multi-paradigma, en su lado orientado a objetos, no utiliza clases para la reutilización de código, más bien usa composición de objetos. Pero ¿Cómo hace esta composición de objetos?

Mejor composición de objetos sobre herencia de clases

Mejor composición de objetos sobre herencia de clases

Design Patterns: Elements of Reusable Object-Oriented Software

Este principio es bastante aplicado en Javascript y por consiguiente se aplica de igual manera en Node.js, de hecho así fue construido. Todos los objetos en Javascript tiene un enlace a un prototipo, y puede utilizar todas las propiedades y funciones de su prototipo.

Este enlace de prototipo puede ser referenciado en código, usando la propiedad especial __proto__.

En el caso de los objetos literales, como jaime, son enlazados a Object.prototype, que es el objeto base de todos los objetos en Javascript.

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  }
};
jaime.__proto__; // { constructor: f Object(), hasOwnProperty: f hasOwnProperty, ... }
jaime.__proto__ === Object.prototype; // true
jaime.toString === Object.prototype.toString; // true
jaime.toString(); //[object Object]

Si expandimos los prototipos en un navegador web como chrome, se visualiza de esta manera.

Objeto literal tiene como prototipo a Object.prototype
Objeto literal jaime tiene como prototipo a Object.prototype

Gráficamente, el enlace de prototipos es como la imagen de abajo.

Object.prototype el prototipo de jaime
Object.prototype el prototipo de jaime

El prototipo también lo puedes ver en node.js si lo enlazas a un debugger. Pero no veremos como hacer eso hoy.

¿Qué es la cadena de prototipos?

Hemos creado el objeto jaime literalmente, pero que tal si lo creamos a través de otro objeto persona, haciendo el objeto persona su prototipo inmediato.

const persona = {
  saludar: function () {
    return 'Hola';
  },
  comer: function () {
  	return 'comiendo...';
  }
};
const jaime = Object.create(persona); // Creamos jaime en base al prototipo persona

Si hacemos ahora un console.log(jaime):

console.log(jaime);

Y expandimos el resultado en un navegador web como chrome, el resultado se visualiza de la siguiente forma.

Cadena de prototipo
Cadena de prototipo

En la imagen, podemos ver que [[prototype]] apunta al objeto persona, por lo que jaime tiene acceso a los métodos comer y saludar del objeto persona.

Y si hacemos estas comparaciones vemos que el enlace al prototipo persona existe.

jaime.__proto__; // { saludar: f (), comer: f () }
jaime.__proto__  === persona; // true

Podemos invocar los métodos comer y saludar del objeto persona como si fueran de jaime.

jaime.comer();  // 'Comiendo...'
jaime.saludar(); // 'Hola'

Ahora si expandimos el último [[prototype]] ¿Cuál crees que sea el resultado?

Cadena de prototipos Object.prototype
Cadena de prototipos Object.prototype

Vemos claramente que el último [[prototype]] es Object.prototype, el prototipo base de todos los objetos en Javascript. Y puedes comprobarlo en código comparando Object.prototype con las propiedades especiales __proto__.

jaime.__proto__.__proto__ === Object.prototype; // true

También podemos usar el método toString como si fuera de jaime.

jaime.toString(); // [object Object]

A esto se le llama cadena de prototipos, un objeto puede tener los sub prototipos necesarios para reutilizar sus propiedades y métodos. En este caso fue así, jaime → persona → Object.prototype.

jaime --> persona --> Object.prototype
jaime –> persona –> Object.prototype

Peculiaridades con la cadena de prototipos

Usa los métodos y propiedades de los sub prototipos

El objeto jaime puede utilizar directamente las propiedades y métodos de sus prototipos.

jaime.toString(); // '[object Object]' --> de Object.prototype
jaime.hasOwnProperty('nombre'); // true --> de Object.prototype
jaime.saludar(); // 'Hola' --> de persona
jaime.comer(); // 'Comiendo...' --> de persona
jaime.nombre; // 'Jaime' --> de si mismo, objeto jaime

Actualizar propiedades no afecta a la cadena de prototipos

Cuando tenemos una propiedad que existe en varios sub prototipos, javascript usa el que esté más cerca en la cadena de prototipos. Supongamos que queremos cambiar el comportamiento del método toString en jaime así que en lugar de usar Object.prototype.toString, a jaime le agregamos un método con el mismo nombre.

Se puede decir que lo que estamos haciendo es actualizarlo.

jaime.toString = function () {
  return `${this.nombre} ${this.edad}`;
};
jaime.toString(); // 'Jaime 33'

Ahora se usa el método toString de jaime, no se recorre la cadena de prototipos hasta llegar a Object.prototype.toString como se hizo en los anteriores ejemplos. Por que ya existe un metodo toString directamente en nuestro objeto jaime.

El operador delete no elimina las propiedades de los prototipos

Cuando ocupamos el operador delete, este nunca toca la cadena de prototipos, solo elimina las propiedades propias del objeto en cuestión.

Si en la cadena de prototipos existe una propiedad con el mismo nombre de la que eliminamos, entonces ahora esa propiedad en la cadena de prototipos será utilizada.

Usando el ejemplo anterior, del método toString de jaime, si lo eliminamos con delete, entonces ahora el toString() de Object.prototype es el que se usara porque el objeto persona no tiene ese método directamente.

jaime.toString = function () {
  return `${this.nombre} ${this.edad}`;
};
jaime.toString(); // 'Jaime 33'
delete jaime.toString
jaime.toString(); // '[[object Object]]' --> De Object.prototype

En la última línea notamos que ahora jaime vuelve a usar el toString() de Object.prototype.

Ejemplos de objetos predefinidos en Node.js y Javascript

Como ya hemos mencionado en otras publicaciones, a excepción de los tipos primitivos, todo lo demás en javascript son objetos. En futuras publicaciones veremos más detalle de los objetos de tipo function y Array

¿Cómo crear un objeto function?

function imprimirPersona(persona) {
  console.log(persona.nombre);
  console.log(persona.edad);
}

Como la función imprimirPersona es un objeto, podemos agregarle propiedades sin ningún problema.

imprimirPersona.miPropiedad = 'Mi propiedad de una funcion';
console.log(imprimierPersona.miPropiedad); // 'Mi propiedad de una funcion

Las funciones son muy útiles, tan versátiles que con ellas podemos generar nuevos objetos, pero ese tema no pertenece a esta publicación.

¿Cómo crear un objeto Array?

La forma recomendada es simplemente crearlos usando corchetes [].

const vacio = [];
const numeros = [1, 2, 3, 4, 5, 6];
const animales = ['perro', 'gato', 'caballo'];
const conObjetos = [
  		{
          nombre: 'Jaime',
          edad: 33
        },
        function imprimirPersona(persona) {
          console.log(persona);
        }
	];

Como un array es un objeto, igual que una función, también le puedes agregar propiedades.

const numeros = [1, 2, 3, 4, 5, 6];
numeros.miPropiedad = 'Mi propiedad de un arreglo';
console.log(numeros.miPropiedad); // 'Mi propiedad de un arreglo

El Array es el objeto más claro para usarlo como estructura de datos.

¿Cómo crear un objeto Date?

const fecha = new Date();
console.log(fecha); // Fri May 28 2021 10:46:27 GMT-0500 (hora de verano central)

Como te podrás imaginar, al objeto date también le podemos agregar propiedades porque es un objeto

Conclusión

En Javascript y en cualquier plataforma donde se ejecute, un ejemplo es en node.js, tienen la peculiaridad de siempre tener un enlace a un prototipo. Así es como el lenguaje está diseñado. Ya sé que actualmente existen las clases, pero en realidad estas son funciones que internamente hacen uso de prototipos, también lo explicaremos en otra publicación.

Con excepción de los datos primitivos, todo lo demás en Javascript son objetos, es importante saber como funcionan para no atorarnos en este aprendizaje.

Con esta información tienes lo suficiente para seguir avanzando y utilizar los objetos más sabiamente.

El tema de los objetos es bastante extenso, todavía quedan muchas cosas a tomar en cuenta, pero ya es demasiada información que tenemos que dividirla en más publicaciones. Así que muy pronto continuaremos. Cualquier duda, no dudes en escribirla en los comentarios, ¡Estaremos contentos de ayudarte!.

es_MXES_MX