A JavaScript evoluiu de um modo inimaginável nos últimos cinco anos. Desde então, há vários modos de escrever mais código em menos linhas e de garantir mais legibilidade ao mesmo tempo.
Entretanto, quando se está acostumado a escrever "do jeito antigo", pode ser difícil trocar seus hábitos. Por isso, neste artigo você aprenderá (ou relembrará) quatro funcionalidades que, hoje, são essenciais ao conhecimento de qualquer desenvolvedor que lida com a linguagem.
1) let
e const
Não são raros os casos em que utilizamos, ao longo de nosso código, diversas variáveis que possuem nome semelhante ou mesmo igual para a realização de funcionalidades totalmente diferentes. Isso pode causar uma imensa confusão conforme o código cresce.
Nos é muito habitual, por exemplo, o uso de uma variável com o nome i
para contar iterações em loops. Se a mesma variável estiver sendo utilizada em dois loops diferentes, muitos bugs podem vir à tona! E, bem, este é só um exemplo.
Por isso let
e const
facilitam tanto o nosso trabalho quando falamos de variáveis de escopo.
A diferença delas para a palavra-chave var
começa pelo fato de que só existem dentro do escopo onde foram criadas. Considere o seguinte exemplo:
if (condition) {
let someScopeVariable = "Hey, I'm a scoped variable!";
console.log(someScopeVariable); // "Hey, I'm a scoped variable!"
}
console.log(someScopeVariable); // ReferenceError
A variável someScopeVariable
só existe dentro daquele bloco if
. Por isso, obtemos um erro tentando acessá-la de fora.
Agora, vamos alterar a ordem do código que está dentro da condicional:
if (condition) {
someScopeVariable = "Hey, I'm a scoped variable!"; // ReferenceError
console.log(someScopeVariable);
let someScopeVariable;
}
As funções e as variáveis criadas com a palavra-chave var
são hoisted. Ou seja, quando o código é rodado, suas declarações vão para o topo. Portanto, não há problema em chamá-las antes de sua declaração. Mas o mesmo não acontece com let
e const
!
Ok, mas quando devo utilizar a palavra-chave const
? O exemplo a seguir ajuda a esclarecer seu uso:
const age = 19;
age = 18; // TypeError
Utilizamos const
quando queremos declarar constantes, ou seja, quando nosso objetivo é guardar valores que jamais serão alterados. Daí que a prática de declarar funções com a palavra-chave const
tenha se tornado tão popular. Isto garante que sempre que chamemos uma função ela faça realmente aquilo que estamos esperando que faça. Antigamente, por exemplo, você poderia fazer isso:
function test() {
return "Testing my code";
}
// algumas linhas de código depois...
function test() {
return {
message: "Testing my code"
};
};
Bem, imagine a confusão que um código assim poderia causar! Hoje, quando criamos uma função constante, garantimos que ela sempre será a mesma.
2) Arrow functions
Como o nome deixa transparecer, as arrow functions são funções representadas pelo uso de uma flecha (->
).
Veja o código abaixo:
function greeting(name) {
console.log("Welcome, " + name);
}
Certo, não há nada novo aqui. Diversas vezes você já escreveu funções como essa. Analisemos, então, como a mesma funcionalidade poderia ser escrita com o uso de uma arrow function:
const greeting = name => console.log("Welcome, " + name);
A sintaxe é muito simples: nome da função = parâmetros => retorno.
No exemplo acima, o retorno pôde ser escrito na mesma linha. Caso mais tarefas precisem ser executadas até que um valor seja retornado, você pode criar um escopo para a função utilizando chaves ({}
):
let calledNames = [];
const greeting = name => {
if (!calledNames.includes(name)) {
console.log("Welcome, " + name);
calledNames.push(name);
}
};
Conforme seu algoritmo ganha forma e você começa a lidar com várias funções de callback, percebe que o uso das arrow functions torna o código bem menos verboso.
3) Classes
Classes são são exatamente um recurso novo na JavaScript. Atualmente, porém, sua sintaxe é bem mais amigável e convidativa. Veja a diferença de sua definição antes e depois da ES6:
Antes
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log("Hello world! My name is " + this.name);
}
Depois
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log("Hello world! My name is " + this.name);
}
}
A principal diferença está na questão de escrever todo o código referente a Person
dentro de um só escopo. Isto ajuda você a organizar melhor seu código e a ter maior facilidade em realizar a manutenção dele.
Hoje em dia, também, o controle sobre a acessibilidade das propriedades e métodos de uma classe é bem mais explícito e, portanto, mais legível.
Se você ainda tem dificuldades em compreender a diferença entre campos públicos, estáticos e privados, guarde bem estes pontos:
- public: campos que podem ser acessados de qualquer lugar - de dentro e de fora da classe - a partir de uma instância.
- static: são como os públicos, com a diferença de que não precisam ser lidos a partir de uma instância e não possuem acesso à palavra-chave
this
, haja vista quethis
refere-se à instância do objeto criado pela classe, não a classe em si. - private: campos que só podem ser acessados de dentro da classe.
Um último exemplo, para fixar bem essas informações:
class Person {
constructor(name, creditCardNumber) {
this.name = name; // public
this.#creditCardNumber = creditCardNumber; // private
}
sayName() {
console.log("My name is " + this.name);
}
static sayHello() {
console.log("Hello world!");
}
}
let person = new Person("Lipe", 1111111111111111);
person.name = "Luiz"; // "Luiz"
person.#creditCardNumber; // erro: este atributo é privado
person.sayName(); // imprime "My name is Luiz"
Person.sayHello(); // imprime "Hello world!"
Note que, para criar propriedades e métodos privados, basta utilizar #
antes de seus nomes. Já para métodos estáticos, usamos a palavra-chave static
antes da declaração.
Para um breve tour sobre orientação a objetos na JavaScript, veja meu primeiro post neste blog.
4) Optional chaining (?
) e nullish coalescing (??
)
Você já deve estar familiarizado(a) a ver códigos como este:
let friendsList = [];
if (user && user.friends) {
friendsList = user.friends;
}
A nossa condição é a seguinte: se a minha variável user
foi definida e não é nula e, se possui mesmo a propriedade friends
, poderei utilizar esta.
Com os operadores ?
e ??
, pulamos esse tipo de verificação. Veja como nosso código fica:
let friendsList = user?.friends ?? [];
O que acontece é simples: se user
existe e a propriedade friends
também, a variável friendsList
copiará o valor desta. Se não, será declarada com um array
vazio.
O operador ??
é muito semelhante ao ||
. Mas ele vem para evitar algumas confusões. Analise a seguinte situação:
let userVolume = 0;
let currentVolume = userVolume || 1;
Aqui, userVolume
existe e seu valor foi declarado, portanto não é nulo. Mesmo assim, currentVolume
fica com valor 1
, pois o operador ||
entende que 0
representa uma falsy
.
Se, por outro lado, utilizarmos ??
no lugar, currentVolume
será igual a 0
.
Conclusão
Conforme as linguagens e frameworks que utilizamos ganham novas funcionalidades e se tornam mais robustos, podemos ficar com medo de ficar para trás ou mesmo de utilizar os recursos novos.
A maioria deles, porém, está aí para tornar sua vida mais fácil e a programação mais segura - tanto para você, quanto para seus clientes. A JavaScript está em constante evolução. Ficar em dia com ela é essencial para que você evolua suas habilidades também. 😁