Nel nostro primo post in questa serie, abbiamo introdotto sprite, e come può essere usato per fare un'animazione cross-browser facile ed efficace sul web. Nel secondo post abbiamo alcune semplici animazioni funzionanti, anche se avevano una buona dose di bug e il codice non era certamente pronto per essere pubblicato.
Oggi, affronteremo questi bug e puliremo il nostro codice in modo che possiamo pubblicarlo su una pagina senza timore di crash di alcun codice usando un metodo chiamato incapsulamento.
Per spiegare veramente cosa c'era di sbagliato nel codice nel nostro ultimo passaggio e perché l'incapsulamento è importante, dobbiamo prima spiegare lo scope variabile.
Immagina di lavorare con il codice qui sotto. Hai una variabile utile nella tua funzione Fai questo()
, e vorresti usare la stessa variabile in un'altra funzione, Fai quello()
, ma ti imbatti in un piccolo problema.
function do_this () var very_helpful_variable = 20; ... // Questo mostra '20', proprio come ti aspetti alert (very_helpful_variable); function do_that () alert (very_helpful_variable); // Ma questo mostra "indefinito"!
La tua variabile funziona alla grande nella funzione che è stata dichiarata, ma al di fuori di quella funzione è come se non fosse mai esistita! Questo è perché Fai quello()
non è dentro il scopo della variabile very_helpful_variable
.
Le variabili sono disponibili solo all'interno del blocco di codice dove sono dichiarate, questo è il loro scopo. Una volta che quel blocco di codice è finito, le sue variabili vengono cancellate.
Dai uno sguardo a questi esempi:
var w = 1; funzione a () var x = 2; funzione b () var y = 3; alert (w); // funziona alert (x); // funziona alert (y); // funziona alert (z); // undefined alert (w); // funziona alert (x); // funziona alert (y); // avviso non definito (z); // undefined function c () var z = 4; alert (w); // funziona alert (x); // avviso non definito (y); // avviso non definito (z); // funziona b (); // undefined alert (w); // funziona alert (x); // avviso non definito (y); // avviso non definito (z); // non definito
Per prima cosa abbiamo la variabile w
, che è dichiarato al di fuori di qualsiasi funzione. Si chiama a variabile globale, e funzionerà ovunque perché il suo ambito è l'intero documento.
La prossima è la variabile X
, poiché è dichiarato all'interno della funzione un()
, funzionerà solo all'interno di quella funzione. Questo include anche il lavoro all'interno della funzione b ()
, da b ()
è dentro un()
.
Tuttavia, una variabile definita all'interno di b ()
(piace y
) non funzionerà nella funzione esterna, poiché è al di fuori del suo ambito.
Si può anche notare che abbiamo tentato invano di chiamare la funzione b ()
dall'interno della funzione c ()
; i nomi delle funzioni seguono le stesse regole delle altre variabili.
Un'altra stranezza con JavaScript, se iniziamo a usare un nome di variabile all'interno di una funzione senza dichiararlo con la parola chiave var
, allora il browser assumerà che quella variabile dovrebbe essere globale. Quindi, se non si è sicuri di dichiarare sempre le variabili con il var
parola chiave, finirai con le variabili globali e non te ne renderai conto!
Quindi, per riassumere: ogni volta che dichiariamo una variabile, possiamo usarla all'interno di quel blocco di codice o all'interno di qualsiasi blocco nidificato al suo interno. Se proviamo a usarlo al di fuori del suo ambito, il valore è impostato su non definito
.
Questo è il motivo per cui nel nostro ultimo post, abbiamo inserito il Timer
variabile al di fuori delle funzioni che lo usavano, dal momento che dovevamo ancora afferrare quella variabile dopo che le funzioni erano finite.
var timer; // Questa è una funzione di variabile globale run_right (stage, left) ... timer = setTimeout (function () run_right (2, left);, 200); ... function stop_running () document.getElementById ('j' ) .style.backgroundPosition = "0px 0px"; // Se 'timer' non è stato impostato come globale, non potremmo fermarlo qui clearTimeout (timer);
Per cancellare il timer, avevamo bisogno Smetti di correre()
essere nell'ambito di applicazione della variabile Timer
. Quindi, abbiamo fatto Timer
una variabile globale che potrebbe essere usata ovunque, cosa potrebbe esserci di sbagliato in questo?
In un determinato ambito, è impossibile avere due elementi chiamati la stessa cosa. Se dovessi provare ad avere due variabili diverse con lo stesso nome, il browser scriverà solo su una di esse. Quindi, se avessimo una variabile chiamata Timer
, e aveva anche una variabile separata chiamata Timer
che è stato chiamato all'interno dello stesso ambito, uno di loro eliminerebbe e prenderebbe il posto dell'altro, e avremmo il caos nel nostro codice. Se avessimo un variabile globale chiamato Timer
, quindi interferirebbe con qualsiasi altra variabile chiamata Timer contenuto in qualsiasi punto della pagina, incluse tutte le librerie JavaScript e tutti i file esterni.
Questa è un'enorme fonte di mal di testa, hai appena visto un plug-in JavaScript molto pulito da qualche parte, e lo scarichi sul tuo sito, e improvvisamente tutti gli altri plug-in si bloccano ... Uno dei plug-in era sciatto con globale variabili, è successo a condividere lo stesso nome con qualcos'altro, il tuo browser inciampa su se stesso, e l'intera pagina si ferma.
Ciò che lo rende ancora peggio è che non noterai mai questo problema quando testerai il codice per la prima volta. Come il nostro codice di animazione dell'ultimo post, funzionerà benissimo da solo. Ma più pezzi aggiungi, maggiori sono le probabilità di avere un conflitto di denominazione, e sarai bloccato a ordinare una dozzina di diversi file JavaScript cercando di capire quali non vanno d'accordo.
Ora ti starai chiedendo: "Le variabili globali sono così convenienti! Che succede se guardo il mio codice con molta attenzione e mi assicuro che non abbia conflitti?" Potrebbe funzionare in un mondo perfetto, ma in realtà spesso ci sono diverse persone che lavorano su parti diverse della stessa pagina, o devono tornare indietro e aggiornare diverse parti del codice anni dopo, o addirittura avere il codice di terze parti su la tua pagina che sarà fuori dal tuo controllo (come la pubblicità a pagamento).
Quindi, in breve, non vorrai le variabili globali più di quanto vorrai per il cablaggio esposto lungo le pareti della tua casa o le macchine esposte nella tua auto, è solo una questione di tempo prima che accada qualcosa che si surriscalda. Per fortuna, c'è un modo migliore per evitare queste insidie.
Possiamo avere tutti i vantaggi delle variabili globali senza i problemi usando una tecnica chiamata incapsulamento. Pensa che stai costruendo un muro attorno al tuo codice con solo poche porte speciali, niente può entrare o uscire da quel codice se non lo permetti specificamente.
JavaScript ha un tipo di variabile chiamato a oggetto. Gli oggetti sono raccolte di dati definite dall'utente che contengono informazioni e funzioni (indicate come proprietà e metodi, rispettivamente). Scriveremo una funzione che crea un oggetto speciale che ha tutte le funzioni di cui abbiamo bisogno "cotto" in esso, e ci permetterà persino di avere più di un robot senza dover duplicare il nostro codice!
Iniziamo definendo una nuova funzione con un nome di variabile. Dovremo passare la variabile a pochi argomenti, passerò l'elemento HTML che animeremo, oltre ad alcuni valori univoci per la velocità di esecuzione e l'altezza di salto in modo da poterli variare da robot a robot.
var RobotMaker = function (robot, run_speed, jump_height) // Metteremo tutte le nostre funzioni e variabili in quest'area. // Questo è all'interno del nostro muro 'impenetrabile', quindi nulla in questa // area entrerà in conflitto con altri codici. return // Qui dentro, inseriamo tutte le nostre "porte" ... // queste saranno l'unico modo in cui tutto può ottenere // dentro o fuori da questo codice. // E poiché questo è ancora all'interno dello stesso 'scope' // come RobotMaker, possiamo usare qualsiasi variabile menzionata sopra!
Dal momento che stiamo mettendo tutte le nostre funzioni all'interno del nostro nuovo "muro", ora sarebbe il momento giusto per rivisitare i bug che avevamo con il codice originale. (Puoi vedere che in azione qui)
Si può notare che se si fa clic su due pulsanti di esecuzione (o un pulsante di esecuzione e di salto) senza fare clic su Stop pulsante intermedio, J continuerà a fare entrambe le azioni. Un secondo problema è che indipendentemente dalla direzione in cui si trova J, quando clicchiamo su Saltare o Stop pulsante, si trova a destra ogni volta. Infine, se si fa clic sul Saltare pulsante di nuovo mentre J sta cadendo da un primo salto, continuerà a cadere attraverso la pagina in un ciclo infinito.
Per affrontare queste cose, dobbiamo essere più specifici su ciò che vogliamo accadere con ciascuna delle nostre funzioni:
Quando clicchiamo a destra:
Quando clicchiamo a sinistra:
Quando facciamo clic su Interrompi in esecuzione:
Quando clicchiamo su Jump:
Prima di tutto, aggiungeremo qualche altra variabile ora. Poiché il timer dovrebbe comportarsi in modo diverso per correre e saltare, avremo due timer separati. Vogliamo anche introdurre un booleano
(true / false) variabile da tracciare se dovremmo essere rivolti a sinistra oa destra, e faremo a palcoscenico
variabile solo per salvarci dal dover digitare il nome completo dell'elemento.
// All'interno della funzione RobotMaker ... var stage = document.getElementById ('stage'); var run_timer, jump_timer; var face_right = true;
Quindi aggiungeremo nuovamente le nostre funzioni per correre a destra, correre a sinistra e saltare. Questi saranno per lo più gli stessi, con alcune differenze. Prima di tutto, tutti i riferimenti all'elemento che stiamo animando possono essere sostituiti con la variabile robot
(che sarà passato come uno degli argomenti nel RobotMaker
funzione). In secondo luogo, abbiamo apportato alcune leggere modifiche alla velocità di esecuzione e all'altezza di salto nelle funzioni in modo che possiamo variare quelle passando valori diversi. Terzo, stiamo usando il face_right
variabile per tracciare quale direzione è rivolta verso J (e nella funzione salto, usando face_right
decidere quale sprite che salta a mostrare). Infine, stiamo usando timer separati per correre e saltare.
// All'interno della funzione RobotMaker ... function run_r (phase, left) face_right = true; if ((left + (15 * run_speed)) < (stage.offsetWidth - robot.offsetWidth)) left = left + (15 * run_speed); robot.style.left = left+"px"; switch (phase) case 1: robot.style.backgroundPosition = "-40px 0px"; run_timer = setTimeout(function()run_r(2, left);, 200); break; case 2: robot.style.backgroundPosition = "-80px 0px"; run_timer = setTimeout(function()run_r(3, left);, 200); break; case 3: robot.style.backgroundPosition = "-120px 0px"; run_timer = setTimeout(function()run_r(4, left);, 200); break; case 4: robot.style.backgroundPosition = "-80px 0px"; run_timer = setTimeout(function()run_r(1, left);, 200); break; else robot.style.backgroundPosition = "0px 0px"; function run_l(phase, left) face_right = false; if (0 < robot.offsetLeft - (15 * run_speed)) left = left - (15 * run_speed); robot.style.left = left+"px"; switch (phase) case 1: robot.style.backgroundPosition = "-40px -50px"; run_timer = setTimeout(function()run_l(2, left);, 200); break; case 2: robot.style.backgroundPosition = "-80px -50px"; run_timer = setTimeout(function()run_l(3, left);, 200); break; case 3: robot.style.backgroundPosition = "-120px -50px"; run_timer = setTimeout(function()run_l(4, left);, 200); break; case 4: robot.style.backgroundPosition = "-80px -50px"; run_timer = setTimeout(function()run_l(1, left);, 200); break; else robot.style.backgroundPosition = "0px -50px"; function jmp(up, top) if (face_right) robot.style.backgroundPosition = "-160px 0px"; else robot.style.backgroundPosition = "-160px -50px"; if (up && (robot.offsetTop > (20 * (1 / jump_height)))) top = top - (in alto * .1); robot.style.top = top + "px"; jump_timer = setTimeout (function () jmp (up, top);, 60); else if (up) up = false; jump_timer = setTimeout (function () jmp (up, top);, 60); else if (! up && (robot.offsetTop < 115)) top = top + (top * .1); robot.style.top = top+"px"; jump_timer = setTimeout(function()jmp(up, top);, 60); else robot.style.top = "120px"; if (face_right) robot.style.backgroundPosition = "0px 0px"; else robot.style.backgroundPosition = "0px -50px"; jump_timer = false;
Tutte queste variabili e funzioni sono all'interno del nostro "muro", quindi ora dobbiamo rendere le "porte" in grado di accedere solo a ciò di cui abbiamo bisogno. Queste quattro "porte" saranno oggetto metodi per le stesse quattro funzioni che avevamo in precedenza e farà riferimento alle funzioni protette di cui sopra. Inoltre, completeremo il nostro bug fixing controllando ogni funzione se il jump_timer
sta andando, e quindi assicurandosi di cancellare il run_timer
. Ricorda, questi due timer sono in ambito ovunque all'interno del RobotMaker ()
funzione, quindi possiamo usarli qui. Tuttavia, dal momento che non sono variabili globali, non incontreremo alcun problema con loro altrove.
// All'interno della funzione RobotMaker ... return run_right: function () if (! Jump_timer || jump_timer == undefined) clearTimeout (run_timer); run_r (1, robot.offsetLeft); , run_left: function () if (! jump_timer || jump_timer == undefined) clearTimeout (run_timer); run_l (1, robot.offsetLeft); , stop_running: function () if (! jump_timer || jump_timer == undefined) clearTimeout (run_timer); if (face_right) robot.style.backgroundPosition = "0px 0px"; else robot.style.backgroundPosition = "0px -50px"; , jump: function () if (! jump_timer || jump_timer == undefined) clearTimeout (run_timer); jmp (true, robot.offsetTop);
Ora che abbiamo scritto una funzione che crea oggetti, possiamo usarla tutte le volte che vogliamo creare oggetti con le proprietà di animazione che vogliamo. Nella parte inferiore della nostra pagina, dichiareremo due nuovi RobotMaker
oggetti e passa loro l'elemento che vogliamo animare, una velocità di esecuzione e un'altezza di salto.
var j = RobotMaker (document.getElementById ('j'), 1, 1); var j2 = RobotMaker (document.getElementById ('j2'), .8, 5);
Ora non abbiamo alcun pericolo di nulla in RobotMaker ()
funzione che si interrompe e interferisce con il nostro codice, e possiamo ancora arrivare alle funzioni che vogliamo attraverso le "porte" che abbiamo installato in questo modo:
Quindi, ora puoi vedere il prodotto finito sulla penna Hyrgo.
Nota come non ci sono più problemi con le funzioni che interferiscono tra loro, e puoi azionare ogni robot singolarmente senza influenzare l'altro. L'incapsulamento è una tecnica incredibilmente importante, e dovresti davvero familiarizzare con esso se vuoi fare qualsiasi progetto web interattivo.
Se vuoi, ti preghiamo di controllare tutto questo codice, completamente commentato, e puoi ottenere gli sprite usando i seguenti link: ecco i primi sprite ed ecco i secondi. Tieni presente che per far funzionare lo stesso codice con entrambi gli sprite, avevo bisogno di creare il secondo sprite nello stesso formato e dimensioni esattamente come il primo.
In modo che avvolge la terza parte dello sprite! Nel nostro post successivo e finale, sostituirò quei pulsanti facendo in modo che i nostri robot seguano il mouse sullo schermo e mostrino come impostare ascoltatori di eventi e abilitare il supporto tra browser e dispositivi touch.