JavaScript y TypeScript: 11 construcciones compactas que debe conocer

Existe una línea muy fina entre el código limpio y eficiente y el código que solo el autor puede entender. Y lo peor es que es imposible definir claramente esta línea. Algunos programadores que lo buscan están dispuestos a ir mucho más lejos que otros. Por lo tanto, si necesita hacer un cierto fragmento de código de tal manera que esté garantizado que todos lo entenderán, en dicho código generalmente intentan no usar todo tipo de construcciones compactas como operadores ternarios y funciones de flecha de una línea.



Pero la verdad, la desagradable verdad, es que estos diseños compactos suelen resultar muy útiles. Y son, al mismo tiempo, bastante simples. Esto significa que cualquiera que esté interesado en el código en el que se utilizan puede dominarlos y comprender dicho código.







En esta publicación, voy a echar un vistazo a algunas construcciones compactas muy útiles (y a veces crípticas) que puede encontrar en JavaScript y TypeScript. Después de estudiarlos, puede usarlos usted mismo, o al menos puede comprender el código de aquellos programadores que los usan.



1. Operador?



El operador para verificar los valores de nully undefined(anular el operador de fusión) parece dos signos de interrogación ( ??). Cuesta creer que este, con tal o cual nombre, sea el operador más popular. ¿Cierto?



El significado de este operador es que devuelve el valor del operando derecho si el valor del izquierdo es igual a nullo undefined. Esto no se refleja claramente en su nombre, pero bueno, ¿qué es? He aquí cómo usarlo:



function myFn(variable1, variable2) {
  let var2 = variable2 ?? "default value"
  return variable1 + var2
}

myFn("this has ", "no default value") // "this has no default value"
myFn("this has no ") // "this has no default value"
myFn("this has no ", 0) // "this has no 0"


Aquí intervienen mecanismos muy similares a los utilizados para organizar el trabajo del operador ||. Si el lado izquierdo de la expresión es igual a nullo undefined, se devolverá el lado derecho de la expresión. De lo contrario, se devolverá el lado izquierdo. Como resultado, el operador ??es ideal para usar en situaciones en las que una determinada variable puede asignarse a cualquier cosa, pero es necesario tomar algunas medidas en caso de que esta variable obtenga nullo undefined.



2. Operador ?? =



Un operador utilizado para asignar un valor a una variable solo si tiene un valor nullo undefined(operador de asignación de anulación lógica) se ve como dos signos de interrogación seguidos de un signo igual ( ??=). Piense en ello como una extensión del operador anterior ??.



Echemos un vistazo al fragmento de código anterior, reescrito con ??=.



function myFn(variable1, variable2) {
  variable2 ??= "default value"
  return variable1 + variable2
}

myFn("this has ", "no default value") // "this has no default value"
myFn("this has no ") // "this has no default value"
myFn("this has no ", 0) // "this has no 0"


El operador le ??=permite verificar el valor de un parámetro de función variable2. Si es igual a nullo undefined, le escribirá un nuevo valor. De lo contrario, el valor del parámetro no cambiará.



Tenga en cuenta que el diseño ??=puede parecer incomprensible para quienes no estén familiarizados con él. Por lo tanto, si lo está utilizando, es posible que desee agregar un breve comentario con explicaciones en el lugar apropiado del código.



3. Declaración abreviada de constructores de TypeScript



Esta función es específica de TypeScript. Por lo tanto, si es un campeón de la pureza de JavaScript, se está perdiendo mucho. (Es broma, por supuesto, pero esto realmente no se aplica a JS normal).



Como sabe, al declarar una clase, generalmente enumeran todas sus propiedades con modificadores de acceso y luego, en el constructor de la clase, asignan valores a estas propiedades. Pero en los casos en que el constructor es muy simple, y en él los valores de los parámetros pasados ​​al constructor simplemente se escriben en las propiedades, puede usar una construcción que sea más compacta que la habitual.



Así es como se ve:



// ...
class Person {
  
  private first_name: string;
  private last_name: string;
  private age: number;
  private is_married: boolean;
  
  constructor(fname:string, lname:string, age:number, married:boolean) {
    this.first_name = fname;
    this.last_name = lname;
    this.age = age;
    this.is_married = married;
  }
}

// ,   ...
class Person {

  constructor( private first_name: string,
               private last_name: string,
               private age: number,
               private is_married: boolean){}
}


Usar el enfoque anterior al crear constructores definitivamente ayuda a ahorrar tiempo. Especialmente cuando se trata de una clase que tiene muchas propiedades.



Lo principal aquí es no olvidar agregar {}inmediatamente después de la descripción del constructor, ya que esta es una representación del cuerpo de la función. Después de que el compilador encuentre tal descripción, lo entenderá todo y hará el resto por sí mismo. De hecho, estamos hablando del hecho de que tanto el primer como el segundo fragmento del código TS se convertirán eventualmente al mismo código JavaScript.



4. Operador ternario



El operador ternario es una construcción fácil de leer. Este operador se usa a menudo en lugar de instrucciones breves if…else, ya que le permite deshacerse de caracteres adicionales y convertir una construcción de varias líneas en una construcción de una línea.



//   if…else
let isEven = ""
if(variable % 2 == 0) {
  isEven = "yes"
} else {
  isEven = "no"
}

//  
let isEven = (variable % 2 == 0) ? "yes" : "no"


En la estructura de un operador ternario, el primero es una expresión lógica, el segundo es algo así como un comando returnque devuelve un valor si la expresión lógica es verdadera, y el tercero también es algo así como un comando returnque devuelve un valor si la expresión lógica es falsa. Aunque el operador ternario se usa mejor en el lado derecho de las asignaciones de valor (como en el ejemplo), también se puede usar de forma autónoma, como un mecanismo para llamar a funciones, cuándo se llamará a qué función, o con qué argumentos se llamará uno y el mismo. la misma función está determinada por el valor de la expresión lógica. Así es como se ve:



let variable = true;

(variable) ? console.log("It's TRUE") : console.log("It's FALSE")


Tenga en cuenta que la estructura de la declaración se ve igual que en el ejemplo anterior. La desventaja de utilizar el operador ternario es que si en el futuro es necesario expandir una de sus partes (ya sea la que hace referencia al valor verdadero de la expresión lógica, o la que hace referencia a su valor falso), esto supondrá la necesidad de ir a la instrucción habitual if…else.



5. Utilizando un ciclo de cálculo corto utilizado por el operador ||



En JavaScript (y también en TypeScript), el operador lógico OR ( ||) implementa un modelo informático abreviado. Es decir, devuelve la primera expresión evaluada como truey no comprueba las expresiones restantes.



Esto significa que si existe la siguiente declaración if, donde la expresión expression1contiene un valor falso (reducible a false), y expression2- verdadero (reducible a true), entonces solamente expression1y serán calculadas expression2. Expresiones espression3y expression4no se evaluarán.



if( expression1 || expression2 || expression3 || expression4)


Podemos aprovechar esta oportunidad fuera del enunciado if, donde asignamos valores a las variables. Esto permitirá, en particular, escribir un valor predeterminado en una variable en el caso de que un valor, digamos, representado por un parámetro de función, resulte falso (por ejemplo, igual undefined):



function myFn(variable1, variable2) {
  let var2 = variable2 || "default value"
  return variable1 + var2
}

myFn("this has ", " no default value") // "this has no default value"
myFn("this has no ") // "this has no default value"


Este ejemplo demuestra cómo puede usar un operador ||para escribir en una variable el valor del segundo parámetro de una función o un valor predeterminado. Sin embargo, si observa detenidamente este ejemplo, puede ver un pequeño problema en él. El hecho es que si hay variable2un valor 0o una cadena vacía, entonces var2se escribirá el valor predeterminado, ya que tanto 0la cadena vacía como la cadena vacía se convertirán a false.



Por lo tanto, si en su caso no necesita reemplazar todos los valores falsos con el valor predeterminado, puede recurrir a un operador menos conocido ??.



6. Operador doble bit a bit ~



Los desarrolladores de JavaScript no suelen estar especialmente interesados ​​en utilizar operadores bit a bit. ¿A quién le importan las representaciones binarias de números en estos días? Pero el hecho es que debido a que estos operadores trabajan a nivel de bits, realizan las acciones correspondientes mucho más rápido que, por ejemplo, algunos métodos.



Si hablamos del operador bit a bit NOT ( ~), entonces toma un número, lo convierte en un entero de 32 bits (descartando los bits "extra") e invierte los bits de este número. Esto hace que el valor se xconvierta en valor -(x+1). ¿Por qué estamos interesados ​​en tal conversión de números? Y el hecho de que si lo usa dos veces, nos dará el mismo resultado que una llamada a un método Math.floor.



let x = 3.8
let y = ~x // x   -(3 + 1),    ,    
let z = ~y //  y ( -4)  -(-4 + 1)   -  3

//   :

let flooredX = ~~x //      


Observe los dos iconos ~en la última línea del ejemplo. Puede parecer extraño, pero si tienes que convertir muchos números de coma flotante en enteros, esta técnica puede ser muy útil.



7. Asignar valores a las propiedades del objeto



Las capacidades del estándar ES6 simplifican el proceso de creación de objetos y, en particular, el proceso de asignación de valores a sus propiedades. Si los valores de propiedad se asignan según las variables que tienen los mismos nombres que estas propiedades, no es necesario repetir esos nombres. Anteriormente, era necesario.



Aquí hay un ejemplo escrito en TypeScript.



let name:string = "Fernando";
let age:number = 36;
let id:number = 1;

type User = {
  name: string,
  age: number,
  id: number
}

// 
let myUser: User = {
  name: name,
  age: age,
  id: id
}

// 
let myNewUser: User = {
  name,
  age,
  id
}


Como puede ver, el nuevo enfoque para asignar valores a las propiedades del objeto le permite escribir código más compacto y simple. Y dicho código no es más difícil de leer que el código escrito de acuerdo con las reglas antiguas (lo que no se puede decir sobre el código escrito utilizando otras construcciones compactas descritas en este artículo).



8. Devolución implícita de valores de las funciones de flecha



¿Sabía que las funciones de flecha de una sola línea devuelven los resultados de los cálculos realizados en una sola línea?



El uso de este mecanismo le permite deshacerse de una expresión innecesaria return. Esta técnica se usa a menudo en funciones de flecha que se pasan a métodos de matriz como filtero map. Aquí hay un ejemplo de TypeScript:



let myArr:number[] = [1,2,3,4,5,6,7,8,9,10]

//  :
let oddNumbers:number[] = myArr.filter( (n:number) => {
  return n % 2 == 0
})

let double:number[] = myArr.map( (n:number) => {
  return n * 2;
})

//  :
let oddNumbers2:number[] = myArr.filter( (n:number) => n % 2 == 0 )

let double2:number[] = myArr.map( (n:number) =>  n * 2 )


Aplicar esta técnica no significa necesariamente hacer que el código sea más complejo. Construcciones como estas son una buena manera de limpiar un poco su código y deshacerse de espacios innecesarios y líneas adicionales. Por supuesto, la desventaja de este enfoque es que si es necesario extender los cuerpos de funciones tan cortas, tendrá que volver a usar llaves nuevamente.



La única peculiaridad que habrá que tener en cuenta aquí es que lo que está contenido en la única línea de las funciones de flecha corta aquí consideradas debe ser una expresión (es decir, debe producir algún resultado que pueda ser devuelto desde la función). De lo contrario, dicho diseño no funcionará. Por ejemplo, las funciones unifilares anteriores no se pueden escribir así:



const m = _ => if(2) console.log("true"else console.log("false")


En la siguiente sección, continuaremos hablando de funciones de flecha de una sola línea, pero ahora hablaremos de funciones que no se pueden crear sin llaves.



9. Parámetros de función, que pueden tener valores predeterminados



ES6 introdujo la capacidad de especificar valores que se asignan a los parámetros de función predeterminados. Anteriormente, JavaScript no tenía tales capacidades. Por tanto, en situaciones en las que era necesario asignar valores similares a los parámetros, era necesario recurrir a algo así como un modelo de cálculos de operador reducidos ||.



Pero ahora el mismo problema se puede resolver de manera muy simple:



//    2  
//     ,   
function myFunc(a, b, c = 2, d = "") {
  //   ...
}


Mecanismo simple, ¿verdad? Pero, de hecho, todo es aún más interesante de lo que parece a primera vista. El punto es que el valor predeterminado puede ser cualquier cosa, incluida una llamada a función. Se llamará a esta función si no se le pasa el parámetro correspondiente cuando se llama a la función. Esto facilita la implementación del patrón de parámetros de función requerido:



const mandatory = _ => {
  throw new Error("This parameter is mandatory, don't ignore it!")
}

function myFunc(a, b, c = 2, d = mandatory()) {
  //    ...
}

// !
myFunc(1,2,3,4)

// 
myFunc(1,2,3)


Aquí, de hecho, está la función de flecha de una línea que no puede hacer sin llaves al crearla. El punto es que la función mandatoryusa una instrucción throw. Preste atención: "instrucción", no "expresión". Pero, supongo, este no es el precio más alto por la capacidad de equipar funciones con los parámetros requeridos.



10. Conversión de cualquier valor a un tipo booleano usando !!



Este mecanismo funciona según el mismo principio que la construcción anterior ~~. Es decir, estamos hablando del hecho de que para convertir cualquier valor a un tipo lógico, puede usar dos operadores lógicos NOT ( !!):



!!23 // TRUE
!!"" // FALSE
!!0 // FALSE
!!{} // TRUE


Un operador !ya resuelve la mayor parte de este problema, es decir, convierte el valor en un tipo booleano y luego devuelve el valor opuesto. Y el segundo operador !toma lo que sucedió y simplemente devuelve lo contrario. Como resultado, obtenemos el valor original convertido a un tipo booleano.



Esta breve construcción puede resultar útil en una variedad de situaciones. Primero, cuando necesita asegurarse de que a una variable se le asigne un valor booleano real (por ejemplo, si estamos hablando de una variable de tipo TypeScript boolean). En segundo lugar, cuando necesite realizar una comparación (uso ===) estricta de algo con trueo false.



11. Desestructuración y sintaxis de difusión



Se puede hablar y hablar de los mecanismos mencionados en el título de esta sección. El caso es que, si se utiliza correctamente, puede dar lugar a resultados muy interesantes. Pero aquí no voy a profundizar demasiado. Te diré cómo usarlos para simplificar la solución de algunos problemas.



▍Desestructurar objetos



¿Alguna vez se ha enfrentado a la tarea de escribir múltiples valores de propiedades de objeto en variables ordinarias? Esta tarea es bastante común. Por ejemplo, cuando es necesario trabajar con estos valores (modificándolos, por ejemplo) y al mismo tiempo no afectar lo que está almacenado en el objeto original.



El uso de la desestructuración de objetos le permite resolver problemas similares utilizando la cantidad mínima de código:



const myObj = {
  name: "Fernando",
  age: 37,
  country: "Spain"
}

// :
const name = myObj.name;
const age = myObj.age;
const country = myObj.country;

// 
const {name, age, country} = myObj;


Cualquiera que haya usado TypeScript ha visto esta sintaxis en las instrucciones import. Le permite importar métodos de biblioteca individuales sin contaminar el espacio de nombres del proyecto con muchas funciones innecesarias:



import { get } from 'lodash'


Por ejemplo, esta instrucción le permite importar lodashsolo un método de la biblioteca get. Al mismo tiempo, otros métodos de esta biblioteca no entran en el espacio de nombres del proyecto. Y hay muchos de ellos en él.



▍ Difundir la sintaxis y crear nuevos objetos y matrices basados ​​en los existentes



El uso de la sintaxis spread ( ) simplifica la tarea de crear nuevas matrices y objetos basados ​​en los existentes. Ahora esta tarea se puede resolver escribiendo literalmente una línea de código y sin recurrir a ningún método especial. He aquí un ejemplo:



const arr1 = [1,2,3,4]
const arr2 = [5,6,7]

const finalArr = [...arr1, ...arr2] // [1,2,3,4,5,6,7]

const partialObj1 = {
  name: "fernando"
}
const partialObj2 = {
  age:37
}

const fullObj = { ...partialObj1, ...partialObj2 } // {name: "fernando", age: 37}


Tenga en cuenta que el uso de este enfoque para combinar objetos sobrescribirá sus propiedades con el mismo nombre. Esto no se aplica a las matrices. En particular, si las matrices que se van a fusionar tienen los mismos valores, todas terminarán en la matriz resultante. Si necesita deshacerse de las repeticiones, puede recurrir al uso de una estructura de datos Set.



▍Combinando desestructuración y sintaxis de propagación



La desestructuración se puede utilizar junto con la sintaxis de propagación. Esto permite un efecto interesante. Por ejemplo, elimine el primer elemento de la matriz y deje el resto intacto (como en el ejemplo común con el primer y último elemento de la lista, cuya implementación se puede encontrar en Python y otros lenguajes). Y también, por ejemplo, incluso puede extraer algunas propiedades de un objeto y dejar el resto intacto. Consideremos un ejemplo:



const myList = [1,2,3,4,5,6,7]
const myObj = {
  name: "Fernando",
  age: 37,
  country: "Spain",
  gender: "M"
}

const [head, ...tail] = myList

const {name, age, ...others} = myObj

console.log(head) //1
console.log(tail) //[2,3,4,5,6,7]
console.log(name) //Fernando
console.log(age) //37
console.log(others) //{country: "Spain", gender: "M"}


Tenga en cuenta que los tres puntos que se utilizan en el lado izquierdo de la declaración de asignación deben aplicarse al último elemento. No puede usar la sintaxis de propagación primero y luego describir variables individuales:



const [...values, lastItem] = [1,2,3,4]


Este código no funcionará.



Salir



Hay muchos más diseños similares a los de los que hablamos hoy. Pero, usándolos, vale la pena recordar que cuanto más compacto es el código, más difícil es leerlo para alguien que no esté acostumbrado a las construcciones abreviadas que se usan en él. Y el propósito de usar tales construcciones no es minimizar el código ni acelerarlo. Este objetivo es solo eliminar las estructuras innecesarias del código y hacer la vida más fácil para quienes leerán este código.



Por lo tanto, para mantener contentos a todos, se recomienda mantener un equilibrio saludable entre las construcciones compactas y el código legible regular. Siempre vale la pena recordar que usted no es la única persona que lee su código.



¿Qué construcciones compactas utiliza en código JavaScript y TypeScript?










All Articles