De conversão de valores a cálculos matemáticos, ES6 agrega muitas propriedades estáticas e métodos a vários nativos e objetos globais para ajudar com tarefas comuns. Além disso, instâncias de alguns dos nativos têm novas capacidades através de vários métodos de prototipagem.
Nota: A maioria dessas funcionalidades podem ser fielmente polyfilled. Nós não vamos entrar em detalhes aqui, mas cheque o "ES6 Shim" (https://github.com/paulmillr/es6-shim/) para padrões compátiveis de shims/polyfills.
Uma das funcionalidades em JS mais comumente estendidas por várias bibliotecas é o Array type. Não deveria ser surpresa que o ES6 adiciona uma grande quantidade de helpers para Array, tanto estático quanto prototipagem (instancia).
Há uma pegadinha bem conhecida com o construtor Array(..)
, que é se só um argumento é passado e esse argumento é um número, ao invés de criar um array de um elemento contendo esse valor, ele constrói um array vazio com uma propriedade length
igual ao número. Essa ação produz o infeliz e peculiar comportamento do "slot vazio" que os arrays Javascript tanto são criticados.
Array.of(..)
substitui Array(..)
como o formato preferido de construtor para arrays, porque Array.of(..)
não tem aquele caso especial de argumento-numérico-único. Considere:
var a = Array( 3 );
a.length; // 3
a[0]; // undefined
var b = Array.of( 3 );
b.length; // 1
b[0]; // 3
var c = Array.of( 1, 2, 3 );
c.length; // 3
c; // [1,2,3]
Sob essas circunstâncias, você iria querer usar Array.of(..)
ao invés de apenas criar um array com sintaxe literal, como c = [1,2,3]
? Há dois casos possíveis.
Se você tem um callback que deve agrupar o(s) argumento(s) passados a ele em um array, Array.of(..)
encaixa perfeitamente. Isso não é muito comum, mas pode quebrar seu galho.
O outro cenário é se você criar uma subclasse de Array
(veja "Classes" no Capítulo 3) e quiser criar e inicializar elementos na instância do seu objeto, como por exemplo:
class MyCoolArray extends Array {
sum() {
return this.reduce( function reducer(acc,curr){
return acc + curr;
}, 0 );
}
}
var x = new MyCoolArray( 3 );
x.length; // 3 -- oops!
x.sum(); // 0 -- oops!
var y = [3]; // Array, not MyCoolArray
y.length; // 1
y.sum(); // `sum` is not a function
var z = MyCoolArray.of( 3 );
z.length; // 1
z.sum(); // 3
Você não pode criar (facilmente) um construtor para MyCoolArray
que sobrescreve o comportamento do construtor pai Array
, porque esse construtor é necessário para criar um valor de array que se comporte bem (inicializando o this
). O método "herdado" estático of(..)
na subclasse MyCoolArray
provê uma boa solução.
Um objeto array-like em JavaScript é um objeto que tem uma propriedade length
especificamente com um valor inteiro, igual ou maior que zero.
Esses valores têm sido notóriamente frustrantes de se trabalhar em JS; É bem comum que seja preciso transformá-los em um verdadeiro array, assim os vários métodos do Array.prototype
(map(..)
, indexOf(..)
etc) podem ser usados. Esse processo geralmente é assim:
// array-like object
var arrLike = {
length: 3,
0: "foo",
1: "bar"
};
var arr = Array.prototype.
.call( arrLike );
Outra tarefa comum onde geralmente se utiliza o slice(..)
é para duplicar um array real:
var arr2 = arr.slice();
Em ambos os casos, o novo método do ES6 Array.from(..)
pode ter uma abordagem mais compreensivel e elegante -- e também menos verbosa:
var arr = Array.from( arrLike );
var arrCopy = Array.from( arr );
Array.from(..)
verifica se o primeiro argumento é um iterável (veja "Iteradores" no capítulo 3), e então, usa o iterador para produzir valores para "copiar" para o array retornado. Por conta dos arrays reais terem um iterador para esses valores, o iterador é automaticamente usado.
Mas se você passar um objeto array-like como primeiro argumento ao Array.from(..)
, ele se comporta basicamente da mesma forma que o slice()
(sem argumentos!) ou apply(..)
fazem, que é simplesmente percorrer o valor, acessando propriedades nomeadas numericamente de 0
até o valor de length
.
Considere:
var arrLike = {
length: 4,
2: "foo"
};
Array.from( arrLike );
// [ undefined, undefined, "foo", undefined ]
Por conta das posições 0
, 1
, e 3
não existirem no arrLike
, o resultado foi um valor undefined
para cada um desses espaços.
Você pode produzir uma saída similar assim:
var emptySlotsArr = [];
emptySlotsArr.length = 4;
emptySlotsArr[2] = "foo";
Array.from( emptySlotsArr );
// [ undefined, undefined, "foo", undefined ]
Há uma diferença sutil mas importante no fragmento anterior entre o emptySlotsArr
e o resultado da chamada do Array.from(..)
. Array.from(..)
nunca produz espaços vazios.
Antes do ES6, se você quisesse criar um array inicializado com um certo tamanho e valores undefined
em cada espaço (e não espaços vazios!), você tinha que fazer um trabalho extra:
var a = Array( 4 ); // four empty slots!
var b = Array.apply( null, { length: 4 } ); // four `undefined` values
Mas Array.from(..)
agora torna isso mais fácil:
var c = Array.from( { length: 4 } ); // four `undefined` values
Atenção: Usar um espaço vazio como a
no fragmento anterior pode funcionar com algumas funções de array, mas outras ignoram espaços vazios (como map(..)
, etc). Você não deve nunca trabalhar intencionalmente com espaços vazios, já que é quase certo que vai resultar em comportamentos estranhos/imprevisíveis nos seus programas.
O utilitário Array.from(..)
tem outra carta na manga. O segundo argumento, se passado, é um mapping callback (quase igual ao que o Array#map(..)
regular espera) que é chamado para mapear/transformar cada valor do original ao alvo retornado. Considere:
var arrLike = {
length: 4,
2: "foo"
};
Array.from( arrLike, function mapper(val,idx){
if (typeof val == "string") {
return val.toUpperCase();
}
else {
return idx;
}
} );
// [ 0, 1, "FOO", 3 ]
Nota: Assim como outros métodos de array que aceitam callbacks, Array.from(..)
aceita um terceiro argumento opicional que se for definido, vai especificar o this
para o callback passado no segundo argumento. Caso contrário, this
vai ser undefined
.
Veja "Arrays tipados" no Capítulo 5 para um exemplo de uso do Array.from(..)
traduzindo valores de um array de valores 8-bit para um array de valores 16-bit.
Nas duas últimas seções, nós discutimos Array.of(..)
e Array.from(..)
, ambos para criar um novo array de forma similar a um construtor. Mas o que eles fazem nas subclasses? Eles criam instâncias do Array
base ou de subclasses derivadas?
class MyCoolArray extends Array {
..
}
MyCoolArray.from( [1, 2] ) instanceof MyCoolArray; // true
Array.from(
MyCoolArray.from( [1, 2] )
) instanceof MyCoolArray; // false
Ambos of(..)
e from(..)
usam o construtor que eles são acessados para construir o array. Então se você usa a base Array.of(..)
você vai ter uma instância de Array
, mas se você usa MyCollArray.of(..)
, você vai ter uma instância de MyCoolArray
.
Em "Classes" no Capítulo 3, nós cobrimos a configuração @@species
que todas as classes nativas (como Array
) têm definidas, que são usadas por qualquer método prototipado se eles criam uma nova instância. slice(..)
é um ótimo exemplo:
var x = new MyCoolArray( 1, 2, 3 );
x.slice( 1 ) instanceof MyCoolArray; // true
Geralmente, o comportamento padrão provavelmente vai ser o desejado, mas como discutimos no Capítulo 3, você pode sobrescrevê-lo se quiser:
class MyCoolArray extends Array {
// force `species` to be parent constructor
static get [Symbol.species]() { return Array; }
}
var x = new MyCoolArray( 1, 2, 3 );
x.slice( 1 ) instanceof MyCoolArray; // false
x.slice( 1 ) instanceof Array; // true
É importante notar que a configuração de @@species
só é usada para métodos prototipados, como slice(..)
. Não é usado por of(..)
e from(..)
; ambos somente usam o this (qualquer que seja o construtor usado para fazer referência). Considere:
class MyCoolArray extends Array {
// force `species` to be parent constructor
static get [Symbol.species]() { return Array; }
}
var x = new MyCoolArray( 1, 2, 3 );
MyCoolArray.from( x ) instanceof MyCoolArray; // true
MyCoolArray.of( [2, 3] ) instanceof MyCoolArray; // true
Array#copyWithin(..)
é um novo método mutador disponível para todos os arrays (incluindo Arrays Tipados; veja o capítulo 5). copyWithin(..)
copia uma porção de um array a outro local no mesmo array, sobrescrevendo o que quer que estivesse lá antes.
Os argumentos são target (o índice a ser copiado), start (o índice inclusivo que vamos partir a cópia), e opcionalmente end (o índice exclusivo que vamos parar de copiar). Se algum dos argumentos for negativo, eles passam a ser relativos ao final do array.
Considere:
[1,2,3,4,5].copyWithin( 3, 0 ); // [1,2,3,1,2]
[1,2,3,4,5].copyWithin( 3, 0, 1 ); // [1,2,3,1,5]
[1,2,3,4,5].copyWithin( 0, -2 ); // [4,5,3,4,5]
[1,2,3,4,5].copyWithin( 0, -2, -1 ); // [4,2,3,4,5]
O método copyWithin(..)
não estende o tamanho do array, como o primeiro trecho do exemplo mostra. A cópia simplesmente para quando chega no final do array.
Ao contrário do que você provavelmente pensa, a cópia nem sempre vai da ordem direita-para-esquerda (índice ascendente). É possível que isso possa resultar em repetidamente copiar algum valor já copiado se o intervalo do alvo e do início sobrepor, o que se presume ser um comportamento não esperado.
Então internamente, o algoritmo evita esse caso copiando na ordem inversa para evitar essa pegadinha. Considere:
[1,2,3,4,5].copyWithin( 2, 1 ); // ???
Se o algoritmo foi movido estritamente da direita pra esquerda, então o 2
deve ser copiado para sobrescrever o 3
, então esse 2
copiado deve ser copiado para sobrescrever o 4
, então esse 2
copiado deve ser copiado para sobrescrever o 5
, e você terminaria com [1,2,2,2,2]
.
Ao invés disso, o algorítmo de cópia inverte a direção e copia o 4
para sobrescrever o 5
, então copia o 3
para sobrescrever o 4
, então copia o 2
para sobrescrever o 3
, e o resultado final é [1,2,2,3,4]
. Esse é provavelmente o mais "correto" em termos de expectativa, mas pode ser confuso se você está apenas pensando no algorítimo de cópia na moda indênua direita-para-esquerda.
Preencher um array existente por completo (ou parcialmente) com um valor específico é nativamente suportado em ES6 com o método Array#fill(..)
:
var a = Array( 4 ).fill( undefined );
a;
// [undefined,undefined,undefined,undefined]
fill(..)
opcionalmente aceita os parâmetros início e fim, que indicam o subconjunto do array para preencher, tal como:
var a = [ null, null, null, null ].fill( 42, 1, 3 );
a; // [null,42,42,null]
A forma mais comum de procurar por um valor em um array, geralmente tem sio o método indexOf(..)
, que retorna o índice se o valor for encontrado, ou -1
se não for encontrado:
var a = [1,2,3,4,5];
(a.indexOf( 3 ) != -1); // true
(a.indexOf( 7 ) != -1); // false
(a.indexOf( "2" ) != -1); // false
A comparação indexOf
requer uma equivalência estrita com ===
, então a procura por "2"
falha ao encontrar o valor 2
e vice-versa. Não tem como sobrescrever o algoritmo de equivalência para indexOf(..)
. E também é infeliz/deselegante ter que fazer uma comparação manual com o valor -1
.
Dica: Veja o título Tipos e Gramática dessa série para uma técnica interessante (e controversamente confusa) para trabalhar com a feiura do -1
com o operador ~
.
Desde o ES5, a solução alternativa para ter controle da lógica de equivalência tem sido o método some(..)
. Ele funciona chamando uma função callback para cada elemento, até que um deles retorne um valor true
/verdadeiro, e então para. Por conta de você ter que definir uma função callback, você tem total controle de como a equivalência é feita:
var a = [1,2,3,4,5];
a.some( function matcher(v){
return v == "2";
} ); // true
a.some( function matcher(v){
return v == 7;
} ); // false
Mas o lado negativo dessa abordagem é que você só tem o true
/false
indicando se uma equivalência adequada foi encontrada, mas não qual o valor dela.
O find(..)
do ES6 aborda isso. Funciona basicamente da mesma maneira que o some(..)
, exceto que uma vez que o callback retorna true
/valor verdadeiro, o real valor do array é retornado:
var a = [1,2,3,4,5];
a.find( function matcher(v){
return v == "2";
} ); // 2
a.find( function matcher(v){
return v == 7; // undefined
});
Usar uma função customizada matcher(..)
também te deixa testar a equivalência contra valores complexos, como objetos:
var points = [
{ x: 10, y: 20 },
{ x: 20, y: 30 },
{ x: 30, y: 40 },
{ x: 40, y: 50 },
{ x: 50, y: 60 }
];
points.find( function matcher(point) {
return (
point.x % 3 == 0 &&
point.y % 4 == 0
);
} ); // { x: 30, y: 40 }
Nota: Assim como outros métodos de arrays que aceitam callbacks, find(..)
aceita um argumento opcional que, se definido, vai especificar o this
para o callback passado no primeiro argumento. Caso contrário, this
será indefinido.
Enquanto a seção anterior ilustra como o some(..)
produz um resultado booleano para uma procura em um array, find(..)
produz o próprio valor combinadodo array que buscamos, mas ainda há a necessidade de saber o índice da posição desse valor.
indexOf(..)
faz isso, mas não há controle sobre a sua lógica de equivalência; ele sempre usa igualdade restrita ===
. Então o findIndex
do ES6 é a resposta:
var points = [
{ x: 10, y: 20 },
{ x: 20, y: 30 },
{ x: 30, y: 40 },
{ x: 40, y: 50 },
{ x: 50, y: 60 }
];
points.findIndex( function matcher(point) {
return (
point.x % 3 == 0 &&
point.y % 4 == 0
);
} ); // 2
points.findIndex( function matcher(point) {
return (
point.x % 6 == 0 &&
point.y % 7 == 0
);
} ); // -1
Não use o findIndex(..) != -1
(do jeito que é feito com indexOf(..)
) para pegar um valor booleano da busca, porque some(..)
já produz o valor true
/false
que você quer. E não faça a[ a.findIndex(..) ]
para pegar o valor combinado, porque é o que o find(..)
faz. E finalmente, use indexOf(..)
se você precisa do índice de uma igualdade restrita, ou findIndex(..)
se você precisa do índice de uma equivalência mais customizada.
Nota: Assim como outros métodos de arrays que aceitam callbacks, find(..)
aceita um argumento opcional que, se definido, vai especificar o this
para o callback passado no primeiro argumento. Caso contrário, this
será indefinido.
No Capítulo 3, nós ilustramos como estruturas de dados podem prover uma enumeração modelada item-por-item dos seus valores, via um iterador. Nós então expusemos essa abordagem no Capítulo 5, quando exploramos como as novas collections do ES6 (Map, Set, etc.) provêem vários métodos para produzir diferentes tipos de iterações.
Por conta de isso não ser novo no ES6, Array
pode não ser pensado tradicionalmente como uma "coleção", mas é se pensarmos que ele fornece os mesmos métodos para iterar: entries()
, values()
, e keys()
. Considere:
var a = [1,2,3];
[...a.values()]; // [1,2,3]
[...a.keys()]; // [0,1,2]
[...a.entries()]; // [ [0,1], [1,2], [2,3] ]
[...a[Symbol.iterator]()]; // [1,2,3]
Assim como com Set
, o iterador padrão Array
do array retorna o mesmo que values()
.
Em "Evitando Espaços Vazios" mais acima nesse capítulo, nós ilustramos como Array.from(..)
trata espaçoes vazios em um array somente colocando undefined
. Isso é na verdade porque, por baixo dos panos, os iteradores de array se comportam desse jeito:
var a = [];
a.length = 3;
a[1] = 2;
[...a.values()]; // [undefined,2,undefined]
[...a.keys()]; // [0,1,2]
[...a.entries()]; // [ [0,undefined], [1,2], [2,undefined] ]
Alguns outros helpers estáticos foram adicionados ao Object
. Tradicionalmente, funções desse tipo têm sido focadas nos comportamentos/capacidades dos valores do objeto.
Contudo, iniciando com ES6, funções estáticas de Object
vão servir também para o propósito geral de APIs globais de qualquer tipo que ainda não pertença naturalmente a loutro local (como Array.from(..)
).
A função estática Object.is(..)
faz comparação de valores de uma maneira ainda mais estilosamente estrita do que a comparação com ===
.
Object.is(..)
invoca o algoritmo subjacente SameValue
(ES6 spec, seção 7.2.9). O algoritmo SameValue
é basicamente o mesmo que ===
Algoritmo de Comparação de Igualidade Estrita (ES6 spec, seção 7.2.13), com duas exceções importantes.
Considere:
var x = NaN, y = 0, z = -0;
x === x; // false
y === z; // true
Object.is( x, x ); // true
Object.is( y, z ); // false
Você deveria continuar usando ===
para comparações de igualidade estritas; Object.is(..)
não deveria ser pensado como um substituto para o operador. Entretanto, em casos onde você está tentando estritamente identificar um NaN
ou o valor -0
, Object.is(..)
é agora a opção preferida.
Nota: ES6 também adiciona um utilitário Number.isNaN(..)
(discutido mais adiante nesse capítulo) que pode ser levemente mais conveniente; você pode preferir Number.isNaN(x)
ao invés de Object.is(x,NaN)
. Você pode precisamente testar para -0
com um desajeitado x == 0 && 1 / x === -Infinity
, mas nesse caso Object.is(x,-0)
é muito melhor.
A seção "Símbolos" no Capítulo 2 discute o novo tipo de valor primitivo Symbols em ES6.
Símbolos provavelmente vão ser os mais usados como propriedades especiais (meta) em objetos. Então o utilitário Object.getOwnPropertySymbols(..)
foi introduzido, que recupera apenas as propriedades símbolo diretamente de um objeto:
var o = {
foo: 42,
[ Symbol( "bar" ) ]: "hello world",
baz: true
};
Object.getOwnPropertySymbols( o ); // [ Symbol(bar) ]
Também no Capítulo 2, nós mencionamos o utilitário Object.setPrototypeOf(..)
, que (sem surpresa) seta o [[Prototype]]
de um objeto para fins de delegação de comportamento (veja o título this & Object Prototypes dessa série). Considere:
var o1 = {
foo() { console.log( "foo" ); }
};
var o2 = {
// .. o2's definition ..
};
Object.setPrototypeOf( o2, o1 );
// delegates to `o1.foo()`
o2.foo(); // foo
Alternativamente:
var o1 = {
foo() { console.log( "foo" ); }
};
var o2 = Object.setPrototypeOf( {
// .. o2's definition ..
}, o1 );
// delegates to `o1.foo()`
o2.foo(); // foo
Em ambos os trechos anteriores, a relação entre o2
e o1
aparece no fim da definição do o2
. Mais comumente, a relação entre um o2
e o1
é especificada no topo da definição do o2
, assim como com classes, e também com __proto__
em objetos literais (veja "Setting [[Prototype]]
no Capítulo 2").
Atenção: Configurar um [[Prototype]]
logo após a criação do objeto é razoável, como mostrado. Mas mudá-lo muito depois de criá-lo não é uma boa ideia e geralmente acaba mais em confusão do que clareza.
Muitas bibliotecas/frameworks JavaScript provêem utilitários para copiar/misturar propriedades de um objeto ao outro (por exemplo, extend(..)
do jQuery). Tem diferenças de nuances entre esses diferentes utilitários, como se a propriedade com valor undefined
é ignorada ou não.
ES6 adiciona Object.assign(..)
, que é uma versão simplificada desses algoritmos. O primeiro argumento é o alvo, e quaisquer outros argumentos passados são origens (sources), que vão ser processadas em uma ordem listada. Para cada origem, seu enumerável e suas próprias chaves (exemplo, não "herdadas"), incluindo símbolos, são copiadas como se fosse uma atribuição com =
. Object.assign(..)
retorna o objeto alvo.
Considere essa configuração de objeto:
var target = {},
o1 = { a: 1 }, o2 = { b: 2 },
o3 = { c: 3 }, o4 = { d: 4 };
// setup read-only property
Object.defineProperty( o3, "e", {
value: 5,
enumerable: true,
writable: false,
configurable: false
} );
// setup non-enumerable property
Object.defineProperty( o3, "f", {
value: 6,
enumerable: false
} );
o3[ Symbol( "g" ) ] = 7;
// setup non-enumerable symbol
Object.defineProperty( o3, Symbol( "h" ), {
value: 8,
enumerable: false
} );
Object.setPrototypeOf( o3, o4 );
Somente as propriedades a
, b
, c
, e
, e o Symbol("g")
vão ser copiados ao target
:
Object.assign( target, o1, o2, o3 );
target.a; // 1
target.b; // 2
target.c; // 3
Object.getOwnPropertyDescriptor( target, "e" );
// { value: 5, writable: true, enumerable: true,
// configurable: true }
Object.getOwnPropertySymbols( target );
// [Symbol("g")]
As propriedades d
, f
, e Symbol("h")
são omitidas da cópia; Propriedades não-enumeráveis e não-próprias são todas excluidas da atribuição. Também, e
é copiada como uma atribuição normal de propriedade, não duplicada como uma propriedade read-only.
Em uma seção anterior, nós mostramos o uso de setPrototypeOf(..)
para configurar uma relação [[Prototype]]
entre os objetos o2
e o1
. Tem outra forma que se aproveita do Object.assign(..)
:
var o1 = {
foo() { console.log( "foo" ); }
};
var o2 = Object.assign(
Object.create( o1 ),
{
// .. o2's definition ..
}
);
// delegates to `o1.foo()`
o2.foo(); // foo
Nota: Object.create(..)
é o utilitário padrão do ES5 que cria um objeto vazio que é [[Prototype]]
-linked. Veja o título this & Object Prototypes dessa série para mais informação.
ES6 adiciona diversos utilitários matemáticos que preenchem buracos ou ajudam com operações comuns. Todos podem ser manualmente calculados, mas a maioria está agora definida nativamente, então em alguns casos o motor do JS pode otimizar a performance dos cálculos e ser mais performático com mais precisão de números decimais do que a solução manual.
É provavel que asm.js/código JS transpilado (veja o título Async & Performance dessa série) é o consumidor mais provável de muitos desses utilitários, ao invés de desenvolvedores diretos.
Trigonometria:
cosh(..)
- Coseno hiperbólicoacosh(..)
- Arco-cosseno hiperbólicosinh(..)
- Seno hiperbólicoasinh(..)
- Arco-seno hiperbólicotanh(..)
- Tangente hiperbólicaatanh(..)
- Arco-tangente hiperbólicahypot(..)
- A raíz quadrada da soma dos quadrados (ou seja, o generalizado Teorema de Pitágoras)
Aritmética:
cbrt(..)
- Raíz cúbicaclz32(..)
- Conta os zeros à esquerda em uma representação binária de 32-bitexpm1(..)
- O mesmo queexp(x) - 1
log2(..)
- Logarítimo binário (log de base 2)log10(..)
- Log de base 10log1p(..)
- O mesmo quelog(x + 1)
imul(..)
- Multiplicação de dois números inteiros de 32-bit
Meta:
sign(..)
- Retorna o sinal de um númerotrunc(..)
- Retorna apenas a parte inteira de um númerofround(..)
- Arrendonda para o valor float mais próximo de 32-bit (precisão única)
Importante, para seu programa funcionar corretamente, ele deve lidar com precisão de números. ES6 adiciona algumas propriedades adicionais e funções para ajudar com operações numéricas comuns.
Duas adições ao Number
são apenas referências aos globais preexistentes: Number.parseInt(..)
e Number.parseFloat(..)
.
ES6 adiciona alguns constante númericos úteis como propriedades estáticas:
Number.EPSILON
- O valor mínimo entro dois números quaisquer:2^-52
(veja o Capítulo 2 do título Types & Grammar dessa série para usar esse valor como uma tolerância para precisão em aritmética de pontos-flutuantes)Number.MAX_SAFE_INTEGER
- O maior inteiro que pode ser representado sem ambiguidade com segurança em um valor numérico de JS:2^53 -1
Number.MIN_SAFE_INTEGER
- O menor inteiro que pode ser representado sem ambiguidade com segurança em um valor numérico de JS:-(2^53 - 1)
ou(-2)^53 + 1
.
Nota: Veja o capítulo 2 do título Types & Grammar dessa série para mais informações a respeito de inteiros "seguros".
O utilitário global padrão isNaN(..)
tem sido quebrado desde o início, onde ele retornava true
para coisas que não são números, não apenas o valor NaN
, porque ele força o argumento a um tipo de número (o que pode falsamente resultar em um NaN). ES6 adiciona um utilitário reparado, que funciona como deveria:
var a = NaN, b = "NaN", c = 42;
isNaN( a ); // true
isNaN( b ); // true -- oops!
isNaN( c ); // false
Number.isNaN( a ); // true
Number.isNaN( b ); // false -- fixed!
Number.isNaN( c ); // false
Há uma tentação de olhar para uma função nomeada de isFinite(..)
e assumir que é simplesmente "não infinito". Porém, isso não está exatamente correto. Há uma pequena divergência nesse novo utilitário do ES6. Considere:
var a = NaN, b = Infinity, c = 42;
Number.isFinite( a ); // false
Number.isFinite( b ); // false
Number.isFinite( c ); // true
O padrão global isFinite(..)
força esse argumento, mas Number.isFinite(..)
omite esse comportamento forçado:
var a = "42";
isFinite( a ); // true
Number.isFinite( a ); // false
Você pode ainda preferir a coerção, e nesse caso usar o isFinite(..)
global é uma boa escolha. Uma outra possibilidade, e talvez mais sensível, é você usar Number.isFinite(+x)
, que explicitamente força x
a um número antes de passá-lo (veja o Capítulo 4 do título Types & Grammar dessa série).
Valores numéricos de JavaScript são sempre ponteiros flutuantes (IEEE-754). Então a noção de determinar se um número é um "inteiro" não é checando seu tipo, porque JS não faz nenhuma distinção. JavaScript number values are always floating point (IEEE-754). So the notion of determining if a number is an "integer" is not about checking its type, because JS makes no such distinction.
Ao invés disso, você precisa checar se há algum decimal diferente de zero que é parte do valor. A forma mais fácil de fazer isso tem sido comumente assim:
x === Math.floor( x );
ES6 adiciona o utilitário Number.isInteger(..)
que potencialmente pode determinar sua qualidade de maneira levemente mais eficiente:
Number.isInteger( 4 ); // true
Number.isInteger( 4.2 ); // false
Nota: Em JavaScript, não tem nenhuma diferença entre 4
, 4.
, 4.0
ou 4.0000
. Todos esses seriam considerados como um "inteiro", e, assim, retornariam true
em Number.isInteger
.
Alem disso, Number.isInteger(..)
filtra alguns valores que claramente não são inteiros e x === Math.floor(x)
potencialmente iria se confundir:
Number.isInteger( NaN ); // false
Number.isInteger( Infinity ); // false
Trabalhar com "inteiros" às vezes é importante, já que pode simplificar alguns tipos de algoritmos. O Código JS por si só não vai rodar mais rápido apenas por filtrar somente números inteiros, mas há algumas técnicas de otimização que a engine pode fazer (exemplo, asm.js) onde somente inteiros são usados.
Por conta da forma como Number.isInteger(..)
lida com valores NaN
e Infinity
, definir um utilitário isFloat
não seria tão simples como !Number.isInteger(..)
. Ia ser algo como:
function isFloat(x) {
return Number.isFinite( x ) && !Number.isInteger( x );
}
isFloat( 4.2 ); // true
isFloat( 4 ); // false
isFloat( NaN ); // false
isFloat( Infinity ); // false
Nota: Isso pode ser estranho, mas Infinito nunca deveria ser considerado nem inteiro nem float.
ES6 também tem um utilitário Number.isSafeInteger(..)
, que checa para ter certeza que o valor é inteiro e está dentro do intervalo de Number.MIN_SAFE_INTEGER
-Number.MAX_SAFE_INTEGER
(inclusivo).
var x = Math.pow( 2, 53 ),
y = Math.pow( -2, 53 );
Number.isSafeInteger( x - 1 ); // true
Number.isSafeInteger( y + 1 ); // true
Number.isSafeInteger( x ); // false
Number.isSafeInteger( y ); // false
Strings já tinham alguns helpers antes do ES6, mas alguns mais foram adicionados à mistura.
"Unicode-Aware String Operations" no Capítulo 2 discute String.fromCodePoint(..)
, String#codePointAt(..)
, e String#normalize(..)
em detalhes. Eles foram adicionados para melhorar o suporte a Unicode em valores string de JS.
String.fromCodePoint( 0x1d49e ); // "𝒞"
"ab𝒞d".codePointAt( 2 ).toString( 16 ); // "1d49e"
O método prototipado de string normalize(..)
é usado para realizar normalizações Unicode que combinam caracteres com "combinação de marcas" adjacentes ou decompõem caractéres combinados.
Geralmente, a normalização não vai criar um efeito visível nos conteúdos da string, mas vai mudar o conteúdo da string, o que pode afetar como coisas como a propriedade length
é reportada, e também em como um acesso a um caracter pela posição se comporta:
var s1 = "e\u0301";
s1.length; // 2
var s2 = s1.normalize();
s2.length; // 1
s2 === "\xE9"; // true
normalize(..)
aceita um argumento opcional que especifica a forma normalizada de usar. Esse argumento deve ser um dos 4 valores a seguir: "NFC"
(padrão), "NFD"
, "NFKC"
, or "NFKD"
.
Nota: Formas de normalização e seus efeitos nas strings está bem além do escopo que nós estamos discutindo aqui. Veja "Unicode Normalization Forms" (http://www.unicode.org/reports/tr15/) para mais informações.
O utilitário String.raw(..)
é fornecido como uma função nativa para ser usado com template string literals (veja o Capítulo 2) para obter o valor crú da string sem o processamento de sequências de escape.
Essa função quase nunca será chamada manualmente, mas vai ser usada com tagged template literals:
var str = "bc";
String.raw`\ta${str}d\xE9`;
// "\tabcd\xE9", not " abcdé"
Na string resultante, \
e t
são caracteres crus separados, e não o caracter escapado \t
. A mesma coisa acontece com a sequência de escape Unicode.
Em linguagens como Python e Ruby, você pode repetir uma string assim:
"foo" * 3; // "foofoofoo"
Isso não funciona em JS, porque a multiplicação *
está definida apenas para números, e assim "foo"
é forçado para um número NaN
.
No entanto, ES6 define o método prototipado de string repeat(..)
para realizar a tarefa:
"foo".repeat( 3 ); // "foofoofoo"
Alem de String#indexOf(..)
e String#lastIndexOf(..)
de antes do ES6, três novos métodos para busca/inspeção foram adicionados: startsWith(..)
, endsWidth(..)
, e includes(..)
.
var palindrome = "step on no pets";
palindrome.startsWith( "step on" ); // true
palindrome.startsWith( "on", 5 ); // true
palindrome.endsWith( "no pets" ); // true
palindrome.endsWith( "no", 10 ); // true
palindrome.includes( "on" ); // true
palindrome.includes( "on", 6 ); // false
Para todos os métodos de busca/inspeção de string, se você busca por uma string vazia ""
, ela vai ser encontrada tanto no começo quanto no final da string.
Atenção: Esses métodos não vão aceitar uma expressão regular por padrão para a string de busca. Veja "Regular Expression Symbols" no Capítulo 7 para informação a respeito de desabilitar a checagem isRegExp
que é realizada nesse primeiro argumento.
ES6 adiciona muitos API de helpers extras nos vários objetos nativos:
Array
adiciona as funções estáticasof(..)
efrom(..)
, e também funções prototipadas comocopyWithin(..)
efill(..)
.Object
adiciona funções estáticas comois(..)
eassign(..)
.Math
adiciona funções estáticas comoacosh(..)
eclz32(..)
.Number
adiciona propriedades estáticas comoNumber.EPSILON
, e também funções estáticas comoNumber.isFinite(..)
.String
adiciona funções estáticas comoString.fromCodePoint(..)
eString.raw(..)
, e também funções de prototipagem comorepeat(..)
eincludes(..)
.
A maioria dessas adições podem ser polyfilled (veja ES6 Shim) e foram inspiradas por utilitários em bibliotecas/frameworks comuns de JS.