JavaScript é uma linguagem complexa. Se você, em qualquer nível, estiver envolvido no desenvolvimento de JavaScript, isso significa que é vital que você entenda os conceitos básicos dessa linguagem. O material, cuja tradução estamos publicando hoje, abrange 12 conceitos críticos de JavaScript. Obviamente, o desenvolvedor de JavaScript precisa saber muito mais, mas sem o que falaremos hoje, ele definitivamente não pode fazer.

1. Variáveis que armazenam valores e referências
Compreender como exatamente os valores variáveis são atribuídos no JavaScript é extremamente importante para quem deseja escrever o código de trabalho correto. O mal-entendido desse mecanismo leva a escrever programas nos quais os valores das variáveis podem mudar inadvertidamente.
JavaScript, se uma entidade tiver um dos tipos primitivos (em particular, os tipos
Boolean
,
null
,
undefined
,
String
e
Number
), sempre funcionará com o valor dessa entidade. Ou seja, o valor é gravado na variável correspondente. Se estamos falando de um objeto (por exemplo, os tipos
Object
,
Array
,
Function
), ao atribuí-lo a uma variável, uma referência a ele é gravada, o endereço no qual está localizado na memória.
Considere um exemplo. No seguinte trecho de código, uma string é gravada em
var1
. Depois disso, o valor de
var2
gravado na variável
var2
. Como a variável
var1
possui um tipo primitivo (
String
), uma cópia da string disponível em
var1
var2
gravada em
var1
. Isso nos permite considerar
var2
como uma variável completamente independente de
var1
, embora armazene o mesmo valor que
var1
. Escrever um novo valor em
var1
não afeta
var1
.
let var1 = 'My string'; let var2 = var1; var2 = 'My new string'; console.log(var1);
Agora considere um exemplo de trabalho com objetos.
let var1 = { name: 'Jim' } let var2 = var1; var2.name = 'John'; console.log(var1);
Como você pode ver, aqui estamos trabalhando com a variável
var2
, e o que acontece com ela é refletido na variável
var1
, pois eles armazenam uma referência ao mesmo objeto. É fácil imaginar o que isso pode levar ao código real se alguém decidir que as variáveis que armazenam objetos se comportam da mesma maneira que as variáveis que armazenam valores de tipos primitivos. Isso é especialmente desagradável, por exemplo, nos casos em que eles criam uma função projetada para trabalhar com o valor do objeto passado para ele, e essa função altera inadvertidamente esse valor.
2. Curto-circuito
O fechamento é um importante padrão de design em JavaScript que permite organizar o trabalho protegido com variáveis. No exemplo a seguir, a função
createGreeter()
retorna uma função anônima que tem acesso ao argumento original fornecido com o argumento de
greeting
que contém a cadeia
Hello
. Uma referência a essa função anônima é gravada na variável
sayHello
. Depois disso, não importa quantas vezes chamamos a função
sayHello()
, ela sempre terá acesso ao valor da
greeting
. Nesse caso, o acesso à
greeting
será apenas uma função anônima, cujo link é gravado no
sayHello
.
function createGreeter(greeting) { return function(name) { console.log(greeting + ', ' + name); } } const sayHello = createGreeter('Hello'); sayHello('Joe');
Este foi um exemplo muito simples. Se olharmos para algo mais próximo do mundo real, podemos imaginar, por exemplo, uma função para conectar-se a uma determinada API (vamos chamá-la
apiConnect()
), que, quando é chamada pela primeira vez, recebe uma chave de acesso à API. Essa função, por sua vez, retorna um objeto que contém vários métodos que usam a chave de acesso da API passada para
apiConnect()
. Nesse caso, a chave é armazenada no fechamento e, quando você chama esses métodos, não é mais necessário mencioná-la.
function apiConnect(apiKey) { function get(route) { return fetch(`${route}?key=${apiKey}`); } function post(route, params) { return fetch(route, { method: 'POST', body: JSON.stringify(params), headers: { 'Authorization': `Bearer ${apiKey}` } }) } return { get, post } } const api = apiConnect('my-secret-key');
3. Atribuição destrutiva
Se você ainda não usou a atribuição destrutiva no JavaScript, é hora de corrigi-la. A atribuição destrutiva é uma maneira comum de recuperar propriedades de objetos usando uma construção pura de linguagem sintática.
const obj = { name: 'Joe', food: 'cake' } const { name, food } = obj; console.log(name, food);
Se você precisar atribuir os nomes de propriedades extraídos que são diferentes daqueles que eles possuem no objeto, você pode fazer isso:
const obj = { name: 'Joe', food: 'cake' } const { name: myName, food: myFood } = obj; console.log(myName, myFood);
No exemplo a seguir, a desestruturação é usada para passar com precisão os valores armazenados nas propriedades do objeto
person
para a função
introduce()
. Este é um exemplo de como essa construção é usada ao declarar uma função para recuperar dados de um objeto com parâmetros passados para ele. A propósito, se você está familiarizado com o React, provavelmente já viu isso.
const person = { name: 'Eddie', age: 24 } function introduce({ name, age }) { console.log(`I'm ${name} and I'm ${age} years old!`); } console.log(introduce(person));
4. O operador de spread
O operador de propagação é uma construção bastante simples que pode parecer incompreensível para uma pessoa despreparada. O exemplo a seguir possui uma matriz numérica, o valor máximo armazenado no qual precisamos encontrar. Queremos usar o método
Math.max()
para isso, mas ele não sabe como trabalhar com matrizes. Ele, como argumento, assume valores numéricos independentes. Para extrair seus elementos da matriz, usamos o operador spread, que se parece com três pontos.
const arr = [4, 6, -1, 3, 10, 4]; const max = Math.max(...arr); console.log(max);
5. A declaração restante
O operador resto permite converter qualquer número de argumentos passados para uma função em uma matriz.
function myFunc(...args) { console.log(args[0] + args[1]); } myFunc(1, 2, 3, 4);
6. Métodos de matriz
Os métodos de matriz geralmente fornecem ao desenvolvedor ferramentas convenientes para resolver lindamente uma variedade de tarefas de conversão de dados. Às vezes, respondo perguntas no StackOverflow. Entre eles, geralmente existem aqueles dedicados a algo como essas ou outras maneiras de trabalhar com matrizes de objetos. É nessas situações que os métodos de matriz são especialmente úteis.
Aqui vamos considerar vários desses métodos, unidos pelo princípio de sua semelhança entre si. Note-se que aqui não vou falar sobre todos os métodos de matrizes. Você pode encontrar sua lista completa no
MDN (a propósito, esta é minha referência JavaScript favorita).
MethodsMapa (), filtro () e métodos de redução ()
Os métodos de matriz
map()
,
filter()
e
reduce()
permitem transformar matrizes ou reduzir matrizes em um único valor (que pode ser um objeto).
O método
map()
retorna uma nova matriz que contém os valores transformados da matriz processada. Como exatamente eles serão transformados é especificado na função passada para este método.
const arr = [1, 2, 3, 4, 5, 6]; const mapped = arr.map(el => el + 20); console.log(mapped);
O método
filter()
retorna uma matriz de elementos, verificando os valores dos quais a função passada para esse método retornou
true
.
const arr = [1, 2, 3, 4, 5, 6]; const filtered = arr.filter(el => el === 2 || el === 4); console.log(filtered);
O método
reduce()
retorna um determinado valor, que é o resultado do processamento de todos os elementos da matriz.
const arr = [1, 2, 3, 4, 5, 6]; const reduced = arr.reduce((total, current) => total + current); console.log(reduced);
Métodos find (), findIndex () e indexOf ()
Os métodos de matriz
find()
,
findIndex()
e
indexOf()
fáceis de confundir um com o outro. A seguir, são apresentadas explicações para ajudar você a entender seus recursos.
O método
find()
retorna o primeiro elemento da matriz que corresponde aos critérios especificados. Este método, encontrando o primeiro elemento adequado, não continua a pesquisa na matriz.
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const found = arr.find(el => el > 5); console.log(found);
Observe que em nosso exemplo, os critérios fornecidos correspondem a todos os elementos da matriz que seguem o que contém o número 5, mas apenas o primeiro elemento adequado é retornado. Esse método é muito útil em situações nas quais, usando os loops for para enumerar e analisar matrizes, esses loops são interrompidos quando o elemento desejado é encontrado na matriz, usando a instrução
break
.
O método
findIndex()
é muito semelhante ao
find()
, mas, em vez de retornar o primeiro elemento adequado na matriz, ele retorna o índice desse elemento. Para entender melhor esse método, dê uma olhada no exemplo a seguir, que usa uma matriz de valores de string.
const arr = ['Nick', 'Frank', 'Joe', 'Frank']; const foundIndex = arr.findIndex(el => el === 'Frank'); console.log(foundIndex);
O método
indexOf()
é muito semelhante ao método
findIndex()
, mas toma como argumento não uma função, mas um valor normal. Pode ser usado se uma lógica complexa não for necessária ao procurar o elemento de matriz desejado.
const arr = ['Nick', 'Frank', 'Joe', 'Frank']; const foundIndex = arr.indexOf('Frank'); console.log(foundIndex);
▍ Métodos Push (), pop (), shift () e unshift ()
Os
unshift()
push()
,
pop()
,
shift()
e
unshift()
são usados para adicionar novos elementos às matrizes e extrair deles os elementos já existentes nas matrizes. Nesse caso, o trabalho é realizado com elementos localizados no início ou no final da matriz.
O método
push()
permite adicionar elementos ao final de uma matriz. Ele modifica a matriz e, após a conclusão, retorna o elemento adicionado à matriz.
let arr = [1, 2, 3, 4]; const pushed = arr.push(5); console.log(arr);
O método
pop()
remove o último elemento da matriz. Ele modifica a matriz e retorna o elemento removido dela.
let arr = [1, 2, 3, 4]; const popped = arr.pop(); console.log(arr);
O método
shift()
remove o primeiro elemento da matriz e o retorna. Ele também modifica a matriz para a qual é chamada.
let arr = [1, 2, 3, 4]; const shifted = arr.shift(); console.log(arr);
O método
unshift()
adiciona um ou mais elementos ao início de uma matriz. Ele, novamente, modifica a matriz. Ao mesmo tempo, diferentemente dos outros três métodos discutidos aqui, ele retorna o novo comprimento da matriz.
let arr = [1, 2, 3, 4]; const unshifted = arr.unshift(5, 6, 7); console.log(arr);
Métodos Slice () e splice ()
Esses métodos são usados para modificar a matriz ou retornar uma parte da matriz.
O método
splice()
altera o conteúdo de uma matriz excluindo elementos existentes ou substituindo-os por outros elementos. Ele é capaz de adicionar novos elementos à matriz. Este método modifica a matriz.
O exemplo a seguir, se você o descrever em linguagem comum, terá a seguinte aparência: na posição
1
matriz, remova
0
elementos e adicione um elemento contendo
b
.
let arr = ['a', 'c', 'd', 'e']; arr.splice(1, 0, 'b')
O método
slice()
retorna uma cópia superficial da matriz que contém seus elementos, iniciando na posição inicial especificada e terminando na posição anterior à posição final especificada. Se, ao chamá-lo, apenas a posição inicial for especificada, ele retornará toda a matriz, iniciando nessa posição. Este método não modifica a matriz. Ele retorna apenas a parte dessa matriz descrita quando foi chamada.
let arr = ['a', 'b', 'c', 'd', 'e']; const sliced = arr.slice(2, 4); console.log(sliced);
Sort Método sort ()
O método
sort()
classifica a matriz de acordo com a condição especificada pela função passada para ela. Essa função utiliza dois elementos da matriz (por exemplo, eles podem ser representados como parâmetros
b
) e, comparando-os, retorna, se os elementos não precisarem ser trocados, 0 se for necessário colocar um índice menor que
b
é um número negativo e se
b
precisar ser colocado em um índice mais baixo que
a
é um número positivo.
let arr = [1, 7, 3, -1, 5, 7, 2]; const sorter = (firstEl, secondEl) => firstEl - secondEl; arr.sort(sorter); console.log(arr);
Se você não consegue se lembrar desses métodos pela primeira vez, é bom lembrar deles. O mais importante é que agora você sabe o que os métodos de matriz padrão podem fazer. Portanto, se você não conseguir se lembrar imediatamente dos recursos de um método específico, o que você sabe sobre ele permitirá que você encontre rapidamente o que precisa na documentação.
7. Geradores
Os geradores JavaScript são declarados usando um caractere asterisco. Eles permitem que você especifique qual valor será retornado na próxima vez que o
next()
método
next()
for chamado. Os geradores podem ser projetados para retornar um número limitado de valores. Se esse gerador retornou todos esses valores, a próxima chamada para
next()
retornará
undefined
. Você também pode criar geradores projetados para retornar um número ilimitado de valores usando ciclos.
Aqui está um gerador projetado para retornar um número limitado de valores:
function* greeter() { yield 'Hi'; yield 'How are you?'; yield 'Bye'; } const greet = greeter(); console.log(greet.next().value);
E aqui está um gerador projetado para retornar um número infinito de valores através de um loop.
function* idCreator() { let i = 0; while (true) yield i++; } const ids = idCreator(); console.log(ids.next().value);
8. Operadores para verificar a igualdade (==) e a estrita igualdade (===) de valores
É extremamente importante para qualquer desenvolvedor JS entender a diferença entre operadores de igualdade (
==
) e igualdade estrita (
===
). O fato é que o operador
==
, antes de comparar os valores, realiza a conversão de seus tipos (o que pode levar a estranhas, à primeira vista, consequências), e o operador
===
não realiza a conversão de tipos.
console.log(0 == '0');
9. Comparação de objetos
Ocasionalmente, tenho que ver como os novatos na programação JS cometem o mesmo erro. Eles tentam comparar diretamente objetos. As variáveis nas quais os objetos são "armazenados" contêm referências a eles, e não esses objetos em si.
Assim, por exemplo, no exemplo a seguir, os objetos têm a mesma aparência, mas quando comparados diretamente, somos informados de que os objetos são diferentes, pois cada uma das variáveis contém um link para seu próprio objeto e esses links não são iguais entre si.
const joe1 = { name: 'Joe' }; const joe2 = { name: 'Joe' }; console.log(joe1 === joe2);
Além disso, no exemplo a seguir, verifica-se que
joe1
é igual a
joe2
pois ambas as variáveis armazenam uma referência ao mesmo objeto.
const joe1 = { name: 'Joe' }; const joe2 = joe1; console.log(joe1 === joe2);
Um dos métodos de comparação de objetos reais é a conversão preliminar para o formato de string JSON. É verdade que essa abordagem tem um problema: a representação de string obtida do objeto não garante uma certa ordem de suas propriedades. Uma maneira mais confiável de comparar objetos é usar uma biblioteca especial que contém ferramentas para comparação profunda de objetos (por exemplo, este é o método
isEqual () da biblioteca
lodash ).
Para entender melhor os meandros da comparação de objetos e entender as possíveis consequências de escrever links para os mesmos objetos em diferentes variáveis, dê uma olhada no primeiro conceito de JS discutido neste artigo.
10. Funções de retorno de chamada
As funções de retorno de chamada são um conceito JavaScript bastante simples com o qual os iniciantes às vezes têm dificuldade. Considere o seguinte exemplo. Aqui, a função
console.log
(assim - sem parênteses) é passada para
myFunc()
como uma função de retorno de chamada. Essa função define um cronômetro, após o qual
console.log()
é chamado e a sequência passada para
myFunc()
é exibida no console.
function myFunc(text, callback) { setTimeout(function() { callback(text); }, 2000); } myFunc('Hello world!', console.log);
11. Promessas
Depois de dominar as funções de retorno de chamada e começar a usá-las em qualquer lugar, você se encontrará no chamado "inferno de retorno de chamada". Se você realmente está lá - dê uma olhada nas promessas. O código assíncrono pode ser envolvido em uma promessa e, após sua execução bem-sucedida, informar o sistema sobre a resolução bem-sucedida da promessa chamando o método apropriado e, se algo der errado - chame o método indicando isso e rejeite a promessa. Para processar os resultados retornados pela promessa, use o método
then()
e, para manipulação de erros, use o método
catch()
.
const myPromise = new Promise(function(res, rej) { setTimeout(function(){ if (Math.random() < 0.9) { return res('Hooray!'); } return rej('Oh no!'); }, 1000); }); myPromise .then(function(data) { console.log('Success: ' + data); }) .catch(function(err) { console.log('Error: ' + err); });
12. construção assíncrona / aguardada
Depois de trabalhar com as promessas, é possível que você queira algo mais. Por exemplo, domine a construção assíncrona / aguardada. É açúcar sintático para promessas. No exemplo a seguir, criamos, usando a
async
, uma função assíncrona, e nela, usando a palavra-chave wait, organizamos a espera pela
greeter
greeter.
const greeter = new Promise((res, rej) => { setTimeout(() => res('Hello world!'), 2000); }) async function myFunc() { const greeting = await greeter; console.log(greeting); } myFunc();
Sumário
Se o que falamos aqui anteriormente não era familiar para você, provavelmente você, pelo menos um pouco, cresceu acima de si mesmo lendo este artigo. Se você não encontrou nada de novo aqui, gostaria de esperar que este material lhe desse a oportunidade de praticar e fortalecer seu conhecimento de JavaScript.
Caros leitores! Quais outros conceitos JavaScript você adicionaria a este artigo?
