Crie sua primeira biblioteca JavaScript

Já se maravilhou com a magia do React? Já se perguntou como o Dojo faz isso? Já teve curiosidade sobre a ginástica do jQuery? Neste tutorial, vamos nos esgueirar pelos bastidores e tentar construir uma versão supersimples do jQuery.

Usamos bibliotecas JavaScript quase todos os dias. Seja para implementar um algoritmo, fornecer uma abstração sobre uma API ou manipular o DOM, as bibliotecas executam muitas funções na maioria dos sites modernos.

Estamos envolvendo os elementos em um objeto porque queremos ser capazes de criar métodos para o objeto.

Neste tutorial, vamos fazer uma tentativa (decididamente superficial) de construir uma dessas bibliotecas do zero. Trabalharemos na criação de uma biblioteca para manipulação de DOM, como jQuery. Sim, vai ser divertido, mas antes que você fique muito animado, deixe-me esclarecer alguns pontos:

  • Esta não será uma biblioteca completamente completa. Oh, temos um conjunto sólido de métodos para escrever, mas não é jQuery. Faremos o suficiente para lhe dar uma boa noção do tipo de problemas que você encontrará ao construir bibliotecas.
  • Não estamos buscando compatibilidade completa do navegador aqui. O que estamos escrevendo hoje deve funcionar no Chrome, Firefox e Safari, mas pode não funcionar em navegadores mais antigos, como o IE.
  • Não vamos cobrir todos os usos possíveis de nossa biblioteca. Por exemplo, nosso append e prepend métodos só funcionarão se você passar a eles uma instância de nossa biblioteca; eles não funcionarão com nós DOM brutos ou listas de nós.

1. Criando o Boilerplate da Biblioteca

Vamos começar com o próprio módulo. Usaremos os Módulos ECMAScript (ESM), uma forma moderna de importar e exportar código na web.

Como você pode ver, estamos exportando uma classe chamada Dome. Esta será a parte principal da biblioteca e representará um elemento ou uma matriz de elementos.

2. Obtendo elementos e criando instâncias de cúpula

o Dome construtor terá um parâmetro, mas pode ser uma série de coisas. Se for uma string, assumiremos que é um seletor CSS; mas também podemos pegar um único DOM Node, ou um NodeList.

Estamos usando document.querySelectorAll para simplificar a descoberta de elementos. Se selector não é uma string, vamos verificar se há length propriedade. Se existir, saberemos que temos um NodeList; caso contrário, temos um único elemento e o colocaremos em um array. Em seguida, definimos this.elements aos elementos e this.length ao número de elementos.

3. Adicionando alguns utilitários

As primeiras funções que vamos escrever são funções utilitárias simples. Uma vez que nossa Dome objetos podem envolver mais de um elemento DOM, vamos precisar fazer um loop sobre cada elemento em praticamente todos os métodos; então, esses utilitários serão úteis.

Vamos começar com um map função:

Claro, o map função recebe um único parâmetro, uma função de retorno de chamada. Faremos um loop sobre os itens na matriz, coletando o que for retornado do retorno de chamada no results variedade. Observe como estamos chamando essa função de retorno de chamada:

Fazendo desta forma, a função será chamada no contexto do nosso Dome instância, e receberá dois parâmetros: o elemento atual e o número do índice.

Também queremos um forEach função. Isso é realmente muito simples:

Desde NodeLists e Arrays venha com o forEach por padrão, podemos simplesmente encaminhar a chamada para o this.elements.

Mais um: mapOne. É fácil ver o que essa função faz, mas a verdadeira questão é: por que precisamos dela? Isso requer um pouco do que você poderia chamar de “filosofia da biblioteca”.

Um pequeno desvio filosófico

Em primeiro lugar, o DOM pode ser bastante difícil para um iniciante; é uma desculpa muito pobre para uma API.

Se construir uma biblioteca fosse apenas escrever o código, não seria um trabalho muito difícil. Mas enquanto eu trabalhava neste projeto, descobri que a parte mais difícil era decidir como certos métodos deveriam funcionar.

Em breve, vamos construir um text método que retorna o texto de nossos elementos selecionados. Se nosso Dome objeto envolve vários nós DOM (new Dome("li"), por exemplo), o que isso deve retornar? Se você fizer algo semelhante em jQuery ($("li").text()), você obterá uma única string com o texto de todos os elementos concatenados. Isso é útil? Acho que não, mas não tenho certeza de qual seria um valor de retorno melhor.

Para este projeto, retornarei o texto de vários elementos como um array, a menos que haja apenas um item no array; então retornaremos apenas a string de texto, não um array com um único item. Acho que na maioria das vezes você receberá o texto de um único elemento, então otimizamos para esse caso. No entanto, se você estiver recebendo o texto de vários elementos, retornaremos algo com o qual você possa trabalhar.

Voltar para Codificação

Então o mapOne o método simplesmente será executado mape, em seguida, retorne a matriz ou o único item que estava na matriz. Se você ainda não tem certeza de como isso é útil, fique por aqui: você verá!

4. Trabalhando com texto e HTML

Em seguida, vamos adicionar que text método. Assim como o jQuery, podemos passar uma string e definir o texto do elemento ou não usar parâmetros para recuperar o texto.

Como você pode esperar, precisamos verificar um valor em text para ver se estamos configurando ou recebendo. Observe que apenas if (text) não funcionaria, porque uma string vazia é um valor falso.

Se estivermos configurando, faremos um forEach sobre os elementos e definir seus innerText propriedade para o text. Se estivermos recebendo, retornaremos os elementos' innerText propriedade. Observe nosso uso do mapOne método: se estivermos trabalhando com vários elementos, isso retornará um array; caso contrário, será apenas a string.

o html método fará praticamente a mesma coisa que textexceto que ele usará o innerHTML propriedade, em vez de innerText.

Como eu disse: quase idêntico.


5. Manipulando classes

Em seguida, queremos poder adicionar e remover classes; então vamos escrever o addClass e removeClass métodos.

Nosso addClass O método receberá uma string ou um array de nomes de classes. Essencialmente, estamos apenas usando o classList.add método em cada elemento. Quando uma string é passada, somente aquela classe é adicionada, e quando um array é passado, nós iteramos através do array e adicionamos todas as classes contidas.

Bem direto, hein?

Agora, que tal remover classes? Para fazer isso, você faz quase exatamente a mesma coisa, apenas com classList.remove.


6. Ajustando Atributos

Agora, queremos um attr função. Isso vai ser fácil, porque é praticamente idêntico ao nosso text ou html métodos. Assim como esses métodos, poderemos obter e definir atributos: pegaremos um nome de atributo e um valor para definir, e apenas um nome de atributo para obter.

Se o val tem um valor, vamos percorrer os elementos e definir o atributo selecionado com esse valor, usando o elemento setAttribute método. Caso contrário, usaremos mapOne para retornar esse atributo através do getAttribute método.

7. Criando elementos

Devemos ser capazes de criar novos elementos, como qualquer boa biblioteca. Claro, isso não seria bom como um método em um Dome instância, então vamos criá-lo fora do Dome classe

Como você pode ver, tomaremos dois parâmetros: o nome do elemento e um objeto de atributos. A maioria dos atributos pode ser aplicada através do nosso attr método, mas dois terão tratamento especial. Nós vamos usar o addClass método para o className propriedade, e o text método para o text propriedade. Claro, precisaremos criar o elemento e o Dome objeto primeiro. Aqui está tudo isso em ação:

Como você pode ver, criamos o elemento e o enviamos diretamente para um novo Dome objeto. Em seguida, tratamos dos atributos. Claro, terminamos devolvendo o novo Dome objeto.

Mas agora que estamos criando novos elementos, vamos querer inseri-los no DOM, certo?

8. Anexando e Pré-Anexando Elementos

A seguir, escreveremos append e prepend métodos, Agora, essas são realmente funções um pouco complicadas de escrever, principalmente por causa dos vários casos de uso. Aqui está o que queremos ser capazes de fazer:

Os casos de uso são os seguintes: podemos querer acrescentar ou preceder

  • um novo elemento para um ou mais elementos existentes.
  • vários novos elementos para um ou mais elementos existentes.
  • um elemento existente para um ou mais elementos existentes.
  • vários elementos existentes para um ou mais elementos existentes.

Nota: estou usando “new” para significar elementos que ainda não estão no DOM; elementos existentes já estão no DOM.

Vamos passar por isso agora:

Nós esperamos que els parâmetro para ser um Dome objeto. Uma biblioteca DOM completa aceitaria isso como um nó ou uma lista de nós, mas não faremos isso. Temos que fazer um loop sobre cada um de nossos elementos e, dentro disso, fazemos um loop sobre cada um dos elementos que queremos anexar.

Se estamos anexando o els para mais de um elemento, precisamos cloná-los. No entanto, não queremos clonar os nós na primeira vez que forem anexados, apenas nas vezes subsequentes. Então vamos fazer isso:

Este i vem do exterior forEach loop: é o índice do elemento pai atual. Se não estivermos anexando ao primeiro elemento pai, clonaremos o nó. Dessa forma, o nó real irá para o primeiro nó pai e todos os outros pais receberão uma cópia. Isso funciona bem, porque o Dome objeto que foi passado como argumento terá apenas os nós originais (não clonados). Portanto, se estivermos apenas anexando um único elemento a um único elemento, todos os nós envolvidos farão parte de seus respectivos Dome objetos.

Por fim, anexaremos o elemento:

Então, ao todo, é isso que temos:

o prepend Método

Queremos cobrir os mesmos casos para o prepend método, então o método é bastante semelhante:

A diferença ao preceder é que, se você preceder sequencialmente uma lista de elementos a outro elemento, eles terminarão na ordem inversa. Já que não podemos forEach para trás, estou percorrendo o loop para trás com um for ciclo. Novamente, clonaremos o nó se este não for o primeiro pai ao qual estamos anexando.

9. Removendo nós

Para nosso último método de manipulação de nós, queremos remover nós do DOM. Fácil, realmente:

Basta iterar pelos nós e chamar o removeChild método em cada elemento parentNode. A beleza aqui (tudo graças ao DOM) é que isso Dome objeto ainda funcionará bem; podemos usar qualquer método que quisermos, incluindo anexar ou prefixar de volta ao DOM. Legal, hein?

10. Trabalhando com eventos

Por último, mas certamente não menos importante, vamos escrever algumas funções para manipuladores de eventos.

Confira o método e depois discutiremos:

Isso é bastante simples. Nós apenas percorremos os elementos e usamos addEventListener em cada.

o off função, que desconecta manipuladores de eventos, é praticamente idêntica:


11. Usando a Biblioteca

Para usar o Dome, basta colocar nele um script e import isto.

A partir daí, você pode usá-lo assim:

Certifique-se de que o script no qual você está importando é um Módulo ES.

É isso!

Espero que você experimente nossa pequena biblioteca e talvez até a estenda um pouco. Como mencionei anteriormente, eu tenho no Github. Sinta-se à vontade para fazer um fork, brincar e enviar um pull request.

Deixe-me esclarecer novamente: o objetivo deste tutorial não é sugerir que você deve sempre escrever suas próprias bibliotecas.

Existem equipes dedicadas de pessoas trabalhando juntas para tornar as bibliotecas grandes e estabelecidas as melhores possíveis. O objetivo aqui era dar uma pequena espiada no que poderia acontecer dentro de uma biblioteca; Espero que você tenha pego algumas dicas aqui.

Eu realmente recomendo que você pesquise dentro de algumas de suas bibliotecas favoritas. Você descobrirá que eles não são tão enigmáticos quanto você poderia ter pensado, e você provavelmente aprenderá muito. Aqui estão alguns ótimos lugares para começar:

Este post foi atualizado com contribuições de Jacob Jackson. Jacob é desenvolvedor web, redator técnico, freelancer e colaborador de código aberto.

Deixe uma resposta