Innovaciones de JavaScript: resultados de Google I / O 2019. Parte 1

El material, cuya primera parte de la traducción publicamos hoy, está dedicado a las nuevas características estándar de JavaScript que se discutieron en la conferencia Google I / O 2019 . En particular, aquí hablaremos sobre expresiones regulares, sobre campos de clase, sobre cómo trabajar con cadenas.



Verificaciones de expresiones regulares


Las expresiones regulares (Expresión regular, para abreviar: RegEx o RegExp) es una poderosa tecnología de procesamiento de cadenas que se implementa en muchos lenguajes de programación. Las expresiones regulares son muy útiles en casos donde necesita, por ejemplo, buscar fragmentos de cadenas por patrones complejos. Hasta hace poco, la implementación de JavaScript de expresiones regulares tenía todo menos mirar hacia atrás.

Para entender qué es una verificación retrospectiva, primero hablemos de los lookaheads que ya son compatibles con JavaScript.

La segunda parte

▍ Verificación anticipada


La sintaxis de las comprobaciones iniciales en las expresiones regulares le permite buscar fragmentos de cadenas cuando se sabe que hay otros fragmentos a la derecha de ellas. Por ejemplo, cuando trabaje con la cadena MangoJuice, VanillaShake, GrapeJuice puede usar la sintaxis de una verificación MangoJuice, VanillaShake, GrapeJuice positiva para encontrar las palabras seguidas inmediatamente por la palabra Juice . En nuestro caso, estas son las palabras Mango y Grape .

Hay dos tipos de cheques principales. Estas son miradas positivas y negativas.

Comprobación de plomo positiva


Se utiliza una verificación inicial positiva para buscar líneas a la derecha de las cuales hay otras líneas conocidas previamente. Así es como se ve la sintaxis de expresión regular utilizada para esta verificación:

 /[a-zA-Z]+(?=Juice)/ 

Esta plantilla le permite seleccionar palabras que consisten en letras minúsculas o mayúsculas, seguidas de la palabra Juice . No confunda las estructuras que describen controles iniciales y retrospectivos con grupos de captura. Aunque las condiciones de estas comprobaciones se escriben entre paréntesis, el sistema no las captura. Veamos un ejemplo de una verificación de plomo positiva.

 const testString = "MangoJuice, VanillaShake, GrapeJuice"; const testRegExp = /[a-zA-Z]+(?=Juice)/g; const matches = testString.match( testRegExp ); console.log( matches ); // ["Mango", "Grape"] 

Verificación de plomo negativo


Si consideramos, utilizando la línea anterior, el mecanismo de acción de las comprobaciones iniciales negativas, resulta que le permiten encontrar palabras a la derecha de las cuales no hay palabra Juice . La sintaxis de las comprobaciones iniciales negativas es similar a la sintaxis de las comprobaciones positivas. Sin embargo, hay una característica, ¡que el símbolo = (igual) cambia a un símbolo ! (signo de exclamación) Así es como se ve:

 /[a-zA-Z]+(?!Juice)/ 

Esta expresión regular le permite seleccionar todas las palabras a la derecha de las cuales no hay palabra Juice . Pero al aplicar dicha plantilla, se seleccionarán todas las palabras en la línea ( MangoJuice, VanillaShake, GrapeJuice ). El hecho es que, según el sistema, ni una sola palabra termina aquí con Juice . Como resultado, para lograr el resultado deseado, debe aclarar la expresión regular y reescribirla así:

 /(Mango|Vanilla|Grape)(?!Juice)/ 

El uso de esta plantilla le permite seleccionar las palabras Mango , o Vanilla , o Grape , después de lo cual no hay palabra Juice . Aquí hay un ejemplo:

 const testString = "MangoJuice, VanillaShake, GrapeJuice"; const testRegExp = /(Mango|Vanilla|Grape)(?!Juice)/g; const matches = testString.match( testRegExp ); console.log( matches ); // ["Vanilla"] 

▍ Verificación retrospectiva


Por analogía con la sintaxis de las comprobaciones iniciales, la sintaxis de las comprobaciones retrospectivas le permite seleccionar secuencias de caracteres solo si a la izquierda de estas secuencias hay un patrón dado. Por ejemplo, al procesar la cadena FrozenBananas, DriedApples, FrozenFish podemos usar una verificación retrospectiva positiva para encontrar palabras a la izquierda de las cuales está la palabra Frozen . En nuestro caso, las palabras Bananas y Fish corresponden a esta condición.

Existen, como es el caso de las verificaciones iniciales, verificaciones retrospectivas positivas (retrospectiva positiva) y verificaciones retrospectivas negativas (retrospectiva negativa o negativa).

Revisión retrospectiva positiva


Las comprobaciones retrospectivas positivas se utilizan para buscar patrones a la izquierda de los cuales hay otros patrones. Aquí hay un ejemplo de la sintaxis utilizada para describir tales comprobaciones:

 /(?<=Frozen)[a-zA-Z]+/ 

Aquí < usa el símbolo < , que no estaba en la descripción de las comprobaciones iniciales. Además, la condición en la expresión regular se encuentra no a la derecha de la plantilla que nos interesa, sino a la izquierda. Usando la plantilla anterior, puede seleccionar todas las palabras que comienzan con Frozen . Considere un ejemplo:

 const testString = "FrozenBananas, DriedApples, FrozenFish"; const testRegExp = /(?<=Frozen)[a-zA-Z]+/g; const matches = testString.match( testRegExp ); console.log( matches ); // ["Bananas", "Fish"] 

Verificación retrospectiva negativa


El mecanismo de comprobaciones retrospectivas negativas le permite buscar patrones en las líneas a la izquierda de las cuales no hay un patrón especificado. Por ejemplo, si necesita seleccionar palabras que no comienzan con Frozen en la línea FrozenBananas, DriedApples, FrozenFish , puede intentar usar esta expresión regular:

 /(?<!Frozen)[a-zA-Z]+/ 

Pero, dado que el uso de esta construcción conducirá a la selección de todas las palabras de la cadena, ya que ninguna de ellas comienza con Frozen , la expresión regular debe aclararse:

 /(?<!Frozen)(Bananas|Apples|Fish)/ 

Aquí hay un ejemplo:

 const testString = "FrozenBananas, DriedApples, FrozenFish"; const testRegExp = /(?<!Frozen)(Bananas|Apples|Fish)/g; const matches = testString.match( testRegExp ); console.log( matches ); // ["Apples"] 

→ Soporte


Esta y otras secciones similares proporcionarán información sobre la etapa de armonización de las características descritas de JS en el Comité Técnico 39 (Comité Técnico 39, TC39), que es responsable en ECMA International de respaldar las especificaciones de ECMAScript. Dichas secciones también proporcionarán datos sobre las versiones de Chrome y Node.js (y, a veces, sobre la versión de Firefox), comenzando con las cuales puede utilizar las funciones correspondientes.


Campos de clase


Un campo de clase es una nueva construcción de sintaxis utilizada para definir las propiedades de las instancias de clase (objetos) fuera del constructor de la clase. Hay dos tipos de campos de clase: campos de clase pública y campos de clase privada.

▍ Campos de clase pública


Hasta hace poco, las propiedades de los objetos tenían que definirse dentro del constructor de la clase. Estas propiedades eran públicas (públicas). Esto significa que se puede acceder a ellos trabajando con una instancia de la clase (objeto). Aquí hay un ejemplo de declarar una propiedad pública:

 class Dog {    constructor() {        this.name = 'Tommy';    } } 

Cuando era necesario crear una clase que extendiera cierta clase padre, era necesario llamar a super() en el constructor de la clase hija. Esto tenía que hacerse antes de que sus propias propiedades pudieran agregarse a la clase secundaria. Así es como se ve:

 class Animal {} class Dog extends Animal {    constructor() {        super(); //  super   `this`          this.sound = 'Woof! Woof!';    }    makeSound() {        console.log( this.sound );    } } //    const tommy = new Dog(); tommy.makeSound(); // Woof! Woof! 

Gracias a la aparición de la sintaxis de los campos públicos de una clase, es posible describir los campos de clase fuera del constructor. El sistema realizará una llamada implícita a super() .

 class Animal {} class Dog extends Animal {    sound = 'Woof! Woof!'; //       makeSound() {        console.log( this.sound );    } } //    const tommy = new Dog(); tommy.makeSound(); // Woof! Woof! 

Cuando se llama implícitamente a super() , todos los argumentos proporcionados por el usuario al crear la instancia de clase se le pasan (este es el comportamiento estándar de JavaScript, no hay nada especial en los campos de clase privada). Si el constructor de la clase padre necesita argumentos preparados de una manera especial, debe llamar a super() usted mismo. Eche un vistazo a los resultados de la llamada al constructor implícito de la clase principal al crear una instancia de la clase secundaria.

 class Animal {    constructor( ...args ) {        console.log( 'Animal args:', args );    } } class Dog extends Animal {    sound = 'Woof! Woof!'; //    makeSound() {        console.log( this.sound );    } } //    const tommy = new Dog( 'Tommy', 'Loves', 'Toys!' ); tommy.makeSound(); // Animal args: [ 'Tommy', 'Loves', 'Toys!' ] 

▍ Campos de clase privada


Como sabes, en JavaScript no hay modificadores de acceso a campos de clase como public , private o protected . Todas las propiedades de los objetos son públicas de forma predeterminada. Esto significa que el acceso a ellos es ilimitado. Lo más parecido a hacer que una propiedad de un objeto sea similar a una propiedad privada es usar el tipo de datos Symbol . Esto le permite ocultar las propiedades de los objetos del mundo exterior. Es posible que haya utilizado nombres de propiedades con el prefijo _ (guión bajo) para indicar que las propiedades correspondientes deben considerarse destinadas solo para su uso dentro del objeto. Sin embargo, esta es solo una especie de notificación para aquellos que utilizarán la instalación. Esto no resuelve el problema de la restricción real del acceso a las propiedades.

Gracias al mecanismo de los campos privados de clases, es posible hacer que las propiedades de la clase sean accesibles solo dentro de esta clase. Esto lleva al hecho de que no se puede acceder desde el exterior y trabajar con una instancia de la clase (objeto). Tome el ejemplo anterior e intente acceder a la propiedad de la clase desde afuera, cuando se utilizó el prefijo _ .

 class Dog {    _sound = 'Woof! Woof!'; //            makeSound() {        console.log( this._sound );    } } //    const tommy = new Dog(); console.log( tommy._sound ); // Woof! Woof! 

Como puede ver, usar el prefijo _ no resuelve nuestro problema. Los campos privados de clases se pueden declarar de la misma manera que los campos públicos, pero en lugar de un prefijo en forma de guión bajo, debe agregar un prefijo en forma de signo de # ( # ) a sus nombres. Un intento de acceso no autorizado a la propiedad privada del objeto declarada de esta manera dará como resultado el siguiente error:

 SyntaxError: Undefined private field 

Aquí hay un ejemplo:

 class Dog {    #sound = 'Woof! Woof!'; //  -      makeSound() {        console.log( this.#sound );    } } //    const tommy = new Dog(); tommy.makeSound() // Woof! Woof! //console.log( tommy.#sound ); // SyntaxError 

Tenga en cuenta que solo se puede acceder a las propiedades privadas desde la clase en la que se declaran. Como resultado, las clases descendientes no pueden usar directamente propiedades similares de la clase padre.

Los campos privados (y públicos) se pueden declarar sin escribir ciertos valores en ellos:

 class Dog {    #name;    constructor( name ) {        this.#name = name;    }    showName() {        console.log( this.#name );    } } //    const tommy = new Dog( 'Tommy' ); tommy.showName(); // Tommy 

→ Soporte



Método de cadena .matchAll ()


El prototipo de tipo de datos de String tiene un método .match() que devuelve una matriz de fragmentos de cadena que coinciden con la condición especificada por la expresión regular. Aquí hay un ejemplo usando este método:

 const colors = "#EEE, #CCC, #FAFAFA, #F00, #000"; const matchColorRegExp = /([A-Z0-9]+)/g; console.log( colors.match( matchColorRegExp ) ); // : ["EEE", "CCC", "FAFAFA", "F00", "000"] 

Sin embargo, cuando se utiliza este método, no se proporciona información adicional (como índices) sobre los fragmentos encontrados de la cadena. Si elimina el indicador g de la expresión regular pasada al método .match() , devolverá una matriz que contendrá información adicional sobre los resultados de búsqueda. Sin embargo, con este enfoque, solo se encontrará el primer fragmento de la cadena que coincida con la expresión regular.

 const colors = "#EEE, #CCC, #FAFAFA, #F00, #000"; const matchColorRegExp = /#([A-Z0-9]+)/; console.log( colors.match( matchColorRegExp ) ); // : (       ) ["#EEE", "EEE", index: 0, input: "<colors>"] 

Para obtener algo similar, pero para varios fragmentos de una cadena, deberá usar el método de expresión regular .exec() . Las construcciones que se necesitan para esto son más complicadas que aquella en la que se usaría un método de cadena única para obtener resultados similares. En particular, aquí necesitamos un while que se ejecutará hasta que .exec() devuelva null . Con este enfoque, tenga en cuenta que .exec() no devuelve un iterador.

 const colors = "#EEE, #CCC, #FAFAFA, #F00, #000"; const matchColorRegExp = /#([A-Z0-9]+)/g; //        , // Uncaught ReferenceError: match is not defined while( match = matchColorRegExp.exec( colors ) ) {  console.log( match ); } // : (       ) ["#EEE", "EEE", index: 0, input: "<colors>"] ["#CCC", "CCC", index: 6, input: "<colors>"] ["#FAFAFA", "FAFAFA", index: 12, input: "<colors>"] ["#F00", "F00", index: 21, input: input: "<colors>"] ["#000", "000", index: 27, input: input: "<colors>"] 

Para resolver estos problemas, ahora podemos usar el método de cadena .matchAll() , que devuelve un iterador. Cada llamada al método .next() de este iterador .next() siguiente elemento de los resultados de búsqueda. Como resultado, el ejemplo anterior se puede reescribir de la siguiente manera:

 const colors = "#EEE, #CCC, #FAFAFA, #F00, #000"; const matchColorRegExp = /#([A-Z0-9]+)/g; console.log( ...colors.matchAll( matchColorRegExp ) ); // : (       ) ["#EEE", "EEE", index: 0, input: "<colors>"] ["#CCC", "CCC", index: 6, input: "<colors>"] ["#FAFAFA", "FAFAFA", index: 12, input: "<colors>"] ["#F00", "F00", index: 21, input: input: "<colors>"] ["#000", "000", index: 27, input: input: "<colors>"] 

→ Soporte



Grupos nombrados en expresiones regulares


El concepto de grupos en la implementación de JavaScript de los mecanismos de expresión regular es ligeramente diferente de la implementación de un concepto similar en otros lenguajes. Es decir, cuando, usando JavaScript, la plantilla RegEx se coloca entre paréntesis (excepto cuando los paréntesis se usan para verificaciones retrospectivas o avanzadas), la plantilla se convierte en un grupo.

Los fragmentos de la cadena capturados por el grupo se reflejarán en los resultados de la aplicación de la expresión regular.

En el ejemplo anterior, podría ver que el primer elemento de la matriz con los resultados de búsqueda es el que coincide con la expresión regular completa, y el segundo es el que corresponde al grupo. Aquí está este elemento de matriz:

 ["#EEE", "EEE", index: 0, input: "<colors>"] 

Si hay varios grupos en la expresión regular, entrarán en los resultados del procesamiento de la cadena en el orden de su descripción en la expresión regular. Considere un ejemplo:

 const str = "My name is John Doe."; const matchRegExp = /My name is ([az]+) ([az]+)/i; const result = str.match( matchRegExp );console.log( result ); //   result  null -   console.log( { firstName: result[1], lastName: result[2] } ); // : ["My name is John Doe", "John", "Doe", index: 0, input: "My name is John Doe.", groups: undefined] {firstName: "John", lastName: "Doe"} 

Aquí puede ver que la primera línea de la salida es la línea completa correspondiente a la expresión regular. El segundo y tercer elemento representan lo que fue capturado por los grupos.

El uso de grupos con nombre le permite guardar qué grupos están capturando dentro del objeto de groups , cuyos nombres de propiedad corresponden a los nombres asignados a los grupos.

 const str = "My name is John Doe."; const matchRegExp = /My name is (?<firstName>[az]+) (?<lastName>[az]+)/i; const result = str.match( matchRegExp ); console.log( result ); console.log( result.groups ); // : ["My name is John Doe", "John", "Doe", index: 0, input: "My name is John Doe.", groups: {firstName: "John", lastName: "Doe"}] {firstName: "John", lastName: "Doe"} 

Cabe señalar que los grupos con nombre funcionan bien junto con el método .matchAll() .

→ Soporte



Continuará ...

Estimados lectores! ¿Has utilizado alguna de las innovaciones de JavaScript descritas aquí?

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


All Articles