Le librerie JavaScript come jQuery sono state l'approccio go-to per scrivere JavaScript nel browser per quasi un decennio. Sono stati un enorme successo e un intervento necessario per quello che una volta era una terra di browser piena di discrepanze e problemi di implementazione. jQuery ha ignorato senza problemi i bug e le stranezze del browser e ha reso un approccio non banale per ottenere risultati, come la gestione degli eventi, la manipolazione di Ajax e DOM.
All'epoca, jQuery risolveva tutti i nostri problemi, includendone l'onnipotente potenza e mettendosi subito al lavoro. Era, in un certo senso, una scatola nera che il browser "aveva bisogno" per funzionare correttamente.
Ma il web si è evoluto, le API stanno migliorando, gli standard vengono implementati, il web è una scena in movimento molto veloce e non sono sicuro che le librerie giganti abbiano un posto nel futuro per il browser. Sta diventando un ambiente orientato ai moduli.
Un modulo è una parte di funzionalità incapsulata che fa solo una cosa, e quella cosa molto bene. Ad esempio, un modulo potrebbe essere responsabile dell'aggiunta di classi a un elemento, della comunicazione su HTTP tramite Ajax e così via - ci sono infinite possibilità.
Un modulo può venire in molte forme e dimensioni, ma lo scopo generale di esse è quello di essere importati in un ambiente e lavorare fuori dalla scatola. In generale, ogni modulo avrebbe una documentazione di base per gli sviluppatori e il processo di installazione, così come gli ambienti per i quali è concepito (come il browser, il server).
Questi moduli diventano quindi dipendenze del progetto e le dipendenze diventano facili da gestire. I giorni in cui cadono in un'enorme biblioteca stanno lentamente svanendo, le librerie di grandi dimensioni non offrono tanta flessibilità o potenza. Anche le librerie come jQuery hanno riconosciuto questo, il che è fantastico: hanno uno strumento online che ti consente di scaricare solo le cose che ti servono.
Le moderne API sono un enorme stimolo per l'ispirazione dei moduli, ora che le implementazioni dei browser sono drasticamente migliorate, possiamo iniziare a creare piccoli moduli di utilità per aiutarci a svolgere i nostri compiti più comuni.
L'era del modulo è qui, ed è qui per rimanere.
Una moderna API a cui sono sempre stato interessato sin dal suo inizio è l'API classList. Ispirato da librerie come jQuery, ora abbiamo un modo nativo per aggiungere classi a un elemento senza una libreria o funzioni di utilità.
L'API classList esiste da qualche anno, ma non molti sviluppatori ne sono a conoscenza. Questo mi ha spinto ad andare a creare un modulo che utilizzava l'API classList e, per quei browser meno fortunati a supportarlo, fornisce qualche forma di implementazione di fallback.
Prima di immergerci nel codice, diamo un'occhiata a ciò che jQuery ha portato sulla scena per aggiungere una classe a un elemento:
$ (Elem) .addClass ( 'MyClass');
Quando questa manipolazione è avvenuta in modo nativo, ci siamo ritrovati con la summenzionata API classList - un oggetto DOMTokenList (valori separati dallo spazio) che rappresenta i valori memorizzati rispetto a className di un elemento. L'API classList ci fornisce alcuni metodi per interagire con questo DOMTokenList, tutto molto "simile a jQuery". Ecco un esempio di come l'API classList aggiunge una classe, che utilizza il classList.add ()
metodo:
elem.classList.add ( 'MyClass');
Cosa possiamo imparare da questo? Una funzionalità di libreria che si fa strada in una lingua è un grosso problema (o almeno lo stimola). Questo è il bello della piattaforma web aperta, tutti possiamo avere un'idea di come progrediscono le cose.
Quindi che succede adesso? Sappiamo dei moduli e abbiamo un po 'come l'API classList, ma sfortunatamente non tutti i browser lo supportano ancora. Potremmo scrivere una riserva, però. Sembra una buona idea per un modulo che usa classList quando supportato o fallback automatici, se no.
Circa sei mesi fa, ho creato un modulo standalone e molto leggero per aggiungere classi a un elemento in JavaScript semplice - ho finito per chiamarlo apollo.js
.
L'obiettivo principale del modulo era iniziare a utilizzare la brillante API classList e staccare dal bisogno di una libreria per svolgere un'attività molto semplice e comune. jQuery non era (e ancora non lo è) utilizza l'API classList, quindi ho pensato che sarebbe stato un ottimo modo per sperimentare con la nuova tecnologia.
Passeremo attraverso il modo in cui l'ho realizzato e il pensiero dietro ogni pezzo che costituisce il modulo semplice.
Come abbiamo già visto, classList è un'API molto elegante e "jQuery-friendly-friendly", la transizione ad esso è facile. Una cosa che non mi piace, tuttavia, è il fatto che dobbiamo continuare a fare riferimento all'oggetto ClassList per utilizzare uno dei suoi metodi. Ho mirato a rimuovere questa ripetizione quando ho scritto apollo, decidendo il seguente modello di API:
apollo.addClass (elem, 'myclass');
Un modulo di manipolazione di buona classe dovrebbe contenere hasClass
, addClass
, removeClass
e toggleClass
metodi. Tutti questi metodi usciranno dallo spazio dei nomi "apollo".
Osservando da vicino il metodo "addClass" sopra, puoi vedere passare l'elemento come primo argomento. A differenza di jQuery, che è un enorme oggetto personalizzato al quale sei legato, questo modulo accetterà un elemento DOM, come viene alimentato tale elemento fino allo sviluppatore, i metodi nativi o un modulo di selezione. Il secondo argomento è un semplice valore String, qualsiasi nome di classe che ti piace.
Passiamo al resto dei metodi di manipolazione della classe che volevo creare per vedere come sono:
apollo.hasClass (elem, 'myclass'); apollo.addClass (elem, 'myclass'); apollo.removeClass (elem, 'myclass'); apollo.toggleClass (elem, 'myclass');
Quindi, da dove iniziamo? In primo luogo, abbiamo bisogno di un oggetto per aggiungere i nostri metodi e alcune funzioni di chiusura per ospitare qualsiasi funzionamento / variabile / metodo interno. Usando un'espressione di funzione a invocazione immediata (IIFE), avvolgo un oggetto denominato apollo (e alcuni metodi che contengono astrazioni di classList) per creare la nostra definizione di modulo.
(function () var apollo = ; apollo.hasClass = function (elem, className) return elem.classList.contains (className);; apollo.addClass = function (elem, className) elem.classList.add (className);; apollo.removeClass = function (elem, className) elem.classList.remove (className);; apollo.toggleClass = function (elem, className) elem.classList.toggle (className);; window.apollo = apollo;) (); apollo.addClass (document.body, 'test');
Ora abbiamo la classList che funziona, possiamo pensare al supporto del browser legacy. L'obiettivo per il apollomodule
è fornire un'implementazione API piccola e autonoma per la manipolazione della classe, indipendentemente dal browser. È qui che entra in gioco il semplice rilevamento delle funzioni.
Il modo semplice per testare la presenza di feature per classList è questa:
if ('classList' in document.documentElement) // hai supporto
Stiamo usando il nel
operatore che valuta la presenza di classList in Boolean. Il prossimo passo sarebbe quello di fornire condizionalmente l'API a classList solo agli utenti che supportano:
(function () var apollo = ; var hasClass, addClass, removeClass, toggleClass; if ('classList' in document.documentElement) hasClass = function () return elem.classList.contains (className); addClass = function (elem, className) elem.classList.add (className); removeClass = function (elem, className) elem.classList.remove (className); toggleClass = function (elem, className) elem.classList.toggle (className); apollo.hasClass = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; window.apollo = apollo;) ();
Il supporto legacy può essere fatto in vari modi, leggendo la stringa className e eseguendo il looping di tutti i nomi, sostituendoli, aggiungendoli e così via. jQuery usa molto codice per questo, utilizzando loop lunghi e struttura complessa, non voglio estendere completamente questo modulo fresco e leggero, quindi decidi di usare una corrispondenza con espressioni regolari e la sostituisce per ottenere lo stesso identico effetto con il prossimo senza alcun codice.
Ecco l'implementazione più pulita che potrei trovare:
function hasClass (elem, className) return new RegExp ('(^ | \\ s)' + className + '(\\ s | $)'). test (elem.className); function addClass (elem, className) if (! hasClass (elem, className)) elem.className + = (elem.className? ":") + className; function removeClass (elem, className) if (hasClass (elem, className)) elem.className = elem.className.replace (new RegExp ('(^ | \\ s) *' + className + '(\\ s | $) * ',' g '), "); function toggleClass (elem, className) (hasClass (elem, className)? removeClass: addClass) (elem, className);
Integriamoli nel modulo, aggiungendo il altro
parte per i browser non di supporto:
(function () var apollo = ; var hasClass, addClass, removeClass, toggleClass; if ('classList' in document.documentElement) hasClass = function () return elem.classList.contains (className);; addClass = function (elem, className) elem.classList.add (className);; removeClass = function (elem, className) elem.classList.remove (className);; toggleClass = function (elem, className) elem. classList.toggle (className);; else hasClass = function (elem, className) return new RegExp ('(^ | \\ s)' + className + '(\\ s | $)'). test ( elem.className);; addClass = function (elem, className) if (! hasClass (elem, className)) elem.className + = (elem.className? ":") + className;; removeClass = function (elem, className) if (hasClass (elem, className)) elem.className = elem.className.replace (new RegExp ('(^ | \\ s) *' + className + '(\\ s | $) * ',' g '), ");; toggleClass = function (elem, className) (hasClass (elem, className)? removeClass: addClass) (elem, className);; apollo.has Class = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; window.apollo = apollo; ) ();
Un js di lavoro su quello che abbiamo fatto finora.
Lasciamolo lì, il concetto è stato consegnato. Il modulo apollo ha alcune funzionalità in più come l'aggiunta di più classi contemporaneamente, puoi verificarlo qui, se interessato.
Allora, cosa abbiamo fatto? Costruito un pezzo di funzionalità incapsulato dedicato a fare una cosa, e una cosa bene. Il modulo è molto semplice da leggere e comprendere e le modifiche possono essere facilmente apportate e convalidate insieme ai test delle unità. Abbiamo anche la possibilità di aprire apollo per i progetti in cui non abbiamo bisogno di jQuery e della sua enorme offerta, e il piccolo modulo Apollo sarà sufficiente.
Il concetto di moduli non è nuovo, li usiamo sempre. Probabilmente sei a conoscenza del fatto che JavaScript non riguarda più solo il browser, è in esecuzione su server e persino su TV.
Quali modelli possiamo adottare quando creiamo e utilizziamo questi nuovi moduli? E dove possiamo usarli? Ci sono due concetti chiamati "AMD" e "CommonJS", esploriamoli qui sotto.
La definizione di modulo asincrono (di solito indicata come AMD) è un'API JavaScript per la definizione dei moduli da caricare in modo asincrono, in genere eseguiti nel browser poiché il caricamento sincrono comporta costi di prestazioni nonché problemi di usabilità, debug e accesso incrociato. AMD può aiutare lo sviluppo, mantenendo i moduli JavaScript incapsulati in molti file diversi.
AMD usa una funzione chiamata definire
, che definisce un modulo stesso e qualsiasi oggetto di esportazione. Usando AMD, possiamo anche fare riferimento a qualsiasi dipendenza per importare altri moduli. Un rapido esempio tratto dal progetto AMD GitHub:
define (['alpha'], function (alpha) return verb: function () return alpha.verb () + 2;;);
Potremmo fare qualcosa del genere per apollo se dovessimo utilizzare un approccio AMD:
define (['apollo'], function (alpha) var apollo = ; var hasClass, addClass, removeClass, toggleClass; if ('classList' in document.documentElement) hasClass = function () return elem.classList. contiene (className);; addClass = function (elem, className) elem.classList.add (className);; removeClass = function (elem, className) elem.classList.remove (className);; toggleClass = function (elem, className) elem.classList.toggle (className);; else hasClass = function (elem, className) return new RegExp ('(^ | \\ s)' + className + '(\\ s | $) '). test (elem.className);; addClass = function (elem, className) if (! hasClass (elem, className)) elem.className + = (elem.className? ":") + className;; removeClass = function (elem, className) if (hasClass (elem, className)) elem.className = elem.className.replace (new RegExp ('(^ | \\ s) *' + className + '(\\ s | $) *', 'g'), ");; toggleClass = function (elem, className) (hasClass (elem, className)? removeClass: addClass) (elem, clas sName); ; apollo.hasClass = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; window.apollo = apollo; );
Node.js è in aumento da qualche anno, oltre a strumenti e schemi di gestione delle dipendenze. Node.js utilizza qualcosa chiamato CommonJS, che utilizza un oggetto "esporta" per definire il contenuto di un modulo. Un'implementazione CommonJS di base potrebbe assomigliare a questa (l'idea di "esportare" qualcosa da usare altrove):
// someModule.js exports.someModule = function () return "foo"; ;
Il codice sopra sarebbe seduto nel suo file, ho chiamato questo someModule.js
. Per importarlo altrove ed essere in grado di usarlo, CommonJS specifica che dobbiamo usare una funzione chiamata "require" per recuperare le dipendenze individuali:
// fa qualcosa con 'myModule' var myModule = require ('someModule');
Se hai usato anche Grunt / Gulp, sei abituato a vedere questo schema.
Per utilizzare questo modello con apollo, faremo quanto segue e faremo riferimento a esportazioni
Oggetto al posto della finestra (vedere l'ultima riga exports.apollo = apollo
):
(function () var apollo = ; var hasClass, addClass, removeClass, toggleClass; if ('classList' in document.documentElement) hasClass = function () return elem.classList.contains (className);; addClass = function (elem, className) elem.classList.add (className);; removeClass = function (elem, className) elem.classList.remove (className);; toggleClass = function (elem, className) elem. classList.toggle (className);; else hasClass = function (elem, className) return new RegExp ('(^ | \\ s)' + className + '(\\ s | $)'). test ( elem.className);; addClass = function (elem, className) if (! hasClass (elem, className)) elem.className + = (elem.className? ":") + className;; removeClass = function (elem, className) if (hasClass (elem, className)) elem.className = elem.className.replace (new RegExp ('(^ | \\ s) *' + className + '(\\ s | $) * ',' g '), ");; toggleClass = function (elem, className) (hasClass (elem, className)? removeClass: addClass) (elem, className);; apollo.has Class = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; exports.apollo = apollo; ) ();
AMD e CommonJS sono approcci fantastici, ma se dovessimo creare un modulo che volessimo lavorare in tutti gli ambienti: AMD, CommonJS e il browser?
Inizialmente, ne abbiamo fatto un po ' Se
e altro
l'inganno per passare una funzione a ciascun tipo di definizione in base a ciò che era disponibile, avremmo annusato per il supporto AMD o CommonJS e lo useremo se fosse lì. Questa idea è stata poi adattata e una soluzione universale è iniziata, soprannominata "UMD". Lo confeziona se altro
inganno per noi e passiamo semplicemente in una singola funzione come riferimento a entrambi i tipi di modulo supportati, ecco un esempio dal repository del progetto:
(function (root, factory) if (typeof define === 'function' && define.amd) // AMD. Registrati come modulo anonimo. define (['b'], factory); else // Globali del browser root.amdWeb = factory (root.b); (this, function (b) // uso in qualche modo. // Basta restituire un valore per definire l'esportazione del modulo. // Questo esempio restituisce un oggetto , ma il modulo // può restituire una funzione come valore esportato. return ;));
Whoa! Molto sta succedendo qui. Stiamo passando una funzione come secondo argomento al blocco IIFE, che sotto un nome di variabile locale fabbrica
è assegnato dinamicamente come AMD o globalmente al browser. Sì, questo non supporta CommonJS. Tuttavia, possiamo aggiungere questo supporto (rimuovendo i commenti anche questa volta):
(function (root, factory) if (typeof define === 'function' && define.amd) define (['b'], factory); else if (typeof exports === 'object') module .exports = factory; else root.amdWeb = factory (root.b); (this, function (b) return ;));
La linea magica qui è module.exports = factory
che assegna la nostra fabbrica a CommonJS.
Completiamo apollo in questa configurazione di UMD in modo che possa essere utilizzata in ambienti CommonJS, AMD e il browser! Includerò lo script apollo completo, dall'ultima versione su GitHub, quindi le cose appariranno un po 'più complesse di quelle sopra descritte (alcune nuove funzionalità sono state aggiunte ma non sono state appositamente inserite negli esempi precedenti):
/ *! apollo.js v1.7.0 | (c) 2014 @toddmotto | https://github.com/toddmotto/apollo * / (function (root, factory) if (typeof define === 'function' && define.amd) define (factory); else if (typeof exports == = 'oggetto') module.exports = factory; else root.apollo = factory ();) (this, function () 'use strict'; var apollo = ; var hasClass, addClass, removeClass , toggleClass; var forEach = function (items, fn) if (Object.prototype.toString.call (items)! == '[oggetto Array]') items = items.split ("); for (var i = 0; i < items.length; i++) fn(items[i], i); ; if ('classList' in document.documentElement) hasClass = function (elem, className) return elem.classList.contains(className); ; addClass = function (elem, className) elem.classList.add(className); ; removeClass = function (elem, className) elem.classList.remove(className); ; toggleClass = function (elem, className) elem.classList.toggle(className); ; else hasClass = function (elem, className) return new RegExp('(^|\\s)' + className + '(\\s|$)').test(elem.className); ; addClass = function (elem, className) if (!hasClass(elem, className)) elem.className += (elem.className ?":") + className; ; removeClass = function (elem, className) if (hasClass(elem, className)) elem.className = elem.className.replace(new RegExp('(^|\\s)*' + className + '(\\s|$)*', 'g'),"); ; toggleClass = function (elem, className) (hasClass(elem, className) ? removeClass : addClass)(elem, className); ; apollo.hasClass = function (elem, className) return hasClass(elem, className); ; apollo.addClass = function (elem, classes) forEach(classes, function (className) addClass(elem, className); ); ; apollo.removeClass = function (elem, classes) forEach(classes, function (className) removeClass(elem, className); ); ; apollo.toggleClass = function (elem, classes) forEach(classes, function (className) toggleClass(elem, className); ); ; return apollo; );
Abbiamo creato, un pacchetto il nostro modulo per lavorare in molti ambienti, questo ci dà un'enorme flessibilità nel portare nuove dipendenze nel nostro lavoro - qualcosa che una libreria JavaScript non può fornirci senza spezzarla in pochi pezzi funzionali per cominciare.
In genere, i nostri moduli sono accompagnati da test unitari, test di piccole dimensioni che semplificano la partecipazione di altri sviluppatori al progetto e inviano richieste di pull per miglioramenti delle funzionalità, è molto meno scoraggiante di un'enorme libreria e sta elaborando il loro sistema di build ! I moduli piccoli vengono spesso aggiornati rapidamente mentre le librerie più grandi possono impiegare del tempo per implementare nuove funzionalità e correggere i bug.
È stato fantastico creare il nostro modulo e sapere che stiamo supportando molti sviluppatori in molti ambienti di sviluppo. Questo rende lo sviluppo più gestibile, divertente e comprendiamo gli strumenti che stiamo usando molto meglio. I moduli sono accompagnati da una documentazione che possiamo aggiornare rapidamente con il nostro lavoro. Se un modulo non si adatta, potremmo trovarne un altro o scrivere da soli - qualcosa che non potremmo fare facilmente con una grande libreria come un'unica dipendenza, non vogliamo legarci in un'unica soluzione.
Una bella nota da finire, non è stato bello vedere come le librerie JavaScript hanno influenzato le lingue native con cose come la manipolazione delle classi? Un Bene, con ES6 (la prossima generazione del linguaggio JavaScript), abbiamo raggiunto l'oro! Abbiamo importazioni ed esportazioni native!
Dai un'occhiata, esportando un modulo:
/// Funzione myModule.js myModule () // module module export myModule;
E importando:
importare myModule da "myModule";
Puoi leggere di più su ES6 e le specifiche dei moduli qui.