Acum câteva săptămâni, am trimis pe Twitter această întrebare din interviu:
*** Răspundeți la întrebarea din capul dvs. acum înainte de a continua ***
Aproximativ jumătate din răspunsurile la Tweet au fost greșite. Raspunsul este NU V8 (sau alte VM-uri) !! În timp ce este cunoscut sub numele de „JavaScript Timers”, funcții precum setTimeout
și setInterval
nu fac parte din specificațiile ECMAScript sau din nicio implementare a motorului JavaScript. Funcțiile cronometrului sunt implementate de browsere și implementările lor vor fi diferite între diferite browsere. Temporizatoarele sunt, de asemenea, implementate nativ de runtime-ul Node.js în sine.
În browsere, funcțiile principale ale temporizatorului fac parte din Window
interfață, care are câteva alte funcții și obiecte. Interfața respectivă face ca toate elementele sale să fie disponibile la nivel global în domeniul principal JavaScript. Acesta este motivul pentru care puteți executa setTimeout
direct în consola browserului.
În Node, temporizatoarele fac parte din global
obiect, care se comportă similar cu browserul Window
interfață. Puteți vedea codul sursă al temporizatoarelor în nod aici.
Unii ar putea crede că aceasta este o întrebare proastă a interviului – de ce să știi asta oricum ?! În calitate de dezvoltator JavaScript, cred că vă așteptați să știți acest lucru, deoarece dacă nu știți, ar putea fi un semn că nu înțelegeți complet modul în care V8 (și alte VM-uri) interacționează cu browserele și Node.
Să facem câteva exemple și provocări cu privire la funcțiile temporizatorului, nu-i așa?
Actualizați: Acest articol face acum parte din „Introducere completă la Node.js”.
Puteți citi versiunea actualizată a acestuia la aici.
Conţinut
- 1 Întârzierea executării unei funcții
- 2 Argumente trecătoare
- 3 Timers Challenge # 1
- 4 Repetarea executării unei funcții
- 5 Anularea cronometrelor
- 6 O întârziere temporizată nu este un lucru garantat
- 7 Timers Challenge # 2
- 8 Cine „apelează” exact funcțiile întârziate?
- 9 Provocarea Timers # 3
- 10 Timers Challenge # 4
Întârzierea executării unei funcții
Funcțiile cu temporizator sunt funcții de ordin superior care pot fi utilizate pentru a întârzia sau repeta executarea altor funcții (pe care le primesc ca prim argument).
Iată un exemplu despre întârziere:
// example1.js
setTimeout(
() => {
console.log('Hello after 4 seconds');
},
4 * 1000
);
Acest exemplu folosește setTimeout
pentru a întârzia imprimarea mesajului de salut cu 4 secunde. Al doilea argument pentru setTimeout
este întârzierea (în ms). Acesta este motivul pentru care am înmulțit 4 cu 1000 pentru a face în 4 secunde.
Primul argument pentru setTimeout
este funcția a cărei execuție va fi întârziată.
Dacă executați example1.js
fișier cu node
comandă, Node se va întrerupe timp de 4 secunde și apoi va imprima mesajul de salut (și va ieși după aceea).
Rețineți că primul argument pentru setTimeout
este doar o funcție referinţă. Nu trebuie să fie o funcție în linie ca și cum example1.js
are. Iată același exemplu fără a utiliza o funcție inline:
const func = () => {
console.log('Hello after 4 seconds');
};
setTimeout(func, 4 * 1000);
Argumente trecătoare
Dacă funcția care folosește setTimeout
pentru a întârzia execuția acceptă orice argumente, putem folosi argumentele rămase pentru setTimeout
însuși (după cele 2 de care am aflat până acum) să retransmită valorile argumentului către funcția întârziată.
// For: func(arg1, arg2, arg3, ...)
// We can use: setTimeout(func, delay, arg1, arg2, arg3, ...)
Iată un exemplu:
// example2.js
const rocks = who => {
console.log(who + ' rocks');
};
setTimeout(rocks, 2 * 1000, 'Node.js');
rocks
funcția de mai sus, care este întârziată cu 2 secunde, acceptă a who
argumentul și setTimeout
apelul transmite valoarea „Node.js”Ca asta who
argument.
Executarea example2.js
cu node
comanda va imprima „Node.js roci”După 2 secunde.
Timers Challenge # 1
Folosind ceea ce ai învățat până acum setTimeout
, tipăriți următoarele 2 mesaje după întârzierile corespunzătoare.
- Tipăriți mesajul „Bună ziua după 4 secunde”După 4 secunde
- Tipăriți mesajul „Bună ziua după 8 secunde”După 8 secunde.
Constrângeri:
Puteți defini doar o singură funcție în soluția dvs., care include funcții inline. Aceasta înseamnă mulți setTimeout
apelurile vor trebui să utilizeze exact aceeași funcție.
Soluţie
Iată cum aș rezolva această provocare:
// solution1.js
const theOneFunc = delay => {
console.log('Hello after ' + delay + ' seconds');
};
setTimeout(theOneFunc, 4 * 1000, 4);
setTimeout(theOneFunc, 8 * 1000, 8);
am facut theOneFunc
primi o delay
argument și a folosit valoarea acelui delay
argument în mesajul tipărit. În acest fel, funcția poate imprima diferite mesaje pe baza oricărei valori de întârziere pe care i-o transmitem.
Am folosit apoi theOneFunc
in doi setTimeout
apeluri, unul care se declanșează după 4 secunde și altul care se declanșează după 8 secunde. Amandoua setTimeout
apelurile primesc și un A treia argument pentru a reprezenta delay
argument pentru theOneFunc
.
Executarea solution1.js
fișier cu node
comanda va imprima cerințele provocării, primul mesaj după 4 secunde și al doilea mesaj după 8 secunde.
Repetarea executării unei funcții
Ce se întâmplă dacă v-aș cere să imprimați un mesaj la fiecare 4 secunde, pentru totdeauna?
În timp ce poți pune setTimeout
într-o buclă, API-ul cronometrelor oferă setInterval
funcția de asemenea, care ar îndeplini cerința de a face ceva pentru totdeauna.
Iată un exemplu de setInterval:
// example3.js
setInterval(
() => console.log('Hello every 3 seconds'),
3000
);
Acest exemplu va imprima mesajul său la fiecare 3 secunde. Executarea example3.js
cu node
comanda va face ca Node să tipărească acest mesaj pentru totdeauna, până când ucideți procesul (cu CTRL + C).
Anularea cronometrelor
Deoarece apelarea unei funcții de temporizare programează o acțiune, acțiunea respectivă poate fi anulată și înainte de a fi executată.
Un apel către setTimeout
returnează un „ID” al temporizatorului și puteți utiliza acel ID al temporizatorului cu un clearTimeout
apel pentru a anula acel cronometru. Iată un exemplu:
// example4.js
const timerId = setTimeout(
() => console.log('You will not see this one!'),
0
);
clearTimeout(timerId);
Acest cronometru simplu ar trebui să se declanșeze după 0
ms (făcându-l imediat), dar nu va fi pentru că capturăm timerId
valoare și anularea imediat după un clearTimeout
apel.
Când executăm example4.js
cu node
comandă, Node nu va imprima nimic și procesul va ieși.
Apropo, în Node.js, există un alt mod de a face setTimeout
cu 0
Domnișoară. API-ul temporizator Node.js are o altă funcție numită setImmediate
, și este practic același lucru ca un setTimeout
cu 0
ms, dar nu trebuie să specificăm o întârziere acolo:
setImmediate(
() => console.log('I am equivalent to setTimeout with 0 ms'),
);
setImmediate
funcţie nu este disponibil în toate browserele. Nu-l utilizați pentru codul front-end.
La fel ca clearTimeout
, este deasemenea o clearInterval
funcție, care face același lucru, dar pentru setInerval
apeluri și există, de asemenea, un clearImmediate
suna la fel.
O întârziere temporizată nu este un lucru garantat
În exemplul anterior, ați observat cum se execută ceva setTimeout
după 0
ms nu a însemnat executarea imediată (după linia setTimeout), ci mai degrabă executarea imediată după orice altceva din script (inclusiv apelul clearTimeout)?
Permiteți-mi să clarific acest punct cu un exemplu. Iată un simplu setTimeout
apel care ar trebui să se declanșeze după o jumătate de secundă, dar nu va:
// example5.js
setTimeout(
() => console.log('Hello after 0.5 seconds. MAYBE!'),
500,
);
for (let i = 0; i < 1e10; i++) {
// Block Things Synchronously
}
Imediat după definirea temporizatorului în acest exemplu, blocăm timpul de rulare sincron cu un mare for
buclă. 1e10
este 1
cu 10
zerouri în fața ei, deci bucla este a 10
Bucla de miliarde de căpușe (care simulează practic un procesor ocupat). Nodul nu poate face nimic în timp ce această buclă se bifează.
Desigur, acest lucru este foarte rău de făcut în practică, dar vă va ajuta aici să înțelegeți acest lucru setTimeout
întârzierea nu este un lucru garantat, ci mai degrabă un minim lucru. 500
ms înseamnă o întârziere minimă de 500
Domnișoară. În realitate, scenariul va dura mult mai mult pentru a imprima linia de salut. Va trebui să aștepte bucla de blocare pentru a termina mai întâi.
Timers Challenge # 2
Scrieți un script pentru a imprima mesajul „Salut Lume”În fiecare secundă, dar doar de 5 ori. După 5 ori, scriptul ar trebui să imprime mesajul „Terminat”Și lăsați procesul Node să iasă.
Constrângeri: Nu puteți utiliza un setTimeout
apel pentru această provocare.
Aluzie: Ai nevoie de un tejghea.
Soluţie
Iată cum aș rezolva acest lucru:
let counter = 0;
const intervalId = setInterval(() => {
console.log('Hello World');
counter += 1;
if (counter === 5) {
console.log('Done');
clearInterval(intervalId);
}
}, 1000);
Am inițiat un counter
valoare ca 0
și apoi a început un setInterval
apel capturând id-ul său.
Funcția întârziată va imprima mesajul și va incrementa contorul de fiecare dată. În interiorul funcției întârziate, un if
declarația va verifica dacă suntem la 5
ori de acum. Dacă da, se va tipări „Terminat”Și ștergeți intervalul folosind capturat intervalId
constant. Întârzierea intervalului este 1000
Domnișoară.
Cine „apelează” exact funcțiile întârziate?
Când utilizați JavaScript this
cuvânt cheie în interiorul unei funcții obișnuite, astfel:
function whoCalledMe() {
console.log('Caller is', this);
}
Valoarea din interiorul this
cuvântul cheie va reprezenta apelant a funcției. Dacă definiți funcția de mai sus într-un nod REPL, apelantul va fi global
obiect. Dacă definiți funcția din consola unui browser, apelantul va fi window
obiect.
Să definim funcția ca o proprietate pe un obiect pentru a face acest lucru un pic mai clar:
const obj = {
id: '42',
whoCalledMe() {
console.log('Caller is', this);
}
};
// The function reference is now: obj.whoCallMe
Acum, când suni la obj.whoCallMe
funcția folosind direct referința, apelantul va fi obj
obiect (identificat prin id-ul său):
Acum, întrebarea este: ce ar fi apelantul dacă trecem referința la obj.whoCallMe
la o setTimetout
apel?
// What will this print??
setTimeout(obj.whoCalledMe, 0);
Cine va fi apelantul în acest caz?
Răspunsul este diferit în funcție de locul în care este executată funcția timer. Pur și simplu nu puteți depinde de cine este apelantul în acest caz. Pierdeți controlul apelantului, deoarece implementarea temporizatorului va fi cea care vă invocă funcția acum. Dacă îl testați într-un nod REPL, veți primi un Timetout
obiect ca apelant:
Rețineți că acest lucru contează numai dacă utilizați JavaScript this
cuvânt cheie în funcțiile obișnuite. Nu trebuie să vă faceți griji deloc despre apelant dacă utilizați funcții săgeată.
Provocarea Timers # 3
Scrieți un script pentru a imprima continuu mesajul „Salut Lume”Cu întârzieri variate. Începeți cu o întârziere de 1 secundă și apoi incrementați întârzierea cu 1 secundă de fiecare dată. A doua oară va avea o întârziere de 2 secunde. A treia oară va avea o întârziere de 3 secunde și așa mai departe.
Includeți întârzierea în mesajul tipărit. Rezultatul preconizat arată ca:
Hello World. 1
Hello World. 2
Hello World. 3
...
Constrângeri: Puteți utiliza numai const
pentru a defini variabile. Nu poți folosi let
sau var
.
Soluţie
Deoarece valoarea întârzierii este o variabilă în această provocare, nu o putem folosi setInterval
aici, dar putem crea manual o execuție de interval folosind setTimeout
în cadrul unui apel recursiv. Prima funcție executată cu setTimeout va crea un alt timer și așa mai departe.
De asemenea, deoarece nu putem folosi let / var, nu putem avea un contor pentru a incrementa întârzierea în fiecare apel recursiv, dar putem folosi în schimb argumentele funcției recursive pentru a crește în timpul apelului recursiv.
Iată o modalitate posibilă de a rezolva această provocare:
const greeting = delay =>
setTimeout(() => {
console.log('Hello World. ' + delay);
greeting(delay + 1);
}, delay * 1000);
greeting(1);
Timers Challenge # 4
Scrieți un script pentru a imprima continuu mesajul „Salut Lume”Cu același concept diferit de întârzieri ca provocarea nr. 3, dar de data aceasta, în grupuri de 5 mesaje pe interval de întârziere principală. Începând cu o întârziere de 100 ms pentru primele 5 mesaje, apoi o întârziere de 200 ms pentru următoarele 5 mesaje, apoi 300 ms și așa mai departe.
Iată cum ar trebui să se comporte scriptul:
- În momentul de 100 ms, scriptul va începe tipărirea „Hello World” și va face acest lucru de 5 ori cu un interval de 100 ms. Primul mesaj va apărea la 100ms, al doilea mesaj la 200ms și așa mai departe.
- După primele 5 mesaje, scriptul ar trebui să incrementeze întârzierea principală la 200 ms. Deci, al șaselea mesaj va fi tipărit la 500 ms + 200 ms (700 ms), al șaptelea mesaj va fi tipărit la 900 ms, al 8-lea mesaj va fi tipărit la 1100 ms și așa mai departe.
- După 10 mesaje, scriptul ar trebui să incrementeze întârzierea principală la 300 ms. Deci, al 11-lea mesaj ar trebui să fie tipărit la 500ms + 1000ms + 300ms (18000ms). Al 12-lea mesaj ar trebui să fie tipărit la 21000 ms, și așa mai departe.
- Continuați tiparul pentru totdeauna.
Includeți întârzierea în mesajul tipărit. Rezultatul așteptat arată astfel (fără comentarii):
Hello World. 100 // At 100ms
Hello World. 100 // At 200ms
Hello World. 100 // At 300ms
Hello World. 100 // At 400ms
Hello World. 100 // At 500ms
Hello World. 200 // At 700ms
Hello World. 200 // At 900ms
Hello World. 200 // At 1100ms
...
Constrângeri: Puteți utiliza numai setInterval
apeluri (nu setTimeout
) și puteți utiliza doar instrucțiunea ONE if.
Soluţie
Pentru că nu putem folosi decât setInterval
apeluri, vom avea nevoie și de recursivitate aici pentru a crește întârzierea următoarei setInterval
apel. În plus, avem nevoie de o instrucțiune if pentru a controla acest lucru numai după 5 apeluri ale acelei funcții recursive.
Iată o soluție posibilă:
let lastIntervalId, counter = 5;
const greeting = delay => {
if (counter === 5) {
clearInterval(lastIntervalId);
lastIntervalId = setInterval(() => {
console.log('Hello World. ', delay);
greeting(delay + 100);
}, delay);
counter = 0;
}
counter += 1;
};
greeting(100);
Mulțumesc pentru lectură.
Dacă abia începi să înveți Node.js, am publicat recent un cursul primilor pași la Pluralsight, verifică:
#Cronometre #JavaScript #tot #trebuie #să #știți
Cronometre JavaScript: tot ce trebuie să știți