Características de JavaScript poco conocidas

JavaScript es a menudo llamado el lenguaje más simple para principiantes, en la programación, que es el dominio más difícil de lograr. El autor del material, cuya traducción publicamos, dice que no puede dejar de estar de acuerdo con esta declaración. El caso es que JS es un lenguaje muy antiguo y realmente flexible. Está lleno de misteriosas construcciones sintácticas y características obsoletas que aún admite.

imagen

Hoy hablaremos sobre las características y opciones de JavaScript poco conocidas para su aplicación práctica.

JavaScript siempre es algo nuevo


He estado trabajando con JavaScript durante muchos años y constantemente encuentro algo que nunca sospeché que existiera. Aquí intenté enumerar características similares poco conocidas del lenguaje. En modo estricto, algunos de ellos no funcionarán, pero en modo normal son ejemplos de código JS completamente correctos. Cabe señalar que no pretendo aconsejar a los lectores que pongan todo esto en servicio. Aunque lo que voy a hablar le parece muy interesante, puede comenzar a usar todo esto si trabaja en un equipo y, para decirlo suavemente, sorprenda a sus colegas.

→ El código que discutiremos aquí se puede encontrar aquí.

Tenga en cuenta que no incluí cosas como elevar variables, cierres, objetos proxy, herencia de prototipos, asíncrono / espera, generadores y similares. Aunque estas características del lenguaje pueden atribuirse a dificultades de comprensión, no son bien conocidas.

Operador vacío


JavaScript tiene un operador de void unario. Es posible que lo haya encontrado en forma de void(0) o void 0 . Su único propósito es calcular la expresión a su derecha y regresar undefined . 0 aquí se usa simplemente porque es habitual, aunque esto no es necesario, y aquí puede usar cualquier expresión válida. Es cierto que este operador en cualquier caso devolverá undefined .

 //  void void 0                  // undefined void (0)                // undefined void 'abc'              // undefined void {}                 // undefined void (1 === 1)          // undefined void (1 !== 1)          // undefined void anyfunction()      // undefined 

¿Por qué agregar una palabra clave especial al idioma que sirve para devolver undefined , si solo puede usar el valor estándar undefined ? ¿No es así, hay algo de redundancia?

Resultó que, antes de la aparición del estándar ES5 en la mayoría de los navegadores, a un valor estándar de undefined podía asignar un nuevo valor. Digamos que podría ejecutar con éxito este comando: undefined = "abc" . Como resultado, un valor undefined podría no ser lo que debería ser. En aquellos días, el uso del void nos permitía garantizar la confianza en el uso de lo real undefined .

Los corchetes al llamar a los constructores son opcionales


Los corchetes que se agregan después del nombre de la clase, invocando al constructor, son completamente opcionales (a menos que el constructor necesite pasar argumentos).

En el siguiente ejemplo, la presencia o ausencia de paréntesis no afecta el funcionamiento correcto del programa.

 //     const date = new Date() const month = new Date().getMonth() const myInstance = new MyClass() //     const date = new Date const month = (new Date).getMonth() const myInstance = new MyClass 

Los soportes no se pueden usar con IIFE


La sintaxis de IIFE siempre me ha parecido extraña. ¿Por qué hay todos estos corchetes?

Resultó que los corchetes solo son necesarios para decirle al analizador de JavaScript que parte del código es una expresión funcional y no un intento incorrecto de declarar una función. Conocer este hecho nos permite comprender que hay muchas maneras de deshacerse de los corchetes en los que está encerrado el IIFE, y al mismo tiempo escribir código de trabajo.

 // IIFE (function () { console.log('Normal IIFE called') })() // Normal IIFE called void function () { console.log('Cool IIFE called') }() // Cool IIFE called 

Aquí, el operador void le dice al analizador que el código que lo sigue es una expresión funcional. Esto hace posible deshacerse de los corchetes alrededor de la declaración de función. Y, por cierto, aquí puede usar cualquier operador unario ( void , + , ! , - , etc.), y el código seguirá funcionando. ¿No es maravilloso?

Sin embargo, si es un lector atento, puede preguntarse si el operador unario afecta el resultado devuelto por IIFE. De hecho, tal como es. Pero lo bueno es que si necesita el resultado de IIFE, que, por ejemplo, almacena en una variable, entonces no necesita paréntesis alrededor de IIFE. Aquí hay un ejemplo.

 // IIFE,    let result = (function () { // ... -  return 'Victor Sully' })() console.log(result) // Victor Sully let result1 = function () { // ... -  return 'Nathan Drake' }() console.log(result1) // Nathan Drake 

Las llaves alrededor del primer IIFE solo mejoran la legibilidad del código sin afectar su funcionamiento.

Si desea comprender mejor IIFE, eche un vistazo a este material.

Construcción con


¿Sabes que JavaScript tiene una construcción with que admite bloques de expresión? Se ve así:

 with (object)  statement //       with (object) {  statement  statement  ... } 

La construcción with agrega todas las propiedades del objeto que se le pasa en la cadena de alcance utilizada al ejecutar los comandos.

 //    with const person = { firstname: 'Nathan', lastname: 'Drake', age: 29 } with (person) { console.log(`${firstname} ${lastname} is ${age} years old`) } // Nathan Drake is 29 years old 

with puede parecer una gran herramienta. Parece que es incluso mejor que las nuevas características de JS para la desestructuración de objetos , pero en realidad no lo es.

La construcción with está en desuso y no se recomienda su uso. En modo estricto, su uso está prohibido. Resulta que with bloques causan problemas de rendimiento y seguridad.

Constructor de funciones


Usar la palabra clave de function no es la única forma de definir una nueva función. Puede definir funciones dinámicamente utilizando el constructor de Function y el new operador. Así es como se ve.

 //  Function const multiply = new Function('x', 'y', 'return x*y') multiply(2,3) // 6 

El último argumento pasado al constructor es una cadena con el código de función. Otros dos argumentos son parámetros de función.

Es interesante observar que el constructor de Function es el "padre" de todos los constructores en JavaScript. Incluso el constructor de Object es un constructor de Function . Y el constructor de Function nativo también es Function . Como resultado, una llamada del tipo object.constructor.constructor... realizada para cualquier objeto JS un número suficiente de veces devolverá el constructor Function como resultado.

Propiedades de funciones


Todos sabemos que las funciones en JavaScript son objetos de primera clase. Por lo tanto, nadie nos impide agregar nuevas propiedades a las funciones. Esto es perfectamente normal, pero rara vez se usa.

¿Cuándo se puede necesitar esto?

De hecho, hay varias situaciones en las que esta capacidad de función puede ser útil. Considéralos.

▍ Características personalizadas


Supongamos que tenemos una función greet() . Necesitamos que muestre diferentes mensajes de bienvenida según la configuración regional utilizada. Estas configuraciones se pueden almacenar en una variable externa a la función. Además, la función puede tener una propiedad que define estas configuraciones, en particular, la configuración de idioma del usuario. Utilizaremos el segundo enfoque.

 //  ,   function greet () { if (greet.locale === 'fr') {   console.log('Bonjour!') } else if (greet.locale === 'es') {   console.log('Hola!') } else {   console.log('Hello!') } } greet() // Hello! greet.locale = 'fr' greet() // Bonjour! 

▍Funciones con variables estáticas


Aquí hay otro ejemplo similar. Supongamos que necesitamos implementar un determinado generador que produce una secuencia de números ordenados. Por lo general, en tales situaciones, para almacenar información sobre el último número generado, se utilizan variables de contador estáticas en clases o IIFE. Con este enfoque, restringimos el acceso al mostrador y evitamos la contaminación del alcance global con variables adicionales.

Pero, ¿qué sucede si necesitamos flexibilidad, si necesitamos leer o incluso modificar el valor de dicho contador y no obstruir el alcance global?

Por supuesto, puede crear una clase con la variable correspondiente y con métodos que le permitan trabajar con ella. O no puede molestarse con tales cosas y simplemente usar las propiedades de las funciones.

 //  ,   function generateNumber () { if (!generateNumber.counter) {   generateNumber.counter = 0 } return ++generateNumber.counter } console.log(generateNumber()) // 1 console.log(generateNumber()) // 2 console.log('current counter value: ', generateNumber.counter) // current counter value: 2 generateNumber.counter = 10 console.log('current counter value: ', generateNumber.counter) // current counter value: 10 console.log(generateNumber()) // 11 

Argumentos de propiedades de objeto


Estoy seguro de que la mayoría de ustedes saben que las funciones tienen un objeto de arguments . Este es un objeto tipo matriz accesible dentro de todas las funciones (con la excepción de las funciones de flecha, que no tienen su propio objeto de arguments ). Contiene una lista de argumentos pasados ​​a la función cuando se llamó. Además, tiene algunas propiedades interesantes:

  • arguments.callee contiene un enlace a la función actual.
  • arguments.caller contiene una referencia a la función que llamó a la función actual.

Considera un ejemplo.

 //  callee  caller  arguments const myFunction = function () { console.log('Current function: ', arguments.callee.name) console.log('Invoked by function: ', arguments.callee.caller.name) } void function main () { myFunction() } () // Current function: myFunction // Invoked by function: main 

El estándar ES5 prohíbe el uso de callee y propiedades de caller en modo estricto, pero todavía se encuentran ampliamente en muchos textos de programas compilados en JavaScript, por ejemplo, en bibliotecas. Por lo tanto, es útil saber sobre ellos.

Etiquetado Literales de plantilla


Seguramente usted, si tiene algo que ver con la programación de JavaScript, ha oído hablar de los literales de plantilla . Los literales de plantilla son una de las muchas grandes innovaciones del estándar ES6. Sin embargo, ¿sabe acerca de los literales de plantilla etiquetados?

 //    `Hello ${username}!` //    myTag`Hello ${username}!` 

Los literales de plantilla etiquetados permiten al desarrollador controlar cómo el literal de plantilla se convierte en una cadena. Esto se hace mediante el uso de etiquetas especiales. Una etiqueta es solo el nombre de una función de analizador que recibe una matriz de cadenas y valores interpretados por un patrón de cadena. Cuando se utiliza una función de etiqueta, se espera que devuelva la cadena terminada.

En el siguiente ejemplo, nuestra etiqueta, highlight , interpreta los datos de una plantilla literal e incrusta estos datos en una línea final, colocándola en la etiqueta HTML <mark> para seleccionarla cuando dicho texto se muestra en una página web.

 //    function highlight(strings, ...values) { //  i -      let result = '' strings.forEach((str, i) => {   result += str   if (values[i]) {     result += `<mark>${values[i]}</mark>`   } }) return result } const author = 'Henry Avery' const statement = `I am a man of fortune & I must seek my fortune` const quote = highlight`${author} once said, ${statement}` // <mark>Henry Avery</mark> once said, <mark>I am a man of fortune // & I must seek my fortune</mark> 

Se pueden encontrar formas interesantes de usar esta función en muchas bibliotecas. Aquí hay algunos ejemplos:


Getters y Setters en ES5


Los objetos de JavaScript, en su mayor parte, son bastante simples. Supongamos que tenemos un objeto de user e intentamos acceder a su propiedad de age utilizando la construcción user.age . Con este enfoque, si esta propiedad está definida, obtendremos su valor, y si no está definida, obtendremos undefined . Todo es muy sencillo.

Pero trabajar con propiedades no tiene que ser tan primitivo en absoluto. Los objetos JS implementan el concepto de captadores y establecedores. En lugar de devolver directamente el valor de alguna propiedad del objeto, podemos escribir nuestra propia función getter, que devuelve lo que consideramos necesario. Lo mismo se aplica a la escritura de nuevos valores en las propiedades utilizando funciones setter.

Getters y setters le permiten implementar esquemas avanzados para trabajar con propiedades. Al leer o escribir propiedades, puede usar los conceptos de campos virtuales, puede verificar los valores de los campos y, al escribir o leer, pueden producirse algunos efectos secundarios útiles.

 //    const user = { firstName: 'Nathan', lastName: 'Drake', // fullname -    get fullName() {   return this.firstName + ' ' + this.lastName }, //      set age(value) {   if (isNaN(value)) throw Error('Age has to be a number')   this._age = Number(value) }, get age() {   return this._age } } console.log(user.fullName) // Nathan Drake user.firstName = 'Francis' console.log(user.fullName) // Francis Drake user.age = '29' console.log(user.age) // 29 // user.age = 'invalid text' // Error: Age has to be a number 

Getters y setters no son innovaciones estándar de ES5. Siempre estuvieron presentes en el idioma. En ES5, solo se han agregado herramientas de sintaxis convenientes para trabajar con ellas. Los detalles sobre getters y setters se pueden encontrar aquí .

Los ejemplos del uso de getters incluyen la popular biblioteca Node.js Colors .

Esta biblioteca extiende la clase String y le agrega muchos métodos getter. Esto le permite convertir una cadena a su versión "coloreada" para que esta cadena se pueda utilizar para iniciar sesión. Esto se hace trabajando con propiedades de cadena.

Operador de coma


JS tiene un operador de coma. Le permite escribir varias expresiones en una sola línea, separadas por una coma, y ​​devolver el resultado de evaluar la última expresión. Así es como se ven esos diseños.

 let result = expression1, expression2,... expressionN 

Aquí, se calcularán los valores de todas las expresiones, después de lo cual el valor de expresiónN entrará en la variable de result .

Es posible que ya haya utilizado el operador de coma for bucles.

 for (var a = 0, b = 10; a <= 10; a++, b--) 

A veces, este operador es muy útil cuando necesita escribir varias expresiones en la misma línea.

 function getNextValue() {   return counter++, console.log(counter), counter } 

Puede ser útil al diseñar pequeñas funciones de flecha.

 const getSquare = x => (console.log (x), x * x) 

Operador plus


Si necesita convertir rápidamente una cadena en un número, el operador más es útil para usted. Es capaz de trabajar con una variedad de números, y no solo, como podría parecer, con números positivos. Estamos hablando de números negativos, octales, hexadecimales y números en notación exponencial. Además, puede convertir objetos de Date y objetos de biblioteca Moment.js en marcas de tiempo.

 //  "" +'9.11'          // 9.11 +'-4'            // -4 +'0xFF'          // 255 +true            // 1 +'123e-5'        // 0.00123 +false           // 0 +null            // 0 +'Infinity'      // Infinity +'1,234'         // NaN +new Date      // 1542975502981 ( ) +momentObject    // 1542975502981 ( ) 

Doble signo de exclamación


Cabe señalar que lo que a veces se llama el "operador de doble signo de exclamación" (Bang Bang o Double Bang) no es, de hecho, un operador. Este es un operador lógico NOT, o un operador lógico de negación que parece un signo de exclamación repetido dos veces. El doble signo de exclamación es bueno porque le permite convertir cualquier expresión en un valor booleano. Si la expresión, desde el punto de vista de JS, es verdadera, después de procesarla con un signo de exclamación doble, se devolverá true . De lo contrario, false será devuelto.

 //     !!null            // false !!undefined       // false !!false           // false !!true            // true !!""              // false !!"string"        // true !!0               // false !!1               // true !!{}              // true !![]              // true 

Operador de negación bit a bit


Seamos realistas: a nadie le importan los operadores bit a bit. No estoy hablando de usarlos. Sin embargo, el operador de negación bit a bit se puede usar en muchas situaciones.

Cuando este operador se aplica a los números, los convierte de la siguiente manera: del número N resulta -(N+1) . Tal expresión da 0 si N es -1 .

Esta característica se puede usar con el método indexOf() cuando se usa para verificar la existencia de un elemento en una matriz o en una cadena, ya que este método devuelve -1 si no se encuentra el elemento.

 //      indexOf let username = "Nathan Drake" if (~username.indexOf("Drake")) { console.log('Access denied') } else { console.log('Access granted') } 

Cabe señalar que en los estándares ES6 y ES7, respectivamente, para cadenas y matrices, ha aparecido el método includes() . Definitivamente es mucho más conveniente para determinar la presencia de elementos que usar el operador de negación bit a bit e indexOf() .

Bloques con nombre


JavaScript tiene un concepto de etiquetas, mediante el cual puede asignar nombres (etiquetas) a los bucles. Luego puede usar estas etiquetas para referirse al bucle apropiado cuando aplique declaraciones de break o continue . Las etiquetas también se pueden asignar a bloques de código regulares.

Los bucles etiquetados son útiles cuando se trabaja con bucles anidados. Pero también se pueden usar para organizar convenientemente el código en bloques o al crear bloques en los que el código se puede interrumpir.

 //    declarationBlock: { //       //     var i, j } forLoop1: //     - "forLoop1" for (i = 0; i < 3; i++) {       forLoop2: //     -  "forLoop2"  for (j = 0; j < 3; j++) {       if (i === 1 && j === 1) {        continue forLoop1     }     console.log('i = ' + i + ', j = ' + j)  } } /* i = 0, j = 0 i = 0, j = 1 i = 0, j = 2 i = 1, j = 0 i = 2, j = 0 i = 2, j = 1 i = 2, j = 2 */ //      loopBlock4: { console.log('I will print') break loopBlock4 console.log('I will not print') } // I will print 

Tenga en cuenta que, a diferencia de otros lenguajes, no hay una goto en JS. Como resultado, las etiquetas se usan solo con declaraciones de break y continue .

Resumen


En este artículo, hablamos sobre características de JavaScript poco conocidas, cuyo conocimiento es útil para cualquier programador de JS, al menos para estar listo para cumplir con algo inusual en el código de otra persona. Si el tema de "JS desconocido" le resulta interesante, puede echar un vistazo a nuestra publicación.

Estimados lectores! Si conoce algunas características poco conocidas de JS y ve opciones para su aplicación práctica, cuéntenos sobre ellas.

Source: https://habr.com/ru/post/es431878/


All Articles