جاوا اسکریپت چگونه کار می‌کند؟ بخش یکم: مروری بر موتور، زمان اجرا و پشته‌ی فراخوانی
ملیکا صفت زاده
برنامه نویسی
1399/9/30
10 دقیقه
به جایی که هستید توجه کنید (وب سایت ما) تماما توسط Javascript ساخته شده است. حال اگر کمی دقیق تر به هر قسمت از دنیای تکنولوژی نگاه کنید، رد پایی از Javascript آنجا می‌بینید؛ وبسایت ها، اپلیکیشن های موبایل، انواع نرم افزار های دسکتاپ، دستگاه های نهفته (Embedded) و ... Javascript همه جا هست.
این پست، اولین بخش از این سری است که هدفش بررسی عمیق‌تر Javascript و توضیح چگونگی کارکرد آن است. دانستن واحدهای سازنده Javascript و این‌که چگونه در کنارهم ایفای نقش می‌کنند، شما را قادر به نوشتن نرم‌افزارهای بهتری خواهد کرد. علاوه بر این ،ما قانون هایی را به شما معرفی میکنیم که رعایت آنها باعث سبک‌تر شدن و بهبود اجرای نرم افزارتان خواهد شد.
اگر نگاهی به آمار بیندازیم، مشاهده میکنیم که Javascript از دیدگاه ریپازیتری‌های فعال و پوش‌های کلی در صدر قرار دارد.
وابسته‌تر شدن پروژه ها به javascript توسعه‌دهندگان را وادار میکند تا سطح دانش خود را از اتفاقات درونی زبان افزایش دهند تا بتوانند نرم افزار های با کیفیت و قابل رقابت تولید کنند. متاسفانه تعداد زیادی از توسعه‌دهندگان بدون آنکه از اتفاقات پشت پرده زبان کوچک‌ترین آگاهی ای داشته باشند، به طور روزانه از آن در پروژه هایشان استفاده میکنند.

تعاریف مورد نیاز

ابتدا به توضیح مواردی می‌پردازیم که دانستن آنها برای درک مطالب این پست ضروری است.
صف (Queue) :
صف در علوم کامپیوتر کاملا هم‌ معنی با تعریفی است که شما از آن در ذهنتان دارید، هرکس ابتدا وارد صف شود، ابتدا هم کارش انجام می‌شود و از صف خارج خواهد شد.
پشته (Stack) :
برعکس صف، احتمالا تعریفی از پشته در ذهن ندارید نحوه ی کارکرد پشته اینگونه است که هرکس آخر وارد شود، کارش تمام می‌شود و از پشته خارج میشود. برای درک بهتر پشته کافی است تا یک کوله پشتی را در نظر بگیرید، وسایلی که اول وارد کوله پشتی میشوند، آخر خارج میشوند و برعکسش، وسایلی که آخر وارد شوند، اول خارج میشوند! دلیل این امر آن است که در پشته ورودی و خروجی هردو یکی هستند ولی درمورد صف، ورودی یک طرف و خروجی طرفی دیگر است.
نخ (Thread) :
برای تعریف Thread به تعریفش در ویکی‌پدیا سری میزنیم .
" نخ یا ریسه کوچترین توالی از دستورالعمل‌های برنامه‌ریزی شده‌است که زمان‌بند سیستم‌عامل می‌تواند آن‌ها را به شکل مستقل مدیریت کند. یک ریسه، یک فرایند سبک ‌وزن است. "
مواردی که در این تعریف حائز اهمیت هستند:
1) توالی دستورها :
کلمه ی توالی بدین معنی است دستورات پشت سر هم و به ترتیب اجرا می‌شوند نه موازی و در کنار هم.
2) فرآیند:
همانظور که میدانید فرآیند ها مجموعه ای از کارها هستند که تلاش میکنند تا هدفی را محقق کنند، پس هر Thread هدف خاص خودش را دارد.
3) سبک وزنی :
وزن فرآیند ها را با کمی ساده نگری میتوان زمان انجام فرآیند ها توصیف کرد. زمان درگیر بودن یکThread نباید زیاد باشد، بدین معنی که اگر قرار است تعداد کارها در Thread زیاد باشد، باید مدت زمان انجام هر کار کوتاه باشد و برعکسش اگر میخواهیم کار های سنگینی انجام دهیم باید تعدادشان کم باشد تا Thread بیش از حد درگیر یا مسدود نشود. مسدود شدن Thread نیز به این معنی است که وقتی دارد کارهای موجود در خودش را به ترتیب انجام می‌دهد، یک باره به کاری سنگین برخورد نکند تا بقیه دستورات که در ادامه قرار دارند معطل شوند.
پس موارد زیر باید در ایجاد یک Thread مورد توجه قرارگیرند:
۱) سبک وزنی ( تعداد کم کارها، زمان اجرای زیاد \ تعداد زیاد کارها، زمان اجرای کم )
۲) دستورات به ترتیب اجرا می‌شوند
۳) به هیج عنوان کارها نباید جوری باشند که Thread مسدود شود.

چکیده

تقریبا همه اسم موتور V8 را شنیده‌اند و بیشتر افراد می‌دانند که Javascript تک نخی Single Thread است و از صف کال‌بک (Callback Queue) استفاده می‌کند. در این پست، به همه‌ی این موارد با جزئیات خواهیم پرداخت و توضیح خواهیم داد که Javascript چگونه کارمی‌کند. با دانستن این جزئیات میتوانید نرم افزار هایی بنویسید که درکنار بهره بردن از API های موجود، باعث مسدود شدن thread نیز نمی‌شوند.
اگر Javascript برای شما نسبتا تازگی دارد، این پست به شما کمک خواهد کرد تا دریابید چرا Javascript درمقایسه با سایر زبان‌های دیگر خیلی "عجیب" است و اگر یک توسعه‌دهنده با تجربه Javascript هستید، این پست دیدگاه‌های تازه‌ای درمورد چگونگی اجرا شدن Javascript در اختیارتان قرار خواهد داد.

موتور Javascript

یک مثال معروف از یک موتور Javascript ، موتور V8 گوگل است. برای مثال، موتور V8 درون کروم و Node.js استفاده می‌شود. در ادامه یک نمای خلاصه شده از این موتور را مشاهده میکنید:
V8 Engine Partsبخش های موتور V8
موتور شامل دو جز اصلی است:
پشته حافظه – جایی است که تقسیم و توزیع حافظه اتفاق می‌افتد.
پشته فراخوانی – جایی است که دستورات کد شما در آن اجرا می‌شوند.

زمان اجرا

API هایی در مرورگرها وجود دارند که تقریبا توسط تمام توسعه‌دهندگان Javascript استفاده می‌شوند (مانند setTimeout ) ولی این API ها، در داخل موتور قرار ندارند. پس از کجا آمده‌اند؟ حقیقت کمی پیچیده‌تر است.
Javascript partsبخش های مختلف Javascript
ما در مرکز ماجرا موتور V8 را داریم ولی چیزهای زیاد دیگری نیز وجود دارند، ازجمله Web API هایی که توسط مرورگرها فراهم شده‌اند، مانند DOM, AJAX, setTimeout, ... ، حلقه رویداد معروف (Event Loop) و صف کال‌بک‌.

پشته فراخوانی

از آنجا که Javascript یک زبان برنامه‌نویسی Single Thread است؛ بنابراین می‌تواند در یک زمان، فقط یک کار را انجام دهد. یعنی کارها متوالی و به ترتیب انجام میشوند. نه موازی و درکنار هم.
پشته فراخوانی یک ساختار داده است که اساسا موقعیت ما در برنامه را نگه می‌دارد و میگوید که ما کجای برنامه هستیم.
وقتی تابعی را صدا میزنیم، این تابع به منظور اجرا وارد پشته میشود، به محض تمام شدن اجرای آن، تابع از پشته خارج می‌شود. به کد زیر نگاه کنید:
1
function multiply(x, y) {
2
    return x * y;
3
}
4
 
5
function printSquare(x) {
6
    const res = multiply(x, x);
7
    console.log(res);
8
}
9
 
10
printSquare(5);
11
زمانی‌که موتور شروع به اجراکردن این کد می‌کند، پشته خالی است. بعداز آن، گام‌های بعدی به شکل زیر خواهد بود:
javascript stackپشته ی فراخوانی Javascript
هر ورود به پشته فراخوانی یک Stack Frame نامیده می‌شود.
این چنین، زمانی‌که یک خطا رخ می‌دهد اثرات پشته ساخته می‌شوند (توضیحات دقیق درباره محل وقوع خطا) این وضعیت پشته فراخوانی در زمان وقوع یک خطا است.
به کد زیر نگاه کنید:
1
function foo() {
2
    throw new Error('SessionStack will help you resolve crashes :) ');
3
}
4
 
5
function bar() {
6
    foo();
7
}
8
 
9
function start() {
10
    bar();
11
}
12
 
13
start();
14
 
اگر این کد در کروم اجرا شود (با این فرض که این کد درون یک فایلی به اسم foo.js قرار دارد) اثر پشته زیر تولید خواهد شد:
javascript error stackپشته ی خطا
"انفجار یا سرریز پشته" : پشته ی فراخوانی ظرفیت محدودی دارد و تعداد کارهایی که میتواند درون خودش نگه دارد کم است. سرزیر پشته یا Stack Overflow زمانی رخ می‌دهد که به حداکثر اندازه پشته فراخوانی برسیم و چون ظرفیت پشته فراخوانی کم است، رسیدن به حداکثر ظرفیت پشته به‌راحتی می‌تواند اتفاق افتد، مخصوصا اگر از توابع بازگشتی بدون ملاحظه استفاده ‌کنید.
به این نمونه کد نگاه کنید:
1
function foo() {
2
    foo();
3
}
4
5
foo();
زمانی‌که موتور شروع به اجراکردن کد می‌کند، برنامه با فراخوانی تابع "foo" شروع می‌شود. این تابع برگشتی است و شروع به فراخوانی خودش بدون هیچ شرط خاتمه‌ای می‌کند. بنابراین در اولین مراحل اجرا، پیوسته تابع مشابه ای به پشته فراخوانی اضافه می‌شود که چیزی شبیه به شکل زیر می‌شود:
stack overflowسرریز پشته
درواقع، تعداد فراخوانی‌های تابع در پشته فراخوانی از اندازه واقعی پشته فراخوانی تجاوز می‌کند و مرورگر تصمیم می‌گیرد که اقدام به ایجاد یک خطا کند که می‌تواند چیزی مانند این باشد:
stack overflow errorارور مربوط به سرریز پشته
اجرای کد در یک thread می‌تواند به میزان زیادی ساده باشد چون مجبور به رسیدگی به سناریوهای پیچیده‌ای که در محیط‌های multi-thread ایجاد می‌شوند (مانند بن‌بست‌ها) نیستید. از طرف دیگر می‌تواند نسبتا محدود باشد. از آن‌جایی که Javascript دارای یک پشته فراخوانی یکتا است، وقتی که تسک ها سنگین و کند باشند، چه اتفاقی می‌افتد؟

هم‌زمانی و حلقه رویدادها

زمانی‌که درون پشته فراخوانی چندین فراخوانی تابع وجود دارد که اجرای آن‌ها بسیار زمان‌بر است، چه اتفاقی می‌افتد؟ برای مثال، تصور کنید که می‌خواهید باJavascript در مرورگر تعدادی کار سنگین مثل پردازش تصویر انجام دهید.
ممکن است سوال کنید که چرا چنین چیزی یک مشکل است؟ مشکل این است که وقتی پشته فراخوانی توابعی را برای اجرا دارد، در زمان اجرای هر تابع، مرورگر نمی‌تواند هیچ‌کار دیگری انجام دهد (توالی Thread) بنابراین با توجه به تابع درحال اجرا، مرورگر و Thread ممکن است مسدود شوند‌.
اگر توابع در حال اجرا زمان‌بر باشند و به سرعت اجرا نشوند (مانند تسک پردازش تصویر) مرورگر نمی‌تواند رابط کاربری را رندر کند، نمی‌تواند هیچ کد دیگری را اجرا کند و بر روی تسک پردازش تصویر گیر می‌کند و این باعث میشوند تا سیگنال های رابط کاربری و ادامه ی تسک های بعد از تسک سنگین مربوط به تصویر، فرصتی برای پردازش نداشته باشند (تا وقتی که پردازش سنگین مذکور تمام شود) و به همین جهت UI نرم‌افزار شما اصطلاحا freeze می‌شود که اتفاق ناخوشایندی است.
این تنها مشکل نیست. زمانی‌که مرورگر شما شروع به پردازش تعداد زیادی کار در پشته فراخوانی می‌کند، ممکن است در واکنش به زمان نسبتا طولانی ای که سپری شده، متوقف شود. اکثر مرورگرها اقدام به ایجاد یک خطا کرده و از شما می‌پرسند که می‌خواهید این صفحه وب را متوقف کنید یا خیر.
Blocking Threadمسدود شدن Thread اصلی برنامه
خب، قطعا این تجربه ی خوبی برای کاربر نیست، اینگونه نیست؟
پس چگونه می‌توانیم کد سنگین را بدون مسدود کردنUI و بی‌ارتباط و بی‌پاسخ ساختن مرورگر اجرا کنیم؟
راه حل این مشکل، کال‌بک‌های asynchronous یا غیرهمزمان هستند.
ادامه این بحث را با جزئیات بیشتر در قسمت های بعدی آموزش دنبال خواهیم کرد.
در تهیه ی این مجموعه، از آموزش های وبسایت بهره برده ایم.
خطا

خروج
از 5
امتیاز دهید:
خطا

خروج
کامنت

برای ارسال کامنت وارد شوید

مشاهده پاسخ ها
مشاهده پاسخ ها