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

Hoy publicamos la segunda parte de la traducción de las innovaciones de JavaScript. Aquí hablamos sobre separadores de dígitos de números, sobre números BigInt, sobre trabajar con matrices y objetos, sobre globalThis , sobre clasificación, sobre la API de internacionalización y sobre promesas.



La primera parte

Separadores de números


Los números largos que se encuentran en los programas son difíciles de leer. Por ejemplo, 1000000000 es mil millones en decimal. Pero de un vistazo es difícil de entender. Por lo tanto, si el lector del programa encuentra algo similar, él, para percibirlo correctamente, tendrá que considerar cuidadosamente los ceros.

En JavaScript moderno, puede usar el separador de dígitos de números, un guión bajo ( _ ), cuyo uso mejora la legibilidad de los números largos. Así es como se ven los números escritos usando un delimitador en el código:

 var billion = 1_000_000_000; console.log( billion ); // 1000000000 

Los separadores se pueden usar para dividir arbitrariamente los números en fragmentos. JavaScript, cuando se trata de números, simplemente ignora los separadores. Se pueden usar al escribir cualquier número: enteros, coma flotante, binario, hexadecimal, octal.

 console.log( 1_000_000_000.11 ); // 1000000000.11 console.log( 1_000_000_000.1_012 ); // 1000000000.1012 console.log( 0xFF_00_FF ); // 16711935 console.log( 0b1001_0011 ); // 147 console.log( 0o11_17 ); // 591 

→ Soporte


  • TC39: Etapa 3
  • Chrome: más de 75
  • Nodo: 12.5+

Tipo de datos Bigint


Los números en JavaScript se crean utilizando la función de constructor de Number .

El valor máximo que se puede representar de forma segura usando el tipo de datos Number es (2⁵³ - 1), es decir, 9007199254740991. Puede ver este número usando la construcción Number.MAX_SAFE_INTEGER .

Tenga en cuenta que cuando se usa un literal numérico en el código JS, JavaScript lo procesa, creando un objeto basado en él usando el constructor de Number . El prototipo de este objeto contiene métodos para trabajar con números. Esto sucede con todos los tipos de datos primitivos .

¿Qué sucederá si intentamos agregar algo al número 9007199254740991?

 console.log( Number.MAX_SAFE_INTEGER ); // 9007199254740991 console.log( Number.MAX_SAFE_INTEGER + 10 ); // 9007199254741000 

El resultado de agregar Number.MAX_SAFE_INTEGER y 10, la segunda salida de console.log() , es incorrecto. Esto se debe al hecho de que JS no puede realizar correctamente los cálculos con números mayores que el valor de Number.MAX_SAFE_INTEGER . Puede resolver este problema utilizando el bigint datos bigint .

El tipo bigint permite representar enteros mayores que Number.MAX_SAFE_INTEGER . Trabajar con valores BigInt es similar a trabajar con valores de tipo Number . En particular, el lenguaje tiene la función BigInt() , con la que puede crear los valores correspondientes, y el bigint datos primitivo bigint incorporado utilizado para representar enteros grandes.

 var large = BigInt( 9007199254740991 ); console.log( large ); // 9007199254740991n console.log( typeof large ); // bigint 

JavaScript agrega n al final de los literales BigInt. Para nosotros, esto significa que tales literales se pueden escribir agregando n al final de los enteros.

Ahora que tenemos números BigInt a nuestra disposición, podemos realizar operaciones matemáticas con seguridad en grandes números de tipo bigint .

 var large = 9007199254740991n; console.log( large + 10n ); // 9007199254741001n 

Un número de número de tipo no es lo mismo que un número de tipo bigint . En particular, estamos hablando del hecho de que los números BigInt solo pueden ser enteros. Como resultado, resulta que no puede realizar operaciones aritméticas que usen los tipos bigint y number .

Cabe señalar que la función BigInt() puede tomar varios números: decimal, binario, hexadecimal, octal. Dentro de esta función, se convertirán en números, para cuya representación se utiliza el sistema de números decimales.

El tipo bigint también admite separadores de bits:

 var large = 9_007_199_254_741_001n; console.log( large ); // 9007199254741001n 

→ Soporte



Nuevos métodos de matriz: .flat () y .flatMap ()


Aquí hablaremos sobre los nuevos métodos prototipo para el objeto Array : los .flat() y .flatMap() .

Método fla .flat ()


Ahora los objetos de tipo Array tienen un nuevo método: .flat(n) . Devuelve una nueva matriz, lo que permite elevar recursivamente los elementos de las matrices al nivel especificado n . Por defecto, n es 1. Este método puede pasar n igual a Infinity , lo que le permite convertir una matriz con matrices anidadas en una matriz unidimensional.

 var nums = [1, [2, [3, [4, 5]]]]; console.log( nums.flat() ); // [1, 2, [3, [4,5]]] console.log( nums.flat(2) ); // [1, 2, 3, [4,5]] console.log( nums.flat(Infinity) ); // [1, 2, 3, 4, 5] 

→ Soporte



Método method .flatMap ()


Al resolver tareas cotidianas, el programador a veces puede necesitar procesar la matriz usando el método .map() con su posterior transformación en una estructura plana. Por ejemplo, cree una matriz que contenga los números y cuadrados de estos números:

 var nums = [1, 2, 3]; var squares = nums.map( n => [ n, n*n ] ) console.log( squares ); // [[1,1],[2,4],[3,9]] console.log( squares.flat() ); // [1, 1, 2, 4, 3, 9] 

La solución a este problema se puede simplificar usando el método .flatMap() . Convierte las matrices devueltas por la función de devolución de llamada que se le pasó, tal como convertirían su método .flat() con el parámetro n igual a 1.

 var nums = [1, 2, 3]; var makeSquare = n => [ n, n*n ]; console.log( nums.flatMap( makeSquare ) ); // [1, 1, 2, 4, 3, 9] 

→ Soporte



▍ Método Object.fromEntries ()


Es posible extraer pares de : tipo de un objeto : se puede usar usando el Object método estático, que devuelve una matriz, cada elemento de la cual es una matriz que contiene, como primer elemento, una clave, y como segundo, un valor.

 var obj = { x: 1, y: 2, z: 3 }; var objEntries = Object.entries( obj ); console.log( objEntries ); // [["x", 1],["y", 2],["z", 3]] 

Ahora tenemos a nuestra disposición un método estático Object.fromEntries() , que nos permite convertir una estructura similar en un objeto.

 var entries = [["x", 1],["y", 2],["z", 3]]; var obj = Object.fromEntries( entries ); console.log( obj ); // {x: 1, y: 2, z: 3} 

El método de entries() se utilizó para facilitar el filtrado y el mapeo de datos almacenados en objetos. El resultado es una matriz. Pero hasta ahora, la tarea de convertir dicha matriz en un objeto no ha tenido una solución hermosa. Es para resolver este problema que puede usar el método Object.fromEntries() .

 var obj = { x: 1, y: 2, z: 3 }; // [["x", 1],["y", 2],["z", 3]] var objEntries = Object.entries( obj ); // [["x", 1],["z", 3]] var filtered = objEntries.filter( ( [key, value] ) => value % 2 !== 0 //  ,     ); console.log( Object.fromEntries( filtered ) ); // {x: 1, z: 3} 

Si la estructura Map : datos se usa para almacenar los pares : , entonces los datos se almacenan en el orden en que se agregaron. Al mismo tiempo, la forma en que se almacenan los datos se asemeja a la matriz devuelta por el método Object.entries() . El método Object.fromEntries() es fácil de usar para transformar estructuras de datos de Map en objetos.

 var m = new Map([["x", 1],["y", 2],["z", 3]]); console.log( m ); // {"x" => 1, "y" => 2, "z" => 3} console.log( Object.fromEntries( m ) ); // {x: 1, y: 2, z: 3} 

→ Soporte



▍ Propiedad global globalThis


Estamos familiarizados con this utilizada en JavaScript. No tiene un valor fijo. En cambio, el significado de this depende del contexto en el que se accede. En cualquier entorno, la this apunta a un objeto global cuando se accede desde el contexto del nivel más alto. Este es el significado global de this .

En JavaScript basado en navegador, por ejemplo, el valor global para this es el objeto de window . Puede verificar esto utilizando la construcción console.log(this) en el nivel superior del archivo JavaScript (en el contexto más externo) o en la consola JS del navegador.


Accediendo a esto en la consola del navegador

El valor global de this en Node.js apunta a un objeto global . Dentro de un trabajador web, apunta al trabajador mismo. Sin embargo, obtener el valor global de this no es una tarea fácil. El hecho es que no puedes referirte a this ningún lado. Por ejemplo, si intenta hacer esto en el constructor de la clase, resulta que this apunta a una instancia de la clase correspondiente.

En algunos entornos, la this self se puede utilizar para acceder al valor global de this . Esta palabra clave juega el mismo papel que los mecanismos para acceder a este valor en los navegadores, en Node.js y en los trabajadores web. Usando el conocimiento de cómo se llama el valor global de this en diferentes entornos, puede crear una función que devuelva este valor:

 const getGlobalThis = () => { if (typeof self !== 'undefined') return self; if (typeof window !== 'undefined') return window; if (typeof global !== 'undefined') return global; if (typeof this !== 'undefined') return this; throw new Error('Unable to locate global `this`'); }; var globalThis = getGlobalThis(); 

Ante nosotros hay un polyfill primitivo para obtener el objeto global. Lea más sobre esto aquí . JavaScript ahora tiene la palabra clave globalThis . Proporciona una forma universal de acceder al valor global de this para diferentes entornos y no depende de la ubicación del programa desde el que se accede.

 var obj = { fn: function() {  console.log( 'this', this === obj ); // true  console.log( 'globalThis', globalThis === window ); // true } }; obj.fn(); 

→ Soporte



Clasificación estable


El estándar ECMAScript no ofrece un algoritmo de ordenación de matriz específico que los motores JavaScript deberían implementar. Solo describe la API utilizada para ordenar. Como resultado, al usar diferentes motores JS, uno puede encontrar diferencias en el rendimiento de las operaciones de clasificación y en la estabilidad (estabilidad) de los algoritmos de clasificación.

Ahora el estándar requiere que los arreglos de clasificación sean estables. Los detalles sobre la estabilidad de la clasificación se pueden encontrar aquí . La esencia de esta característica de los algoritmos de clasificación es la siguiente. El algoritmo es estable si el resultado de la clasificación, que es una matriz modificada, contiene elementos con los mismos valores que no se vieron afectados por la clasificación en el mismo orden en que se colocaron en la matriz original. Considere un ejemplo:

 var list = [  { name: 'Anna', age: 21 },  { name: 'Barbra', age: 25 },  { name: 'Zoe', age: 18 },  { name: 'Natasha', age: 25 } ]; //      age [  { name: 'Natasha', age: 25 }  { name: 'Barbra', age: 25 },  { name: 'Anna', age: 21 },  { name: 'Zoe', age: 18 }, ] 

Aquí, la matriz de list contiene los objetos se ordena por el campo de age de estos objetos. En la matriz de list , un objeto con la propiedad de name igual a Barbra se encuentra antes del objeto con la propiedad de name igual a Natasha . Dado que age valores de age de estos objetos son iguales, podríamos esperar que en la matriz ordenada estos elementos retengan el orden de disposición anterior entre sí. Sin embargo, en la práctica esto no puede esperarse. La forma exacta en que se formará la matriz ordenada dependía completamente del motor JS utilizado.

Ahora todos los navegadores modernos y Node.js usan un algoritmo de ordenación estable, llamado al acceder al método de matriz .sort() . Esto le permite siempre, para los mismos datos, obtener el mismo resultado:

 //    [  { name: 'Barbra', age: 25 },  { name: 'Natasha', age: 25 }  { name: 'Anna', age: 21 },  { name: 'Zoe', age: 18 }, ] 

En el pasado, algunos motores JS admitían una clasificación estable, pero solo para arreglos pequeños. Para mejorar el rendimiento al procesar matrices grandes, podrían usar algoritmos más rápidos y sacrificar la estabilidad de clasificación.

→ Soporte


  • Chrome: 70+
  • Nodo: 12+
  • Firefox: 62+

API de internacionalización


La API de internacionalización está diseñada para organizar comparaciones de cadenas, para formatear números, fechas y horas, como es habitual en varios estándares regionales (locales). El acceso a esta API se organiza a través del objeto Intl . Este objeto proporciona constructores para crear objetos de clasificación y objetos que formatean datos. La lista de configuraciones regionales admitidas por el objeto Intl se puede encontrar aquí .

▍Intl.RelativeTimeFormat ()


En muchas aplicaciones, a menudo es necesario mostrar el tiempo en un formato relativo. Puede parecer "hace 5 minutos", "ayer", "hace 1 minuto", y así sucesivamente. Si los materiales del sitio web se traducen a diferentes idiomas, debe incluir todas las combinaciones posibles de construcciones relativas que describan el tiempo en el ensamblaje del sitio.

JS ahora tiene el Intl.RelativeTimeFormat(locale, config) , que le permite crear sistemas de formato de fecha y hora para diferentes configuraciones regionales. En particular, estamos hablando de objetos que tienen un método .format(value, unit) , que le permite generar varias marcas de tiempo relativas. Se ve así:

 // español ( ) var rtfEspanol= new Intl.RelativeTimeFormat('es', {  numeric: 'auto' }); console.log( rtfEspanol.format( 5, 'day' ) ); // dentro de 5 días console.log( rtfEspanol.format( -5, 'day' ) ); // hace 5 días console.log( rtfEspanol.format( 15, 'minute' ) ); // dentro de 15 minutos 

→ Soporte



▍Intl.ListFormat ()


El constructor Intl.ListFormat permite combinar elementos de la lista usando las palabras and ( ) y or ( ). Al crear el objeto correspondiente, al constructor se le pasa la configuración regional y el objeto con los parámetros. Su parámetro de type puede ser conjunction , disjunction y unit . Por ejemplo, si queremos combinar los elementos de [apples, mangoes, bananas] usando un objeto de conjunción, obtenemos una cadena de la forma apples, mangoes and bananas . Si usamos un objeto de disyunción, obtenemos una cadena de apples, mangoes or bananas .

El objeto creado por el constructor Intl.ListFormat tiene un .format(list) que combina listas. Considere un ejemplo:

 // español ( ) var lfEspanol = new Intl.ListFormat('es', {  type: 'disjunction' }); var list = [ 'manzanas', 'mangos', 'plátanos' ]; console.log( lfEspanol.format( list ) ); // manzanas, mangos o plátanos 

→ Soporte



▍Intl.Locale ()


El concepto de "estándar regional" suele ser mucho más que el simple nombre de un idioma. Esto puede incluir el tipo de calendario, información sobre los ciclos de tiempo utilizados y los nombres de los idiomas. El constructor Intl.Locale(localeId, config) usa para crear cadenas de Intl.Locale(localeId, config) formateadas basadas en el objeto de config que se le pasa.

Intl.Locale objeto creado con Intl.Locale contiene todas las configuraciones regionales especificadas. Su método .toString() produce una cadena estándar regional formateada.

 const krLocale = new Intl.Locale( 'ko', {  script: 'Kore', region: 'KR',  hourCycle: 'h12', calendar: 'gregory' } ); console.log( krLocale.baseName ); // ko-Kore-KR console.log( krLocale.toString() ); // ko-Kore-KR-u-ca-gregory-hc-h12 

Aquí puede leer sobre identificadores y etiquetas locales en Unicode.

→ Soporte



Promesas


A partir de ahora, JS tiene los métodos estáticos Promise.all() y Promise.race() . El Promise.all([...promises]) devuelve una promesa que se resuelve con éxito después de que se resuelven todas las promesas pasadas al método como argumento. Esta promesa es rechazada en el caso de que al menos una de las promesas transferidas a ella sea rechazada. El Promise.race([...promises]) devuelve una promesa, que se resuelve después de que se resuelve cualquiera de las promesas transferidas y se rechaza si se rechaza al menos una de esas promesas.

La comunidad de desarrolladores de JS estaba desesperada por un método estático, la promesa devuelta que se resolvería después de que todas las promesas que se le pasaran se completaran (permitidas o rechazadas). Además, necesitábamos un método similar a race() , que devolvería una promesa a la espera de la resolución de cualquiera de las promesas que se le pasaran.

Method Método Promise.allSettled ()


El método Promise.allSettled() acepta una serie de promesas. La promesa devuelta por él está permitida después de que todas las promesas sean rechazadas o permitidas. El resultado es que la promesa devuelta por este método no necesita un catch .

El hecho es que esta promesa siempre se resuelve con éxito. El bloque then recibe el status y el value de cada promesa en el orden en que aparecen.

 var p1 = () => new Promise(  (resolve, reject) => setTimeout( () => resolve( 'val1' ), 2000 ) ); var p2 = () => new Promise(  (resolve, reject) => setTimeout( () => resolve( 'val2' ), 2000 ) ); var p3 = () => new Promise(  (resolve, reject) => setTimeout( () => reject( 'err3' ), 2000 ) ); var p = Promise.allSettled( [p1(), p2(), p3()] ).then(  ( values ) => console.log( values ) ); //  [ {status: "fulfilled", value: "val1"}  {status: "fulfilled", value: "val2"}  {status: "rejected", value: "err3"} ] 

→ Soporte



▍ Método Promise.any ()


El método Promise.any() es similar a Promise.race() , pero la promesa que devuelve no ejecuta el catch cuando se rechaza una de las promesas pasadas a este método.

En cambio, espera la resolución de todas las promesas. Si no se permitieron promesas, se ejecutará el bloque catch . Si alguna de las promesas se resuelve con éxito, se ejecutará.

Resumen


En este artículo, analizamos algunas de las innovaciones de JavaScript discutidas en la conferencia Google I / O 2019 . Esperamos que encuentre algo entre ellos que le sea útil.

Estimados lectores! ¿Qué extrañas especialmente en JavaScript?

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


All Articles