بسیاری از توسعه دهندگان پرشور وجود دارند، که بر روی front-end یا back-end کار میکنند و زندگی خود را وقف محافظت از قلمرو جاوااسکریپت کردهاند. جاوااسکریپت برای فهمیدن بسیار راحت و یک بخش اساسی در توسعه front-end است. اما برخلاف سایر زبانهای برنامهنویسی، این زبان به صورت single threaded کار میکند. این به این معنی است که، تمام کد در یک زمان اجرا میشود. از آنجا که اجرای کد به صورت متوالی انجام میشود، هر کد که مدت زمان بیشتری را برای اجرای آن نیاز دارد؛ مانع از اجرای هر چیزی که باید اجرا شود خواهد شد. از این رو گاهیاوقات هنگام استفاده از Google Chrome با این صفحه برخورد خواهید کرد.
زمانی که یک وبسایت را در مرورگر باز میکنید، از یک اسکریپت اجرایی جاوااسکریپت استفاده میکند. این رشته مسئول رسیدگی به همه چیز، مانند scrolling صفحه وب، چاپ چیزی در صفحه وب، گوش دادن به رویدادهای DOM (مانند زمانی که کاربر روی دکمه کلیک میکند) و انجام کارهای دیگر است. اما وقتی اسکریپت اجرایی جاوااسکریپت مسدود میشود، مرورگر تمام آن کارها را متوقف میکند، که به این معنی است که مرورگر فریز ( freeze ) شده است و به هیچ چیز پاسخ نخواهد داد.
شما میتوانید این را در عمل با استفاده از حلقه while با کد زیر مشاهده کنید.
while(true){}
هیچ کدی بعد از اجرای کد بالا اجرا نمیشود زیرا در حلقه بینهایت گیر کرده است تا زمانی که مرورگر به طور کامل بسته شود. این همچنین میتواند زمان فراخوانی تابع بینهایت بازگشتی اتفاق بیفتد.
باید از مرورگرهای جدید تشکر کرد، چون همه تبهای مرورگر باز، روی یک رشته جاوااسکریپت تکیه نمیکنند. در عوض آنها از یک رشته جاوااسکریپت جداگانه در هر تب یا هر دامنه استفاده میکنند. در مورد گوگل کروم، شما میتوانید چندین تب را از وبسایتهای مختلف در حالیکه هر کدام حلقههای بینهایت را اجرا میکنند را باز کنید. این فقط تب فعلی را که در آن کد اجرا شد را مسدود میکند، اما سایر تبها به طور عادی کار می کنند. و همچنین اگر یک تب جدید از همان سایت مسدود شده دوباره باز شود آن را هم مسدود میکند.
برای فهمیدن نحوهی اجرای یک برنامه با جاوااسکریپت باید runtime جاوااسکریپت را بفهمیم.
مانند هر زبان برنامهنویسی دیگر، runtime جاوااسکریپت دارای یک پشته و یک فضای ذخیرهسازی است. من قصد ندارم چیزهای بیشتری راجع به heap توضیح دهم، برای اطلاعات بیشتر شما می توانید این مقاله را بخوانید. آنچه ما به آن علاقه داریم پشته است. Stack LIFO (آخرین ورود ، اولین خروج) . هنگامیکه برنامه ما در حافظه بارگذاری میشود، اجرای از اولین فراخوانی تابع که عبارت است foo() شروع میشود.
از این رو ، اولین ورود به پشته foo است. از آنجا که تابع foo تابع bar را صدا میکند، ورودی دوم پشته، bar است. از آنجا که تابع bar تابع baz را فراخوانی میکند، ورود پشته سوم baz است. و سرانجام، تابع baz، console.log را فراخوانی میکند، ورودی پشته چهارم console.log است ("Hello from baz ").
تا زمانیکه یک تابع چیزی را باز میگرداند (در حالیکه تابع اجرا میشود)، از پشته بیرون نخواهد آمد. به محض بازگرداندن مقداری از مقادیر، مقادیر یک به یک پشته میشوند، و اجرای آن در انتظار اجرای تابع ادامه مییابد.
در هر ورودی، حالت پشته نیز بهعنوان چارچوب پشته نامیده میشود. اگر هر فراخوانی تابع به یک چارچوب پشته دارای خطا باشد، جاوااسکریپت نحوهی اجرایی ( trace ) از پشته را چاپ میکند که چیزی جز شمایی ( snapshot ) از اجرای کد در آن قاب پشته نیست.
function baz(){
throw new Error('Something went wrong.');
}function bar() {
baz();
}function foo() {
bar();
}foo();
در برنامه فوق، ما خطا را از تابع baz بهدست آوردیم و جاوااسکریپت زیر نحوهی اجرای پشته را چاپ خواهد کرد تا بفهمد چه اشتباهی رخ داده است و در کجا قرار دارد.
از آنجا که جاوااسکریپت به صورت single threaded است، تنها یک heap و یک پشته دارد. از اینرو، اگر برنامه دیگری بخواهد چیزی را اجرا کند، باید صبر کرد تا برنامه قبلی به طور کامل اجرا شود.
این برای هر زبان برنامهنویسی بد است اما جاوااسکریپت طوری طراحی شده است که به عنوان زبان برنامهنویسی برای اهداف خاصی استفاده شود، نه برای موارد بسیار پیچیده.
پس اجازه دهید به یک سناریو فکر کنیم. اگر یک مرورگر درخواست HTTP را برای لود کردن برخی از دادهها بر روی شبکه و یا لود کردن یک تصویر برای نمایش در صفحه وب میفرستد، چه میشود؟ آیا تا زمانی که این درخواست انجام نشده باشد، مرورگر مسدود خواهد شد؟ اگر این اتفاق بیافتد، برای تجربه کاربری بسیار بد است.
مرورگر مجهز به موتور JavaScript است که محیطی برای اجرای جاوااسکریپت را فراهم میکند. به عنوان مثال، Google Chrome از موتور V8 JavaScript استفاده می کند، که توسط جاوااسکریپت توسعه داده شده است. اما حدس بزنید چرا مرورگر از بیشتر از یک موتور جاوااسکریپت استفاده میکند. این همان چیزی است که به نظر میرسد.
به نظر بسیار پیچیده میآید اما درک آن بسیار آسان است. زمان اجرای جاوااسکریپت در واقع شامل ۲ جز دیگر است که عبارتند از: حلقه رویداد و صف callback. صف Callback را نیز صف پیام یا صف وظیفه مینامند.
جدا از موتور جاوااسکریپت، مرورگر شامل برنامه های مختلفی است که می تواند موارد مختلفی از جمله ارسال درخواست HTTP، گوش دادن به رویدادهای DOM، تأخیر در اجرای با استفاده از setTimeout یا setInterval، ذخیره سازی، ذخیره سازی پایگاه داده و موارد دیگر را انجام دهد. این ویژگی های مرورگر به ما کمک می کند تا برنامه های وب غنی ایجاد کنیم.
اما در مورد این موضوع فکر کنید، اگر مرورگر مجبور بود از همان رشتههای جاوااسکریپت برای اجرای این ویژگیها استفاده کند، پس واقعا تجربه کاربری وحشتناک میشود. چون حتی زمانی که کاربر فقط دارد صفحه وب را مرور میکند، چیزهای زیادی در پسزمینه انجام میشوند. در نتیجه، مرورگر از زبان سطح پایین مثل ++C برای انجام این عملیاتها استفاده میکند و API JavaScript برای کار با آن را توسعه میدهند. این API ها به API Web معروف هستند.
این API های وب ناهمزمان هستند. این بدان معناست که شما میتوانید به این API ها دستور دهید که کاری را در پس زمینه انجام دهند و داده ها را پس از انجام کار بازگردانند، در عین حال می توانیم کدهای جاوااسکریپت دیگری را اجرا کنیم. در حالیکه به این API ها دستور میدهند کاری را در پسزمینه انجام دهند، ما باید یک تابع callback را آماده کنیم. مسئولیت تابع callback این است که جاوااسکریپتی را اجرا کنند که Web API با آن کار میکند. بیایید درک کنیم که چطور همه قسمتها با هم کار میکنند.
دورههای آموزشی جاوااسکریپت در وبسایت راکت
پس وقتی که یک تابع را فراخوانی میکنید، به سمت پشته هل داده میشود. اگر این تابع شامل درخواست API وبی باشد، جاوااسکریپت کنترل عملکرد آن را با یک تابع بازگشتی به API وب واگذار میکند و تا زمانی که تابع چیزی را بازگرداند، به خطوط بعدی حرکت می کند. زمانی که یک تابع به یک statement برگشتی میخورد، این تابع از پشته بیرون میآید و به سمت ورودی پشته بعدی حرکت میکند. در همین حال، وب API کار خود را در پس زمینه انجام میدهد و به یاد میآورد که کدام تابع callback با آن کار مرتبط است. هنگامی که کار انجام شد، API نتایج آن کار را به تابع callback متصل میکند و پیامی را به صف با کمک callback ارسال میکند . تنها وظیفه حلقه رویداد این است که به صف callback نگاه کند و زمانی که چیزی در صف callback در انتظار است، آن را به پشته بفرستد( push کردن ). حلقه رویداد هر بار که پشته خالی شود، یک تابع callback را به طور همزمان، به پشته push میکند. بعداً، پشته تابع callback را اجرا میکند. بیایید ببینیم که چطور همه کارها با استفاده از API setTimeout وب مرحله به مرحله کار میکند. setTimeout Web API عمدتا برای اجرای چیزی پس از چند ثانیه استفاده میشود. این اجرا هنگامی اتفاق میافتد که تمام کدهای برنامه اجرا شوند (هنگامی که پشته خالی است). نحوهی عملکرد setTimeout به شرح زیر است.
setTimeout(callbackFunction, timeInMilliseconds);
callbackFunction یک تابع callback است که بعد از timeInMilliseconds اجرا میشود. بیایید برنامه قبلی خود را اصلاح کرده و از این API استفاده کنیم.
function printHello() {
console.log('Hello from baz');
}function baz() {
setTimeout(printHello, 3000);
}function bar() {
baz();
}function foo() {
bar();
}foo();
تنها اصلاح انجامشده در برنامه این است که ما اجرای console.log را ۳ ثانیه به تعویق انداختیم. در این حالت، پشته مانند foo() => bar() => baz() ادامه خواهد یافت. وقتی baz شروع به اجرای برنامه API setTimeout میکند، جاوااسکریپت callback را به API وبی منتقل میکند و به خط بعدی حرکت میکند. تا زمانی که، هیچ خط بعدی وجود نداشته باشد، پشته baz را pop میکند، سپس bar و سپس تابع foo فراخوانی میشود. در همین حال، API وب برای عبور، ۳ ثانیه منتظر است. پس از گذشت 3 ثانیه، این callback را به صف هدایت میکند و از آنجا که پشته خالی است، حلقه رویداد این callback را بر روی دسته قرار میدهد و این callback اجرا میشود.
فیلیپ رابرز یک ابزار آنلاین شگفتانگیز برای تجسم نحوه کار جاوااسکریپت در پس زمینه، ایجاد کرده است. مثال فوق ما در این لینک موجود است.
وقتی نوبت به Node.js میرسد، باید بیشتر این کار را انجام دهد چون node به وعدههای ( promise ) بیشتری عمل میکند. در مورد مرورگر، ما به آنچه که میتوانیم در پسزمینه انجام دهیم محدود هستیم. اما در node، ما میتوانیم بیشتر کارها را در پسزمینه انجام دهیم. اما این چطور کار میکند؟
Node.js از موتور V8 Google استفاده می کند تا جاوااسکریپت را اجرا کند، اما فقط به حلقه رویداد متکی نیست. از کتابخانه libuv (نوشته شده با c) برای کار در کنار حلقه رویداد V8 برای گسترش آنچه میتوان در پسزمینه انجام داد، استفاده میکند. node از همان روش پاسخگویی مانند Web API استفاده میکند و به روش مشابه مرورگر عمل میکند.
اگر دیاگرام مرورگر را با دیاگرام node فوق مقایسه کنید، میتوانید شباهتها را مشاهده کنید. کل بخش سمت راست به نظر میرسد مانند API وب باشد، اما همچنین شامل صف رویداد (صف callback / صف پیام) و حلقه رویداد است.
اما v8، صف رویداد و حلقه رویداد بر روی یک رشته واحد اجرا میشوند در حالی که رشتههای کارگر وظیفه ارائه I / O ناهمزمان را دارند. اما V8 ، صف وقایع و حلقه رویداد بر روی موضوعات واحد اجرا می شود در حالی که موضوعات کارگر وظیفه ارائه I / O ناهمزمان را دارند. به همین دلیل گفته میشود Node.js بهعنوان معماری non-blocking منجر به معماری I / O ناهمزمان شده است.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید