Best Practices in Modern JavaScript - Part 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
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 });
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
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
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 });
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
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); } }
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"]
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
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 });
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
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); } }
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
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 });
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
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
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 });
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
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); } }
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
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 });
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
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!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics











Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

The main uses of JavaScript in web development include client interaction, form verification and asynchronous communication. 1) Dynamic content update and user interaction through DOM operations; 2) Client verification is carried out before the user submits data to improve the user experience; 3) Refreshless communication with the server is achieved through AJAX technology.

JavaScript's application in the real world includes front-end and back-end development. 1) Display front-end applications by building a TODO list application, involving DOM operations and event processing. 2) Build RESTfulAPI through Node.js and Express to demonstrate back-end applications.

Understanding how JavaScript engine works internally is important to developers because it helps write more efficient code and understand performance bottlenecks and optimization strategies. 1) The engine's workflow includes three stages: parsing, compiling and execution; 2) During the execution process, the engine will perform dynamic optimization, such as inline cache and hidden classes; 3) Best practices include avoiding global variables, optimizing loops, using const and lets, and avoiding excessive use of closures.

Python and JavaScript have their own advantages and disadvantages in terms of community, libraries and resources. 1) The Python community is friendly and suitable for beginners, but the front-end development resources are not as rich as JavaScript. 2) Python is powerful in data science and machine learning libraries, while JavaScript is better in front-end development libraries and frameworks. 3) Both have rich learning resources, but Python is suitable for starting with official documents, while JavaScript is better with MDNWebDocs. The choice should be based on project needs and personal interests.

Both Python and JavaScript's choices in development environments are important. 1) Python's development environment includes PyCharm, JupyterNotebook and Anaconda, which are suitable for data science and rapid prototyping. 2) The development environment of JavaScript includes Node.js, VSCode and Webpack, which are suitable for front-end and back-end development. Choosing the right tools according to project needs can improve development efficiency and project success rate.
