JS Closure, IIEF, How V8 works(Heep Memory,Call Stack,Callback Queue,Event Loop…)
Salam hərkəsə,bu məqalədə JS-in həssas movzularına toxunmağa çalışmışam.
NativeJS öyrənənlər üçün faydalı olacağını düşünürəm.
Xoş mütaliələr…
Closure - function daxilində təyin olunan inner function-ın outher function-ın dəyişənlərinə kənardan access-i olması hadisəsidir.
Əsasən functional programming languages-ə xas olan konseptdir.
Bir çox dillərdən bildiyimiz kimi normal olaraq hər function işə düşəndə memory-də function daxilində declare olunan dəyişənlər yaradılır və function işini bitirdikdən sonra həmin dəyişənlər avtomatik memory-dən silinir.
Closure istifade edərək bu dəyişənlərin memory-dən silinməsinin qarşısını almış oluruq.
Performance considerations - “It is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task, as it will negatively affect script performance both in terms of processing speed and memory consumption.” More Info
Yəni,Closure işlətmək zəruri olmadıqca işlətmək lazım deyil.Çunki, həm script-in performansına,həm də mənasız yaddaş itkisinə səbəb olur.
Closure konsepti ilə bağlı olan JS interview suallarını anlamaq üçün zəruri olan bəzi JS mövzularını gözdən keçirib və daha sonra yenidən bu mövzuya qayıdacağıq.
Let’s start…
How works V8(JS Engine)
V8-in necə işlədiyini anlamaq üçün aşağıdakı anlayışları bilməliyik.
Call Stack - çagrılan funksiyaları saxlayır və işlənmə ardıcılıgını manage edir.
- “Invoked functions” - ın məlumatlarını saxlayır.
- “LIFO ”konsepti ilə işləyir(Last In First Out),yəni Call Stack-ə sonuncu daxil olan function birinci execute olur.
- JS “Single Thread” olduğu üçün eyni zaman kəsimində yalnız bir funksiya aktiv olur.Yəni,functions ardıcıl işə düşür paralel yox.
- Əlavə edilən hər element “Stack Frame” adlanır. Error baş verərkən ya da Debug edərkən istifadə etdiyimiz Stack-trace-lər hamsı Call Stack-dən gəlir.
Note: Call Stack ilə Stack fərqli anlayışlardır. Stack - Data Structure-dir, Call-Stack yalnız Invoked functions haqqında məlumatları saxlayır.
Heep Memory - Elan edilən functions və variables burda saxlanılır.
Note: Elan edilen functions Heep Memory-de saxlanılır və əgər onlar invoke edilərsə Call Stack-ə düşür.
Event Loop - JS engine-in ən vacib hissəsidir. Bütün functions-ı handle edən sonsuz dövrdür.
- Sync functions-ı Call Stack-ə əlavə edir
- Async functions(ajax, callbacks…)-ı Callback Queue-ə əlavə edir.
- Callback Queue-ə əlavə olunan functions-ı Call Stack boşaldıqda Call Stack-ə göndərir və işə salır.
Callback Queue - Async functions-ın Event loop tərəfindən gözlədildiyi yerdir. (callbacks, Ajax, etc…)
async functions çağrılanda aşağıdakı mərhələlərdən keçir:
- İlk öncə async functions Callback Queue-ə düşür
- daha sonra Event Loop Call Stack-in boş olub olmamasını yoxlayır.
- Əgər Call Stack-də icra ediləcək function qalmayıbsa ardıcıl şəkildə Callback Queue-dəki functionları Call Stack-ə daşıyır və işə salır.
Example-1: bu nümunədə functions-in LIFO prinsipi ilə necə işlədiyini göstərmişəm.Yəni ən son daxil olan function ilk icra olunur.
Şəkildən göründüyü kimi əvvəlcə avarage() işləyir və içərisindəki add() işə düşür, add() işini bitirdikdən sonra yenidən avarage() qaldığı yerdən davam edir.
Example-2:
Aşağıdakı şəkildə setTimeout(()=>{},0) olduğu üçün gözləntimiz:
X-1, X-2, X-3, “Finished” və ya X-1, X-3, X-2, “Finished”(millisecond cox olsaydı) olacaqdı.
Amma aşağıdakı şəkildən də göründüyü kimi cavab bele deyil və əsas məsələnin milliseconds-la heç bir əlaqəsi yoxdur.
Explanation:
Qrafikdən(Image1) də göründüyü kimi functions invoke edilən zaman Call Stack və Callback Queue olaraq 2 yerdə saxlanılır.
Code execute olunanda sync functions “Call Stack”-ə, async functions isə “Callback Queue”-ə düşür.
Nümunədə console.log()-lar və f() sync functions olduğu üçün “Call Stack”-ə düşür.
setTimeout() isə icində callback function(async) olduğuna görə “Callback Queue”-ə düşür.
Execute zamanı JS Engine ilk öncə “Call Stack”-dəki functions-a baxır,
“Call Stack”- dəki bütün functions icra olunub bitəndən sonra “Callback Queue”-dəki functions-ı “Call Stack”-ə əlavə edir və ardıcıl olaraq icra edir.
More info about Event loop: https://www.youtube.com/watch?v=8aGhZQkoFbQ
Lexical scoping
Bir çox dillərdə scope anlayışı Local və Global olaraq ayrılsada JS-də bu anlayış biraz fərqlidir :
- Block scope - let , const : block daxilində declare olunur (if,for…).
- Function scope - var : function daxilinde declare olunur.
- Global scope - function-dan kənarda declare olunur.
Hoisting in JS - Functional scops-da function-ın istənilən sətirində təyin olunan variable-in declare hissəsi avtomatik function-ın ilk sətirinə qalxır. (also Global scope)
NOTE: JavaScript only hoists declarations, not initializations. Yəni,yalnız declare hissəsi ən yuxarı qalxır, qiyməti yox.
Bu nümunədə dəyişənin yalnız declare(var car) hissəsi function-ın ilk sətirinə qalxdığı üçün value-su “undefined” olur.
1-ci nümunədə result function daxilində declare olunsada declare hissəsi ilk sətirə qalxdığı üçün global scop-da call edilə bilir.
2-ci nümunədə isə result if daxilində təyin olunsada declare(“var result”) hissəsi function-ın lk sətrinə qalxdığı üçün function scop-da call edilə bilir.
Immediately Invoked Function Expression - “IIFE”
Ən sadə şəkildə izah etsək function-ın dərhal invoke olunmasını təmin edən JS konseptidir.
İndi isə keçək əsas məsələyə “Closure”…
Example: Bu nümunədə 2 functions var və kənardan inner function ilə outher function-a aid olan n dəyişəninin qiymətini dəyişə bilirik.(Closure ilə)
- Outher function - f()
- Inner function - increment()
2-ci şəkildə hər dəfə f yenidən call edildiyi üçün hər dəfəsində yaddaş-dakı name dəyişəni silinib yenidən yazılır və buna görə də n-in qiyməti həmişə 0 olaraq qalır.
Closure Scope Chain
Problem: setTimeout() + for loop
Explanation:
Burda nə baş verdiyini anlamağa çalışaq:
Bunun üçün əvvəlcə aşağıdakı şəkili analiz edək.
- “JS Hoisting” - Yuxarıda qeyd etdiyim bu məsələyə görə declare (var i) for-dan əvvələ qalxdığı üçün onu for-dan kənarda istifadə edə bilirik.
i-in hər iterasiyada qiymətinin necə dəyişdiyini aşağıdakı şəkildə izah etmişəm.
2. How works V8 - izah etdiyim bu mövzunun məsələ ilə əlaqəsi nədir?
Yenədə setTimeout() async islədiyi üçün hər iterasiyada setTimeout() Callback Queue-ə düşəcək.
Call Stack boşalanda,yəni,ən sonda hər 3 setTimeout() Event Loop tərəfindən Call Stack-ə gondəriləcək və icra ediləcək.
O icra ediləndə də for bitmiş olacaq və for bitdikdən sonrada i-in qiymətinin 3 oldugunu yuxarıdakı səkildən bilirik artıq.
Yəni,ən sonda 3 dəfə setTimeout(()=>console.log(3)) işləyəcək.
How fix it:
Bu problemin həlli üçün aşağıdakı 2 üsuldan istifadə edə bilərik.
- şəkildəki problemi let keyword ilə solve etdik.
- şəkildə isə yuxarıda qeyd etdiyimiz IIEF(Immediately Invoked Function Expression) konsepti ilə solve etdik..
Məqalə çox uzun olmasın deyə burda yekunlaşdırıram mövzunu. Diqqətiniz üçün təşəkkürlər. Ümüd edirəm maraqlananlar üçün faydalı olmuşdur…
Sources:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript
- https://eloquentjavascript.net/
- About most the articles on these topics have been researched.
- Youtube(https://www.youtube.com/watch?v=8aGhZQkoFbQ) ,etc…