آموزش جاوا اسکریپت – جلسه ۱۸ – مفاهیم async و defer

در مقاله قبل درباره مفهوم promise در جاوا اسکریپت صحبت کردیم و call stack و message queue و job/micro-task queue و event loop را بطور عمیق و مفهومی بررسی کردیم. در این مقاله می خواهیم مبحث جلسه قبل را ادامه دهیم و درباره مفاهیم async-defer صحبت کنیم.
مشکلات اجرای دستورات بصورت همگام (sync):
در وب سایت های مدرن، غالبا حجم اسکریپت ها از HTML ها بیشتر است و پردازش آنها زمان بیشتری نیاز دارد. وقتی مرورگر در حال مرور و پردازش کدهای HTML صفحه وب است، پس از رسیدن به اسکریپت های javascript عملیات ایجاد DOM متوقف شده و پردازش و اجرای کدهای جاوا اسکریپت آغاز می شود. به همین ترتیب اگر جاوا اسکریپت در فایل های خارجی تعریف شده باشد، یعنی تگ زیر:
<script src="external_file.js"></script>
باز هم همین سناریو اجرا خواهد شد. یعنی مرورگر مراحل ساخت DOM را متوقف کرده و فایل JS موردنظر را دانلود، پردازش و اجرا می کند.
در اینجا به دو مشکل اساسی برخورد می کنیم:
- اسکریپت ها نمی توانند المان های پایین خود را ببینند. پس قادر نیستند آن ها را مدیریت کنند.
- اگر تعداد زیادی اسکریپت در بالای صفحه وب تعریف شده باشد و زمان زیادی طول بکشد اجرا شود، کاربر نمی تواند محتوای صفحه را مشاهده کند تا زمانیکه دانلود، پردازش و اجرای دستورات js به پایان برسد.
<p>...content before script...</p> <script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script> <!-- This isn't visible until the script loads --> <p>...content after script...</p>
حل مشکل بلاک شدن صفحه بصورت مقطعی:
مشکل بلاک شدن صفحه را می توان با تعریف اسکریپت ها در انتهای صفحه تا حدی حل کرد. در اینصورت اسکریپت ها می توانند المان های بالا سر خود را مشاهده کنند و همچنین اگر حجم اسکریپت ها بالا باشد، اجرای آنها باعث بلاک شدن رندر صفحه وب نخواهد شد.
اما این روش کامل و مناسبی برای حل مشکل مذکور نمی باشد. فرض کنید صفحه وب ما کد HTML مفصل و طولانی داشته باشد. مرورگر تنها زمانی می تواند متوجه اسکریپت ها و دانلود و اجرای آنها شود که کدهای HTML صفحه را کاملا دانلود و اجرا کرده باشد. که خود این مسئله باعث کند شدن لود صفحه وب خواهد شد.
حل مشکل بلاک شدن صفحه بصورت اساسی:
خوشبختانه با تعریف یکی از دو اتریبیوت async و defer برای تگ script می توان مشکل فوق را بخوبی حل کرد.
اتریبیوت defer:
Defer به مرورگر اعلام می کند که اسکریپت های javascript را در پشت صحنه لود کند و پس از لود کامل آنرا اجرا کند. بعنوان مثال:
<p>...content before script...</p> <script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script> <!-- visible immediately --> <p>...content after script...</p>
نکته 1: اسکریپت هایی که با اتریبیوت defer اجرا می شوند هیچگاه باعث بلاک شدن صفحه نمی شوند.
نکته 2: اسکریپت های defer همیشه زمانی اجرا می شوند که DOM آماده است. البته قبل از رویداد DOMContentLoaded اجرا می شود.
اسکریپت فوق به صورت زیر تقسیر می شود:
<p>...content before scripts...</p> <script> document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!")); // (2) </script> <script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script> <p>...content after scripts...</p>
در این مثال داریم:
- محتوای صفحه بلافاصله نمایش پیدا می کند.
- رویداد DOMContentLoaded منتظر اسکریپت defer شده می ماند. زمانی اجرا می شود که اسکریپت دوم بطور کامل لود و اجرا شده باشد.
- اسکریپت های defer همانند اسکریپت های نرمال به ترتیب اجرا می شوند و اگر اسکریپت اول حجیم باشد، مانند کد زیر، اجرای اسکریپت دوم معطل اجرای کد اول می ماند.
<script defer src="https://javascript.info/article/script-async-defer/long.js"></script> <script defer src="https://javascript.info/article/script-async-defer/small.js"></script>
نکته 1: فایل js کوچکتر قبل از فایل بزرگتر دانلود می شود اما بعد از آن اجرا خواهد شد. مرورگر ابتدا صفحه وب را برای یافتن اسکریپت ها اسکن می کند سپس برای افزایش کارایی (performance) این فایل ها را بطور موازی و همزمان دانلود می کند. در مثال فوق، دو فایل small.js و long.js بطور موازی دانلود می شوند و چون فایل small.js کوچکتر است قبل از long.js دانلود می شود اما از آنجائیکه تعریف اسکریپت های فوق بگونه ایست که به ترتیب اجرا می شوند، ابتدا long اجرا می شود سپس small.
نکته 2: اتریبیوت دیفر (defer) فقط برای اسکریپت های خارجی (external) قابل استفاده است. یعنی اگر تگ script شامل اتریبیوت src نباشد اتریبیوت defer کار نخواهد کرد.
اتریبیوت async:
Async اعلام می کند که اسکریپت موردنظر کاملا مجزا و مستقل است. مرورگر منتظر اسکریپت های async نمی ماند. صفحه وب بدون معطل شدن برای اجرای اسکریپت async لود می شود و به کاربر نمایش داده خواهد شد.
رویداد DOMContentLoaded و اسکریپت async منتظر یکدیگر نمی مانند. DOMContentLoaded ممکن است قبل از اسکریپت async اجرا شود و ممکن است بعد از async اجرا شود. سایر اسکریپت ها نیز منتظر اسکریپت های async نخواهند بود و async نیز منتظر اتمام اجرای سایر اسکریپت ها نمی مانند. اگر چندین اسکریپت async در صفحه داشته باشیم، به ترتیب اجرا نخواهند شد. بلکه هر اسکریپتی که اول دانلودش تمام شود شروع به اجرا می کند.
<p>...content before scripts...</p> <script> document.addEventListener('DOMContentLoaded', () => alert("DOM ready!")); </script> <script async src="https://javascript.info/article/script-async-defer/long.js"></script> <script async src="https://javascript.info/article/script-async-defer/small.js"></script> <p>...content after scripts...</p>
در این مثال لود صفحه توسط اسکریپت های async بلاک نمی شود و بلافاصله نمایش داده می شود. دو اسکریپت small و long به ترتیبی که تعریف شده اند اجرا نمی شود. بلکه فایلی که حجم کمتری دارد و زودتر لود می شود ابتدا اجرا می شود. یعنی small.js با اینکه بعد از long.js تعریف شده، بدلیل حجم کمتر و دانلود سریع تر، قبل از long اجرا خواهد شد.
یکی از کاربردهای اتریبیوت async برای اسکریپت های third-part است. منظور اسکریپت هایی است که از دامین دیگری در سایت لود می شود. مانند گوگل آنالیتیکس:
<!-- Google Analytics is usually added like this --> <script async src="https://google-analytics.com/analytics.js"></script>
در اینصورت اسکریپت های ما نیازی ندارند منتظر بود و اجرای اسکریپت های خارجی باشند.
اسکریپت های داینامیک:
ما همچنین می توانیم اسکریپت مورد نظر را بصورت داینامیک به صفحه وب اضافه کنیم.
let script = document.createElement('script'); script.src = "/article/script-async-defer/long.js"; document.body.append(script); // (*)
اسکریپتی که به این روش تعریف می شود به محض اینکه در صفحه append می شود اجرا می شود.
نکته: اسکریپت های داینامیک بطور پیش فرض بصورت async رفتار می کنند. یعنی:
- معطل اجرای هیچ فایلی نمی شوند. همینطور هیچ فایلی معطل آنها نخواهد شد.
- اسکریپتی که اول لود می شود اول هم اجرا می شود.
let script = document.createElement('script'); script.src = "/article/script-async-defer/long.js"; script.async = false; document.body.append(script);
در این مثال به این دلیل که مقدار async=false تعریف شده است، اجرای فایل های js به این صورت انجام می شود که فایلی که اول تعریف شده اول هم اجرا خواهد شد.
function loadScript(src) { let script = document.createElement('script'); script.src = src; script.async = false; document.body.append(script); } // long.js runs first because of async=false loadScript("/article/script-async-defer/long.js"); loadScript("/article/script-async-defer/small.js");
یعنی در این کد اول فایل long اجرا می شود سپس فایل small
دیدگاهتان را بنویسید