Documente Academic
Documente Profesional
Documente Cultură
Performanța codului
La această lecție vom studia cum poate fi măsurată performanța codului, și cum poate fi
scris un cod eficient și performant. Vom studia tot ce ține de evenimentele ciclice JavaScript
și cum acestea afectează codul scris.
Folosirea unui ciclu pentru a adăuga conținut
În ultima lecție, am folosit o buclă for pentru a crea două sute de paragrafe, pentru a le
adăuga ascultători de evenimente și pentru a le adăuga la pagină. Să aruncăm o altă privire
asupra buclei for, dar de data aceasta fără codul ascultătorului de evenimente:
for (let i = 1; i <= 200; i++) {
const newElement = document.createElement('p');
newElement.textContent = 'This is paragraph number ' + i;
document.body.appendChild(newElement);
}
myCustomDiv.appendChild(newElement);
}
document.body.appendChild(myCustomDiv);
Apoi putem testa timpul necesar pentru a rula efectiv acest cod!
Testarea performanței codului
Modul standard de a măsura cât durează codul pentru a rula este folosind
performance.now(). performance.now() returnează un timestamp care este măsurat în
milisecunde, deci este extrem de precis. Cât de precisă? Iată ce spune pagina documentației
sale:
precis la cinci miimi de milisecundă (5 microsecunde)
Este incredibil de precis!
Dacă ați folosit vreodată o procedură de sincronizare într-un alt limbaj de programare,
atunci ați fi auzit de timpul Epoch (numit și timpul Unix sau timpul POSIX). Aceste
instrumente vă indică timpul care a trecut de la 1 ianuarie 1970 (primul ianuarie). Metoda
browserului performance.now()este ușor diferită prin aceea că începe să măsoare de la
momentul încărcării paginii. Informații detaliate pot fi găsite pe pagina documentației sale:
performance.now () pe MDN.
Iată pașii care trebuie utilizați pentru ca performance.now() să măsoare viteza codului dvs.:
- utilizați performance.now() pentru a obține ora inițială de început pentru cod
- rulați codul pe care doriți să-l testați
- executați performance.now() pentru a obține o altă măsurare a timpului
- scade timpul inițial din timpul final
Adăugarea a două sute de paragrafe la pagină va fi de fapt relativ rapidă, așa că hai să
încetinim lucrurile folosind un set de bucle for imbricate care contează doar de la una la o
sută ... de o sută de ori!
for (let i = 1; i <= 100; i++) {
for (let j = 1; j <= 100; j++) {
console.log('i and j are ', i, j);
}
}
Apoi, vom adăuga codul performance.now() pentru a măsura cât durează aceste bucle:
const startingTime = performance.now();
fragment.appendChild(newElement);
}
Cercetări suplimentare
performance.now () pe MDN
Interfață de performanță pe MDN
DocumentFragment Interface pe MDN
documente createDocumentFragment pe MDN
Reflow & Repaint
Am menționat Reflow și Repaint în ultima secțiune, acum este timpul să aruncăm o privire
mai atentă.
Reflow este procesul prin care browserul dispune pagina. Se întâmplă atunci când afișați
DOM-ul pentru prima dată (în general după ce DOM și CSS au fost încărcate) și se întâmplă
din nou de fiecare dată când ceva ar putea schimba aspectul. Acesta este un proces destul
de scump (lent).
Repaint are loc după reflow pe măsură ce browserul atrage noul aspect pe ecran. Acest
lucru este destul de rapid, dar totuși doriți să limitați cât de des se întâmplă.
De exemplu, dacă adăugați o clasă CSS la un element, browserul recalculează deseori
aspectul întregii pagini - acesta este un reflow și unul repaint!
Să luăm un exemplu realist. Spuneți că scrieți următoarea mare platformă de blog și doriți să
aveți un buton „eliminare spam” pentru administrator. HTML-ul dvs. arată astfel:
<div id="comments">
<div class="comment"> <!-- some content --> </div>
<div class="comment"> <!-- some content --> </div>
<div class="comment"> <!-- some content --> </div>
</div>
Când rulăm filtrul de spam, descoperim că comentariile unu și doi trebuie eliminate.
Dacă apelăm pur și simplu .removeChild() la fiecare dintre cele două comentarii care trebuie
eliminate, este vorba de o refluxare și o repaint pentru fiecare modificare (deci un total de 2
refluxuri și 2 repaint). Am putea reconstrui întregul lucru într-un DocumentFragment și să
înlocuim #comments- l - acesta este momentul pentru a reconstrui (posibil implicând citirea
fișierelor sau a datelor), plus cel puțin o reflow și un repaint.
Sau am putea să ascundem #comments , să ștergem spamul și să-l afișăm din nou - asta este
surprinzător de rapid, la costul unei refluxuri și a două repaint (și puțin altceva). Este rapid,
deoarece ascunderea nu schimbă aspectul, ci doar șterge acea secțiune a ecranului (1
repiaint). Când faceți secțiunea modificată vizibilă din nou, aceasta este o refluxare și un
repaint.
// hide #comments
document.getElementById("comments").style.display = "none";
// show #comments
document.getElementById("comments").style.display = "block";
DOM virtual
Apropo, acesta este motivul pentru care React și alte biblioteci „virtual DOM” sunt atât de
populare. Nu faceți modificări la DOM, ci faceți modificări la o altă structură (un „DOM
virtual”), iar biblioteca calculează cel mai bun mod de a actualiza ecranul pentru a se potrivi.
Practic este că trebuie să refaceți codul pentru a utiliza orice bibliotecă adoptați și, uneori,
puteți face o treabă mai bună actualizând singur ecranul (pentru că înțelegeți propria
situație unică).
Recapitulare
În această secțiune, am aruncat o scurtă privire la ceea ce este refluxul și revopsirea și am
văzut cum pot influența performanța unui site web.
Reflow este procesul de calcul al dimensiunilor și poziției elementelor paginii. Aceasta este o
sarcină calculatoare intensă (lentă). Repaint este procesul de atragere a pixelilor pe ecran.
Acest lucru este mai rapid decât reflow, dar încă nu este un proces rapid. Trebuie să vă
asigurați că codul dvs. cauzează cel mai mic număr de refluxuri posibil.
Cercetări suplimentare
Filetare unică
Este posibil să fi auzit că JavaScript are un singur fir, dar ce înseamnă asta? Potrivit
Wikipedia, un singur fir este:
procesarea unei comenzi pe rând
Ok, deci JavaScript poate „procesa” o comandă la rând. Opusul single-threading este
multithreading. Există numeroase argumente pro și contra în care nu vom intra (nu ezitați să
consultați articolul Wikipedia despre filetare pentru mai multe informații despre argumente
pro și contra). Vom arunca o privire asupra modelului cu un singur fir JavaScript și cum / de
ce ar trebui să scriem codul nostru pentru a profita de acesta.
Să ne uităm la câteva coduri:
function addParagraph() {
const para = document.createElement('p');
para.textContent = 'JavaScript is single threaded!';
document.body.appendChild(para);
}
function appendNewMessage() {
const para = document.createElement('p');
para.textContent = "Isn't that cool?";
document.body.appendChild(para);
}
addParagraph();
appendNewMessage();
Ținând cont de natura unică a firului JavaScript (ceea ce înseamnă că poate îndeplini o
singură sarcină la un moment dat), să descompunem acest cod în ordinea în care va rula:
- funcția addParagraph() este declarată pe linia 1
- funcția appendNewMessage() este declarată pe linia 6
- addParagraph() este chemat pe linia 13
o execuția se mută în funcție și execută toate cele trei linii în ordine
o acum că funcția este terminată, execuția revine la locul unde a fost apelată
- funcția appendNewMessage()se utilizează pe linia 14
o execuția se mută în funcție și execută toate cele trei linii în ordine
o acum că funcția este terminată, execuția revine la locul unde a fost apelată
- programul se încheie deoarece toate liniile de cod au fost executate.
Sperăm că ordinea în care a fost executat acest cod nu a fost surprinzătoare. Există câteva
lucruri la care vreau în mod special să fii atent. În primul rând, este caracterul executării
până la finalizarea codului. Când addParagraph() este invocat pe linia 13, tot codul din
funcție este executat : nu doar execută unele linii și lasă alte linii pentru a fi executate mai
târziu. Se execută întregul bloc de cod. Un al doilea lucru pe care vreau să-l subliniez este că
addParagraph() este invocat, rulează și se termină înainte de a fi invocat
appendNewMessage() (inclusiv o posibilă refluxare și repaint); JavaScript nu execută mai
multe linii / funcții în același timp (aceasta este o singură conversație ... se procesează o
comandă pe rând!).
Ce se întâmplă dacă am schimba ușor acest cod pentru a crea funcții imbricate:
function addParagraph() {
const para = document.createElement('p');
para.textContent = 'JavaScript is single threaded!';
appendNewMessage();
document.body.appendChild(para);
}
function appendNewMessage() {
const para = document.createElement('p');
addParagraph();
Sincronicitatea codului
În secțiunea anterioară din Call Stack, am folosit termenii:
- alergare până la finalizare
- cu un singur fir
Un alt termen pentru aceasta este sincron . Prin definiție, „sincron” înseamnă:
existente sau care apar în același timp
Tot codul pe care l-am analizat rulează în ordine, în același timp. Funcțiile sunt adăugate la
teancul de apeluri, apoi sunt eliminate din teancul de apeluri după ce au terminat. Cu toate
acestea, există un cod care nu este sincron - ceea ce înseamnă că codul este scris la fel ca
orice alt cod, dar este executat la un moment dat ulterior. Sună deloc familiar? Tocmai ați
lucrat cu ea:
const links = document.querySelectorAll('input');
const thirdField = links[2];
thirdField.addEventListener('keypress', function
handleKeyPresses(event) {
console.log('a key was pressed');
});
... ascultători de evenimente! Cea mai mare parte a acestui cod este sincronă așa cum
sunteți obișnuiți. Dar funcția ascultătorului de evenimente handleKeyPresses nu este
invocată imediat, este invocată la un moment dat ulterior.
Te-ai întrebat vreodată despre asta? Unde merge codul? Dar „stiva de apeluri” despre care
am aflat? Este ascuns undeva în teancul de apeluri?
Să ne uităm la acest fragment de cod:
console.log('howdy');
document.addEventListener('click', function numbers() {
console.log('123');
});
console.log('ice cream is tasty');
Mai întâi, browserul rulează acest bloc de cod până la finalizare - adică pașii 1, 2 și 3. Pasul 2
transmite un gestionar de evenimente ( numbers) către browser pentru o utilizare
ulterioară: browserul va deține această funcție până când apare un eveniment de clic .
Ce se întâmplă dacă cineva face clic înainte ca acest bloc de cod să fie terminat? Când există
un eveniment de clic și există deja un cod care rulează, funcția numbers nu poate fi
adăugată direct la Stack-ul de apeluri din cauza naturii executării până la finalizarea
JavaScript-ului; nu putem întrerupe niciun cod care s-ar putea întâmpla în prezent. Deci
funcția este plasată în Coadă. Când toate funcțiile din Stack-ul de apeluri s-au terminat
(cunoscut și sub denumirea de timp inactiv ), atunci Coada este verificată pentru a vedea
dacă așteaptă ceva. Dacă ceva este în coadă, atunci se execută, creând o intrare în teancul
de apeluri.
Știind cum funcționează JavaScript și Event Loop ne poate ajuta să scriem un cod mai
eficient.
Cercetări suplimentare
Dacă am rula acest cod, șirul 'Howdy'ar apărea în consolă în aproximativ 1.000 de
milisecunde sau în aproximativ o secundă.
Întrucât setTimeout() este un API furnizat de browser, apelul către setTimeout() conferă
funcția sayHi() browserului pe care pornește un cronometru. După terminarea
temporizatorului, funcția sayHi() se mută în Coadă. Dacă pila de apeluri este goală, atunci
funcția sayHi() este mutată în pila de apeluri și executată.
setTimeout() cu întârziere de 0
Un aspect interesant al setTimeout() este că putem trece o întârziere de 0 milisecunde.
setTimeout(function sayHi() {
console.log('Howdy');
}, 0); // ← 0 milliseconds!
S-ar putea să credeți că, deoarece are o întârziere de 0 milisecunde, funcția sayHi ar rula
imediat. Cu toate acestea, încă trece prin bucla de eveniment JavaScript. Deci, funcția este
predată browserului, unde browserul pornește un cronometru de 0 milisecunde. Deoarece
temporizatorul se termină imediat, funcția sayHi se va muta la Coadă și apoi la Stack-ul de
apeluri, după ce Stack-ul de apeluri a terminat executarea oricăror sarcini care rulează în
prezent.
Deci, de ce este de ajutor? Ei bine, această tehnică ne poate ajuta să convertim codul
potențial de lungă durată într-unul divizat pentru a permite browserului să gestioneze
interacțiunile utilizatorilor!
function generateParagraphs() {
const fragment = document.createDocumentFragment();
fragment.appendChild(newElement);
}
document.body.appendChild(fragment);
generateParagraphs();
Acest cod începe prin setarea unei variabile count la 1. Aceasta va urmări numărul de
paragrafe care au fost adăugate. Funcția generateParagraphs() va adăuga 500 de paragrafe
la pagina de fiecare dată când este invocat. Interesant este că există un apel setTimeout() la
sfârșitul funcției generateParagraphs(). Dacă există mai puțin de douăzeci de mii de
elemente, atunci setTimeout() va fi folosit pentru a apela funcția generateParagraphs().
Dacă încercați să rulați acest cod pe o pagină, puteți interacționa în continuare cu pagina în
timp ce rulează codul. Și nu se blochează din cauza apelurilor setTimeout().
Recapitulare setTimeout()
Funcția furnizată de browser setTimeout() preia o altă funcție și o întârziere și invocă funcția
după ce întârzierea a trecut.
Știind cum funcționează JavaScript Event Loop, putem folosi setTimeout() metoda pentru a
ne ajuta să scriem cod care permite browserului să gestioneze interacțiunile utilizatorilor.
Cercetări suplimentare
Dacă doriți să aflați mai multe despre cum să îmbunătățiți performanța site-urilor dvs.,
consultați cursul nostru Optimizarea randării browserelor realizat în parteneriat cu
Google.