10 conceitos para melhorar seu domínio de JavaScript

Jan 13 2023
O JavaScript é indiscutivelmente uma das linguagens de programação mais populares do mundo e é usado para criar sites interativos e aplicativos da web. Para desenvolvedores, aprender JavaScript pode levar a uma riqueza de oportunidades devido à sua força e versatilidade.
Foto de Gabriel Heinzer no Unsplash

O JavaScript é indiscutivelmente uma das linguagens de programação mais populares do mundo e é usado para criar sites interativos e aplicativos da web. Para desenvolvedores, aprender JavaScript pode levar a uma riqueza de oportunidades devido à sua força e versatilidade. Neste artigo, exploraremos 10 conceitos-chave que podem ajudá-lo a melhorar seu domínio do JavaScript e levar suas habilidades para o próximo nível. Esses conceitos oferecerão a você uma base sólida no idioma e o ajudarão a se tornar um desenvolvedor mais competente e confiante.

1.Promessas

Uma ferramenta poderosa para lidar com ações assíncronas em seu código são as promessas de JavaScript. Eles fornecem um meio de lidar com os resultados de uma operação assíncrona, como uma solicitação de rede, sem bloquear a execução do restante do seu código. Uma promessa pode estar em um dos três estados — cumprida, rejeitada ou pendente — e simboliza o resultado final de uma operação assíncrona.

Em comparação com o emprego de funções de retorno de chamada, as promessas oferecem um mecanismo para lidar com os resultados de uma ação assíncrona de maneira mais simplificada e controlada. Além disso, eles facilitam o tratamento de erros de maneira consistente e encadeiam várias tarefas assíncronas.

As promessas são amplamente usadas em bibliotecas e estruturas JavaScript, como AngularJS, ReactJS e Node.js. Introduzidos no ES6, eles são um conceito fundamental para o desenvolvimento moderno de JavaScript. A legibilidade, capacidade de manutenção e desempenho do seu código podem ser consideravelmente aprimorados ao saber como usar as promessas corretamente.

Ao usar promessas JavaScript, o objeto “ promessa ” é um construtor que cria uma nova promessa e recebe um único argumento, que é uma função chamada "executor". A função do executor recebe dois parâmetros, “ resolver ” e “ rejeitar ”, que podem ser usados ​​para indicar se a operação assíncrona foi bem-sucedida ou falhou.

Vejamos o seguinte exemplo:

Depois que uma promessa é criada, você pode usar seu método “ then” para anexar callbacks que serão invocados quando a promessa for resolvida e seu método “ catch” para anexar callbacks que serão invocados quando a promessa for rejeitada. Como alternativa, você pode gerenciar várias promessas simultaneamente usando os métodos Promise.all() e Promise.race(). As promessas estão disponíveis em todos os ambientes JavaScript modernos e são suportadas em todos os principais navegadores, para que você possa usá-las com confiança em seus projetos.

2. Assíncrono/Aguardar

Assim como as promessas, o recurso “ async/await ” é usado em JavaScript para lidar com operações assíncronas. Foi introduzido no ECMAScript 2017 (ES8) e construído com base nas promessas de tornar o código assíncrono em JavaScript mais gerenciável e fácil de ler. Async/await oferece uma técnica para criar código assíncrono com aparência e comportamento síncronos.

Uma função assíncrona é definida pela palavra-chave “ async ”. A função assíncrona pode então gerar uma promessa e usar a palavra-chave “ await ” para atrasar a execução do código enquanto espera que a promessa seja cumprida.

Um exemplo é mostrado abaixo:

No código acima, “ fetch ” é uma função assíncrona que retorna uma promessa e a palavra-chave “ await ” é usada para aguardar a resolução da promessa antes de analisar os dados JSON.

Usar async/await torna seu código mais fácil de compreender, manter e ser mais robusto. E, em comparação com retornos de chamada e promessas, é uma maneira mais moderna, clara e eficaz de escrever código assíncrono.

Dito isso, é crucial observar que async/await complementa as promessas, não as substitui. Simplificando, é uma sintaxe diferente para executar a mesma operação. Ele ainda depende de promessas para lidar com o fluxo assíncrono internamente.

3. Fechamentos

Em JavaScript, as funções internas podem acessar as variáveis ​​e o escopo de suas funções externas graças a um conceito-chave chamado encerramento. Quando uma função é definida dentro de outra função e a função interna retém o acesso às variáveis ​​e escopo de execução da função externa mesmo após a função externa ter concluído sua execução, um fechamento é gerado.

Considere o seguinte código:

Neste exemplo, a variável x é acessível para a função interna innerFunction do escopo da função externa porque ela é definida dentro da função externa outerFunction . A função externa retorna a função interna, que é então atribuída à variável myClosure. A função interna ainda tem acesso ao valor de x e pode retorná-lo quando chamada, mesmo quando a execução da função externa estiver concluída.

Closures em JavaScript são comumente usados ​​para uma variedade de tarefas, incluindo a criação de variáveis ​​privadas, a aplicação de paradigmas de programação funcional e a criação de fechamentos em loops. Ao permitir que as funções carreguem dados com elas, os encerramentos também podem ser utilizados para criar um código robusto e expressivo. Também em JavaScript, pode ser usado para implementar padrões de programação orientados a objetos.

No entanto, os encerramentos também podem resultar em vazamentos de memória se não forem usados ​​adequadamente, pois a função interna continua a manter uma referência à variável e ao escopo da função externa e o coletor de lixo não consegue liberar a memória.

4. Herança Prototípica

Em contraste com o modelo de herança baseado em classe usado em linguagens como Java e C#, o JavaScript usa um modelo de herança baseado em protótipo. Em JavaScript, os objetos herdam suas propriedades e métodos de outros objetos em vez de classes.

Cada objeto JavaScript tem uma propriedade interna chamada __proto__ (ou [[Prototype]]), que aponta para um objeto diferente conhecido como “objeto protótipo”. Quando as propriedades ou métodos de um objeto são acessados, o JavaScript primeiro determina se o próprio objeto tem a propriedade ou método definido diretamente nele. Caso contrário, o JavaScript verifica o objeto protótipo do objeto e continua procurando na cadeia de protótipos até encontrar a propriedade ou o método ou atingir o final da cadeia.

Vejamos o exemplo abaixo:

Você observará no código acima que o objeto “ cão ” neste exemplo herda o atributo “ come ” de “ animal ”, seu objeto protótipo, ao invés de tê-lo especificado diretamente nele. É assim que a herança prototípica funciona. É um mecanismo em que os objetos podem herdar as propriedades e métodos de outros objetos.

É essencial observar aqui que a herança nesse contexto é dinâmica e não estática, o que significa que você pode modificar o objeto protótipo adicionando ou removendo propriedades e métodos, e os objetos derivados dele serão atualizados para refletir as alterações.

Embora o paradigma de herança baseado em protótipo em JavaScript tenha o potencial de ser mais flexível e potente do que a herança baseada em classe, ele também pode ser mais complicado e difícil de compreender. Para usar e criar objetos em JavaScript corretamente, é preciso ter um conhecimento sólido da herança baseada em protótipo.

5. Curry

Currying em JavaScript é uma técnica que permite a aplicação parcial de uma função pré-preenchendo alguns de seus argumentos. Isso permite que as chamadas de função sejam reutilizadas e flexíveis.

Considere uma função que aceita os dois argumentos x e y e retorna seu total como exemplo.

Usando encerramentos, podemos transformar essa função em uma função curried que aceita o argumento restante.

Nesta ilustração, o uso inicial de add(2) produz uma nova função que aceita o argumento final, y. Como os valores x e y já estão definidos como 2 e 3, respectivamente, quando add2(3) é chamado, o resultado é 5.

O operador de propagação e as funções de seta também podem ser usados ​​para obter currying. Vejamos um exemplo abaixo:

Nesta ilustração, a primeira chamada para add(2) produz uma nova função de seta que adiciona x e y a quaisquer argumentos restantes, independentemente de seu número.

Currying pode aumentar a reutilização e a legibilidade do código, além de permitir que as chamadas de função sejam mais flexíveis.

6. Funções de ordem superior

As funções de ordem superior em JavaScript são aquelas que aceitam uma ou mais funções como argumentos e/ou retornam outra função como saída. Como podem ser usadas como qualquer outra variável ou valor, essas funções também são chamadas de “funções de primeira classe”. O termo “ordem superior” indica um nível de abstração mais alto do que a função JavaScript usual.

Alguns exemplos de funções de ordem superior são:

eu. Array.prototype.map(): Esta função aplica uma função callback a cada elemento de um array que recebe como argumento, retornando um novo array com os resultados.

ii. Array.prototype.filter(): Esta função aceita uma função callback como argumento e a aplica a cada elemento do array, retornando um novo array apenas com os elementos que passam no teste.

iii. Array.prototype.reduce(): Esta função pega uma função callback como argumento e a aplica a cada elemento de um array, acumulando os resultados em um único valor.

4. Array.prototype.forEach(): Esta função é um método iterativo e chama uma função de retorno de chamada fornecida uma vez para cada elemento em uma matriz em ordem de índice crescente.

v. setTimeout(): Esta função aceita uma função de retorno de chamada como uma entrada e agenda sua execução para um determinado período de tempo no futuro.

vi. Promise.then(): Esta função aceita uma função de retorno de chamada como entrada e agenda sua execução para quando uma promessa for resolvida.

Essas funções de ordem superior oferecem um mecanismo para abstrair a lógica e a funcionalidade, passando-as como variáveis ​​ou argumentos, permitindo um código mais adaptável e reutilizável. Por exemplo, se você deseja que uma matriz de números retorne apenas números pares, pode usar o método de ordem superior filter().

A função filter() neste exemplo recebe uma função anônima como retorno de chamada e a utiliza para determinar se cada elemento na matriz é par antes de retornar apenas os números pares.

7. Geradores

Estes são um tipo especial de função em JavaScript que permite pausas e reinicializações de execução. Eles são úteis ao trabalhar com iteradores e programas assíncronos, pois permitem construir uma sequência de valores ao longo do tempo.

Uma função geradora é definida usando a palavra-chave function* e usa a palavra-chave yield para pausar a execução e retornar um valor. Quando a função geradora é invocada, ela produz um objeto iterador que pode ser usado para controlar a execução do gerador.

Supondo que queremos criar uma função geradora que retorne o próximo número na sequência de Fibonacci:

No exemplo acima, a palavra-chave function* é usada para declarar a função geradora fibonacci(). Ele usa a palavra-chave yield para retornar o valor atual de a e atualiza os valores de aeb usando a atribuição de desestruturação. O loop while garante que o gerador continue a produzir novos valores indefinidamente.

A função geradora, quando invocada, retorna um objeto iterador que pode ser usado para controlar a execução do gerador. O método next() é usado para retomar a execução e retornar o próximo valor.

Grandes conjuntos de dados, código assíncrono, iteradores e sequências infinitas podem ser implementados com a ajuda de geradores. Sem ter que carregar todos os dados na memória de uma só vez, eles permitem gerar uma sucessão de valores ao longo do tempo.

8. WeakMaps e WeakSets

Semelhante a um mapa em JavaScript, um WeakMap é um tipo de coleta de dados que permite armazenar pares chave-valor. A distinção importante é que as chaves em um WeakMap são fracamente referenciadas, o que significa que elas podem ser coletadas como lixo se não existirem outras referências a elas. O WeakMap pode, portanto, ser usado para armazenar dados que você não deseja manter na memória se não estiver mais em uso.

Por outro lado, um WeakSet é semelhante a um Set, mas exibe o mesmo comportamento de referência fraca para seus componentes. Isso significa que, se não houver outras referências a um elemento em um WeakSet, ele poderá ser coletado como lixo.

Aqui está um exemplo de uso de um WeakMap em JavaScript:

No exemplo acima, criamos um WeakMap e armazenamos um par chave-valor com um objeto servindo como chave. Em seguida, usamos o objeto como a chave para obter o valor. O par chave-valor também é eliminado do WeakMap quando definimos o objeto como nulo, tornando-o adequado para coleta de lixo.

Um exemplo de um WeakSet, no entanto, é mostrado abaixo:

Neste exemplo, dois objetos são adicionados a um WeakSet que foi criado. O método has() é então usado para determinar se um determinado objeto está presente no conjunto. O primeiro objeto é retirado do WeakSet e é elegível para coleta de lixo depois que o definimos como nulo.

Ambos WeakMap e WeakSet são úteis para gerenciar o uso de memória JavaScript porque permitem que você armazene informações sem armazená-las permanentemente na memória.

9. Procuradores

Objetos que servem como intermediários entre o código que os acessa e os objetos subjacentes que eles representam são conhecidos como proxies. Eles permitem modificar e interceptar operações feitas no objeto subjacente, como chamadas de método, construtores e acesso à propriedade.

O código a seguir é um exemplo de como um proxy pode ser usado para interceptar o acesso à propriedade:

Neste exemplo, um objeto chamado obj é agrupado em um objeto Proxy. Para lidar com acesso e atribuição de propriedade, definimos duas funções de “manipulador”, “ get ” e “ set ”. Podemos interceptar e alterar o comportamento do objeto subjacente usando essas funções. A função “ get ” será chamada, uma mensagem será logada, e o valor da propriedade “ name ” do objeto original será retornado quando acessarmos a propriedade name do objeto proxy.

Os proxies podem ser usados ​​para diversos fins, como:

  • Implementando acessadores de propriedade personalizada
  • Implementando invocação de método personalizado
  • Implementando construtores personalizados
  • Implementando comportamento personalizado para objetos “virtuais”
  • Implementando comportamento personalizado para objetos existentes
  • Implementando o tratamento de erros personalizado
  • Implementando recursos de segurança personalizados

10. Refletir API

Semelhante a como você usaria os operadores e funções integrados do JavaScript, a API JavaScript Reflect oferece vários métodos que permitem realizar várias ações em objetos. A principal distinção é que os operadores e funções integrados geralmente retornam o resultado da operação, enquanto os métodos da API do Reflect normalmente retornam um Booleano indicando o sucesso ou a falha da operação.

Vamos dar uma olhada no exemplo abaixo:

Neste exemplo, a propriedade “name” no objeto obj é definida como o valor “John” usando o método Reflect.set(). Quando a operação for bem-sucedida, o método retornará true.

Aqui está outro exemplo de como usar a API Reflect para determinar se um objeto é extensível ou não:

Este exemplo mostra como verificar a extensibilidade de um objeto usando o método Reflect.isExtensible(). Devido à sua natureza extensível por padrão, inicialmente retorna true. Em seguida, usamos Object.preventExtensions(obj) para impedir que o objeto seja estendido. Em seguida, usamos Reflect.isExtensible(obj) mais uma vez, que retorna false para indicar que o objeto não é mais extensível.

A API do Reflect fornece vários métodos, como:

  • Reflect.get()
  • Reflect.set()
  • Reflect.has()
  • Reflect.deleteProperty()
  • Reflect.defineProperty()
  • Reflect.getOwnPropertyDescriptor()
  • Reflect.getPrototypeOf()
  • Reflect.setPrototypeOf()
  • Reflect.preventExtensions()
  • Reflect.isExtensible()
  • Reflect.apply()
  • Reflect.construct()

Concluindo, dominar o JavaScript exige uma compreensão sólida de uma ampla gama de conceitos e técnicas. Os conceitos discutidos neste artigo, como encerramentos, protótipos, funções de ordem superior, são apenas alguns dos muitos essenciais para entender como o JavaScript funciona e como escrever um código eficiente e eficaz. Além disso, entender como usar recursos modernos como Promises, async/await, WeakMap e WeakSet, Proxies e Reflect API, que também são discutidos aqui, pode ajudá-lo a escrever um código JavaScript mais moderno e poderoso. Ao reservar um tempo para entender completamente esses conceitos e como eles funcionam, você pode se tornar um desenvolvedor de JavaScript mais proficiente e confiante.