Home > Web Front-end > JS Tutorial > Best Practices in Modern JavaScript - Part 2

Best Practices in Modern JavaScript - Part 2

Patricia Arquette
Release: 2024-12-08 16:09:12
Original
683 people have browsed it

Mejores Prácticas en JavaScript Moderno - Parte 2

In the first part of this article, we explored the basics of modern JavaScript and some essential best practices to start writing cleaner, more efficient code. But as developers, we know that there is always more to learn and improve.

8. Optional chaining (?.)

When working with objects or nested structures, we sometimes face the need to check if a property exists before trying to access it. The optional chaining operator (?.) is a powerful tool that simplifies this task, avoiding property access errors of null or undefined values.

Why is it useful?

Imagine that you have a complex object structure and you are not sure if certain properties exist in it. Without optional chaining, you would have to do manual checks at each step, which can make your code longer and less readable. With the ?. operator, you can safely access properties and get undefined if any of the intermediate properties do not exist.

Basic example

const producto = {};
const impuesto = producto?.precio?.impuesto;
console.log(impuesto); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

In this case, since the product does not have the price property, the optional chaining returns undefined instead of generating an error.

Example with a more complex object

Imagine that you have a list of products with different properties, some of which may be empty or undefined:

const productos = [
  { nombre: 'Laptop', detalles: { precio: 1000 } },
  { nombre: 'Teléfono', detalles: null },
  { nombre: 'Tablet', detalles: { precio: 500, impuesto: 50 } }
];

// Acceso seguro a la propiedad 'impuesto' de cada producto
productos.forEach(producto => {
  const impuesto = producto?.detalles?.impuesto;
  console.log(impuesto); // undefined, null o el valor real
});
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

In this example, optional chaining allows us to avoid errors when trying to access product.details.tax, even if details is null or absent.

How does it improve your code?

  • Avoid access errors to null or undefined properties.
  • Simplifies the code, making security checks cleaner and more readable.
  • Allows you to work with uncertain or incomplete data, something very common when you work with responses from APIs or databases.

Bonus: Optional chaining with functions

Optional chaining can also be used with functions, which is very useful when you have functions that may not be defined on an object:

const usuario = { nombre: 'Juan', obtenerEdad: null };
const edad = usuario.obtenerEdad?.();
console.log(edad); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Here, the getAge function is undefined (it's null), but it doesn't throw an error, it just returns undefined.

9. Use async/await for async handling

When you work with asynchronous operations in JavaScript, such as getting data from an API or reading files, the async/await syntax can be your best friend. Instead of using promises with .then() and .catch(), async/await allows you to write asynchronous code in a cleaner and more readable way, similar to how we would write synchronous code.

Why use async/await?

  • Simplicity and readability: You avoid "promise chains" that can become complicated to read and maintain.
  • More intuitive error handling: Using try/catch to handle errors is much clearer than using .catch().
  • More precise control: Allows await to be used anywhere in the function, making it easier to control more complex asynchronous flows.

Basic example of use:

Suppose we are working with an API that returns data. Using async/await instead of .then() makes the flow much easier to follow:

const producto = {};
const impuesto = producto?.precio?.impuesto;
console.log(impuesto); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Practical example: fetching data from an API and displaying it in the UI

Imagine that you have a web page where you need to display user information from an API. Here's an example of how you could do it using async/await to get the data and render it to the interface:

const productos = [
  { nombre: 'Laptop', detalles: { precio: 1000 } },
  { nombre: 'Teléfono', detalles: null },
  { nombre: 'Tablet', detalles: { precio: 500, impuesto: 50 } }
];

// Acceso seguro a la propiedad 'impuesto' de cada producto
productos.forEach(producto => {
  const impuesto = producto?.detalles?.impuesto;
  console.log(impuesto); // undefined, null o el valor real
});
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Object.values()

Returns an array with all the values ​​of the properties of an object. Perfect when you just need the values ​​without the keys.

Example:

const usuario = { nombre: 'Juan', obtenerEdad: null };
const edad = usuario.obtenerEdad?.();
console.log(edad); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Object.entries()

This is the most versatile method. Returns an array of arrays, where each sub-array contains a key and its corresponding value. This is useful if you want to work with both keys and values ​​in a single operation.

Example:

async function obtenerDatos() {
  try {
    const respuesta = await fetch('https://api.ejemplo.com/datos');
    if (!respuesta.ok) {
      throw new Error('Error al obtener los datos');
    }
    const datos = await respuesta.json();
    console.log(datos);
  } catch (error) {
    console.error('Error:', error.message);
  }
}
Copy after login
Copy after login
Copy after login

Bonus: Iteration with for...of

Did you know that you can combine these methods with for...of to make your code even cleaner? Here is an example using Object.entries():

Example:

// Función para obtener y mostrar los datos de usuarios
async function obtenerUsuarios() {
  try {
    const respuesta = await fetch('https://api.ejemplo.com/usuarios');
    if (!respuesta.ok) {
      throw new Error('No se pudieron cargar los usuarios');
    }
    const usuarios = await respuesta.json();
    mostrarUsuariosEnUI(usuarios);
  } catch (error) {
    console.error('Hubo un problema con la carga de los usuarios:', error);
    alert('Error al cargar los usuarios. Intenta más tarde.');
  }
}

// Función para renderizar usuarios en el HTML
function mostrarUsuariosEnUI(usuarios) {
  const contenedor = document.getElementById('contenedor-usuarios');
  contenedor.innerHTML = usuarios.map(usuario => `
    <div>



<h3>
  
  
  ¿Qué mejoramos con async/await?
</h3>

<ol>
<li>
<strong>Manejo claro de errores:</strong> Usamos try/catch para capturar cualquier error que pueda ocurrir durante la obtención de datos, ya sea un problema con la red o con la API.</li>
<li>
<strong>Código más legible:</strong> La estructura de await hace que el flujo del código se lea de manera secuencial, como si fuera código sincrónico.</li>
<li>
<strong>Evita el anidamiento:</strong> Con async/await puedes evitar los callbacks anidados (el famoso "callback hell") y las promesas encadenadas.</li>
</ol>

<p>Usar async/await no solo mejora la calidad de tu código, sino que también hace que sea mucho más fácil depurar y mantener proyectos a largo plazo. ¡Es una herramienta poderosa que deberías incorporar siempre que trabajes con asincronía en JavaScript!</p>

<h2>
  
  
  10. Métodos modernos para objetos
</h2>

<p>Cuando trabajamos con objetos en JavaScript, es común que necesitemos iterar sobre las claves y los valores, o incluso extraer solo las claves o valores. Los métodos modernos como Object.entries(), Object.values() y Object.keys() hacen que estas tareas sean mucho más fáciles y legibles.</p>

<h3>
  
  
  Object.keys()
</h3>

<p>Este método devuelve un array con todas las claves de un objeto. Es útil cuando solo necesitas acceder a las claves y no a los valores.</p>

<p><strong>Ejemplo:</strong><br>
</p>

<pre class="brush:php;toolbar:false">const obj = { a: 1, b: 2, c: 3 };
const claves = Object.keys(obj);
console.log(claves); // ["a", "b", "c"]
Copy after login

This approach is cleaner and easier to read, especially if you are working with large or complex objects.

11. Use Map for non-primitive keys

When you need to associate values ​​with keys that are not strings or symbols, use Map. It is more robust and maintains the type and order of the keys.

Example:

const producto = {};
const impuesto = producto?.precio?.impuesto;
console.log(impuesto); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

12. Use Symbol for unique keys

Symbols are a JavaScript feature that allows you to create unique and immutable keys, making them a powerful tool when we need to ensure that a value is not accidentally overwritten or accessed. Symbols cannot be accessed by methods like Object.keys(), for...in, or JSON.stringify(), making them perfect for private or "hidden" values.

Why use Symbol?

When we create properties of an object using keys such as text strings, they can be easily manipulated or overwritten. However, symbols ensure that each key is unique, even if we create symbols with the same name. Additionally, symbols will not appear in object property enumerations.

Basic example:

const productos = [
  { nombre: 'Laptop', detalles: { precio: 1000 } },
  { nombre: 'Teléfono', detalles: null },
  { nombre: 'Tablet', detalles: { precio: 500, impuesto: 50 } }
];

// Acceso seguro a la propiedad 'impuesto' de cada producto
productos.forEach(producto => {
  const impuesto = producto?.detalles?.impuesto;
  console.log(impuesto); // undefined, null o el valor real
});
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

In this example, the hiddenKey key is unique, and although another part of our code could have created another Symbol('hidden'), it would be completely different and would not affect the value stored in obj.

Advanced Example: Combining Symbol with Object.defineProperty

You can even use Symbol together with Object.defineProperty to add properties to objects in a more controlled way, ensuring that the properties are non-enumerable.

const usuario = { nombre: 'Juan', obtenerEdad: null };
const edad = usuario.obtenerEdad?.();
console.log(edad); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

In this example, secretKey will not appear in the object's key enumeration, making it ideal for "private" values ​​that should not be accessed or modified by accident.

Considerations:

  • Symbols are useful for avoiding property name conflicts, especially when working with third-party libraries or APIs.
  • Although symbols are not strictly "private", the fact that they are not conventionally accessible helps protect the integrity of the data.
  • Please note that symbols cannot be serialized to JSON, so if you need to transmit data, be sure to handle the Symbol properties appropriately.

13. Be careful with JSON and large numbers

In JavaScript, handling large numbers can be a real challenge. The Number data type has a limit on representing integers accurately: the largest safe integer value is 9007199254740991 (also known as Number.MAX_SAFE_INTEGER). If you try to work with numbers larger than this, you may lose precision, which could cause errors in your application.

For example, imagine you receive a large number from an external API:

async function obtenerDatos() {
  try {
    const respuesta = await fetch('https://api.ejemplo.com/datos');
    if (!respuesta.ok) {
      throw new Error('Error al obtener los datos');
    }
    const datos = await respuesta.json();
    console.log(datos);
  } catch (error) {
    console.error('Error:', error.message);
  }
}
Copy after login
Copy after login
Copy after login

As you see, the number 9007199254740999 is incorrectly converted to 9007199254741000. This can be problematic if the number is critical to your application, such as a unique identifier or a financial amount.

How to avoid this problem?

A simple and elegant solution is to use the BigInt data type, introduced in ECMAScript 2020. BigInt can handle much larger numbers without losing precision. However, JSON doesn't natively handle BigInt, so you'll need to convert numbers to strings when serializing them and then convert them back when you deserialize them.

Here is an example of how you could do it:

Solution with BigInt and JSON.stringify

const producto = {};
const impuesto = producto?.precio?.impuesto;
console.log(impuesto); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

By using this approach, you can maintain the accuracy of large numbers without losing important data. When you need the number again, just convert it back to BigInt:

const productos = [
  { nombre: 'Laptop', detalles: { precio: 1000 } },
  { nombre: 'Teléfono', detalles: null },
  { nombre: 'Tablet', detalles: { precio: 500, impuesto: 50 } }
];

// Acceso seguro a la propiedad 'impuesto' de cada producto
productos.forEach(producto => {
  const impuesto = producto?.detalles?.impuesto;
  console.log(impuesto); // undefined, null o el valor real
});
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Other strategies

If you don't want to work with BigInt or if performance is a concern, another strategy is to simply treat large numbers as strings in JSON. This avoids the precision problem at the cost of having to do conversions in your code.

Example:

const usuario = { nombre: 'Juan', obtenerEdad: null };
const edad = usuario.obtenerEdad?.();
console.log(edad); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Why is it important?

Proper handling of large numbers is not only crucial for the accuracy of the calculations, but also for maintaining data integrity. This is especially important when you work with third-party APIs or systems that you don't fully control. A misinterpreted number could lead to failures in your application, or worse, errors in data that could be critical, such as the handling of financial transactions or unique identifiers in databases.

Remember: don't ignore precision limits. Although it may seem like a small detail, it is an area where applications can fail in unexpected and costly ways.

14. Handle expressions in if statements explicitly

In JavaScript, if statements implicitly convert expressions to "truthy" or "falsy" values, which can lead to unexpected results if this behavior is not taken into account. Although this behavior can be useful at times, it is recommended to be explicit in the comparison to avoid subtle errors and improve code readability.

What does "truthy" or "falsy" mean?

  • "Falsy" refers to values ​​that are considered equivalent to false when evaluated in a conditional expression. Examples: 0, "" (empty string), null, undefined, NaN.
  • "Truthy" are all values ​​that are not falsy, that is, any value that is not one of the above. Example: any number other than 0, any non-empty string, objects, etc.

Implicit example (may give unexpected results)

const producto = {};
const impuesto = producto?.precio?.impuesto;
console.log(impuesto); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

In the example above, the condition is not executed, since 0 is considered "falsy". However, this behavior can be difficult to detect when working with more complex values.

Explicit example (better for readability)

const productos = [
  { nombre: 'Laptop', detalles: { precio: 1000 } },
  { nombre: 'Teléfono', detalles: null },
  { nombre: 'Tablet', detalles: { precio: 500, impuesto: 50 } }
];

// Acceso seguro a la propiedad 'impuesto' de cada producto
productos.forEach(producto => {
  const impuesto = producto?.detalles?.impuesto;
  console.log(impuesto); // undefined, null o el valor real
});
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Tip: Whenever you are dealing with values ​​that may be false, such as 0, null, false, or "", it is best to be explicit in your comparison. This way you ensure that the logic is executed according to your expectations and not because of implicit type coercion behavior.

Another example with ambiguous values

Let's consider that you have an object that can be null, an empty array [], or an empty object {}. If you do something like this:

const usuario = { nombre: 'Juan', obtenerEdad: null };
const edad = usuario.obtenerEdad?.();
console.log(edad); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Although [] (an empty array) is a valid and truthful object, it can lead to confusion in the future if you don't fully understand the behavior. Instead of relying on implicit coercion, it is best to make more explicit comparisons, such as:

async function obtenerDatos() {
  try {
    const respuesta = await fetch('https://api.ejemplo.com/datos');
    if (!respuesta.ok) {
      throw new Error('Error al obtener los datos');
    }
    const datos = await respuesta.json();
    console.log(datos);
  } catch (error) {
    console.error('Error:', error.message);
  }
}
Copy after login
Copy after login
Copy after login

Why is it important?

By defining conditions explicitly, you reduce the risk of errors caused by JavaScript's automatic coercion. This approach makes your code clearer, more readable, and more predictable. Additionally, it improves maintainability, as other people (or yourself in the future) will be able to quickly understand the logic without having to remember the implicit behavior of falsy values ​​in JavaScript.

15. Use strict equality (===) whenever possible

One of the most confusing behaviors of JavaScript comes from the non-strict equality operator (==). This operator performs what is known as type coercion, which means that it attempts to convert values ​​to a common type before comparing them. This can produce results surprisingly unexpected and very difficult to debug.

For example:

const producto = {};
const impuesto = producto?.precio?.impuesto;
console.log(impuesto); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

This is the kind of thing that can drive you crazy when you're developing. The == operator compares [] (an empty array) with ![] (which turns out to be false, since [] is considered a true value and ![] converts it to false). However, by JavaScript's internal coercion rules, this is a valid result, although it doesn't make sense at first glance.

Why is this happening?

JavaScript converts both sides of the comparison to a common type before comparing them. In this case, the empty array [] becomes false when compared to the boolean value of ![]. This type of coercion is a clear example of how subtle and difficult to identify errors can occur.

Tip: Always use strict equality

To avoid these problems, whenever possible, you should use strict equality (===). The difference is that this operator does not perform type coercion . This means that it compares both the value and type of the variables strictly.

const productos = [
  { nombre: 'Laptop', detalles: { precio: 1000 } },
  { nombre: 'Teléfono', detalles: null },
  { nombre: 'Tablet', detalles: { precio: 500, impuesto: 50 } }
];

// Acceso seguro a la propiedad 'impuesto' de cada producto
productos.forEach(producto => {
  const impuesto = producto?.detalles?.impuesto;
  console.log(impuesto); // undefined, null o el valor real
});
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

More typical examples

Here are some more common examples of how non-strict equality (==) can be problematic:

const usuario = { nombre: 'Juan', obtenerEdad: null };
const edad = usuario.obtenerEdad?.();
console.log(edad); // undefined
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Why is it important to use ===?

  • Prediction and reliability: Using === gives you more predictable comparisons, without surprises or type conversions.
  • Avoid hard-to-detect bugs: When your code starts to get larger and more complex, bugs related to type coercion can be very difficult to find and debug.
  • Improve code readability: It's easier for other developers (or yourself in the future) to understand your code when you see explicit comparisons, without the confusion of implicit conversions.

The above is the detailed content of Best Practices in Modern JavaScript - Part 2. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template