درود دوستان. هشتمین و آخرین قسمت از مجموعه پست‌های سوالات مصاحبه فرانت‌اند رو می‌خوایم بررسی کنیم و با سوال‌های زیر آشنا می‌شیم:

۷۱. از gzip چی می‌دونید؟
۷۲. منظور از Transitive Dependency توی فایل package.json چیه؟
۷۳. چه چالش‌هایی برای یک توسعه‌دهندهٔ سینیور وجود داره؟
۷۴. منظور و هدف از Semantic HTML چیه؟
۷۵. توی تایپ‌اسکریپت تایپ‌های void و never چه فرقی باهم دارن؟
۷۶. چه زمانی از State management توی برنامه‌ها استفاده کنیم؟
۷۷. چه تکنیک‌هایی برای نمایش بهینهٔ تصاویر توی صفحهٔ وب می‌شناسید؟
۷۸. Debounce و Throttle چه فرقی با هم دارن؟
۷۹. چرا پیشنهاد میشه از await توی حلقه‌ها استفاده نکنیم؟
۸۰. چرا اجرای کد زیر هیچوقت به پایان نمیرسه؟

 

قبل از شروع، دوست دارم این پست رو تقدیم می‌کنم به عزیز. امیدوارم روحش شاد باشه و در آرامش ✨💚

بریم که سوالات رو بررسی کنیم.

 

 

۷۱. از gzip چی می‌دونید؟

یکی از مهمترین تکنیک‌های افزایش سرعت برنامه‌های وب استفاده از gzip هست. gzip یک الگوریتم و قابلیت هست که باید توی سرور فعالسازی و کانفیگ بشه و هدف اون فشرده‌سازی و کاهش دادن اندازهٔ فایل‌هایی هست که از سرور به سمت کلاینت فرستاده میشن. به‌طوری که حجم یک فایل می‌تونه تا ٪۹۰ کاهش پیدا کنه که در نتیجه معیارهایی مثل سرعت، UX و SEO برنامهٔ ما بهبود پیدا می‌کنه. gzip معمولاً به صورت پیشفرض روی همهٔ سرورها فعال هست و همچنین تقریباً همهٔ مرورگرها از اون به‌طور خودکار پشتیبانی می‌کنن.

 

۷۲. منظور از Transitive Dependency توی فایل package.json چیه؟

فرض کنیم قصد داریم پکیج A رو توسط دستور npm install به برنامه اضافه کنیم. فرض کنیم پکیج A توی خودش وابسته به پکیج B هست. این یعنی توسعه‌دهندهٔ پکیج A، پکیج B رو توی قسمت dependencies فایل package.json پکیج A قرار داده. حالا فرض کنیم پکیج B هم وابسته به یک پکیج دیگه به اسم C هست. پس پکیج A بطور غیر مستقیم به پکیج C هم وابستگی داره. به نحوه وابستگی پکیج A به C، میگن وابستگی Transitive. یعنی پکیج A برای فعالیتش نیاز به حضور پکیج C داره:

A  ->  B  ->  C  ->  D ...

پس به وابستگی‌های غیر مستقیم می‌گیم Transitive Dependency.

وقتی توی مسیری که فایل package.json وجود داره، دستور npm install رو بزنیم، همه وابستگی‌های Transitive هم نصب و به پوشهٔ node_modules اضافه میشن. دقت کنین که وابستگی‌های Transitive باید توی قسمت dependencies نوشته شده باشن. وابستگی‌های Transitive که توی devDependencies لیست شدن نصب نخواهند شد.

 

۷۳. چه چالش‌هایی برای یک توسعه‌دهندهٔ سینیور وجود داره؟

یک توسعه‌دهندهٔ سینیور بودن، چیزی فراتر از مهارت‌های فنی و هارد اسکیل‌ها هست و معمولاً یک سینیور واقعی/غیر واقعی رو میشه بدون نگاه کردن به کدها تشخیص داد. به بیان ساده‌تر، اگر بهترین کدنویس دنیا باشیم ولی بعضی از مهارت‌ها رو نداشته باشیم، نمی‌تونیم بگیم سینیور هستیم. این مهارت‌ها شامل:

۱. نقش رهبری و مدیریت تیم: به عنوان یک توسعه‌دهندهٔ سینیور همیشه باید مایل باشیم به درستی و به شکل ساده و قابل فهم به سوالات افراد تازه‌کار جواب بدیم و در برابر اونها صبور و مشخص‌کنندهٔ مسیر باشیم.

۲. مدیریت پروژه: باید مایل باشیم که توی تصمیم‌گیری‌ها شرکت کنیم و نظرمون رو ارائه بدیم. این تصمیم‌گیری‌‌ها می‌تونه شامل مباحثی مثل انتخاب تکنولوژی و ابزارها باشه و یا مباحث عمومی‌تر مثل برنامه‌ریزی و تقسیم‌بندی کردن کارها.

۳. شناخت نیازهای مشتری: در اکثر موارد مشتری کوچیک‌ترین اطلاعاتی از کاری که انجام می‌دیم و خروجی‌ای که قراره تحویل بدیم نداره و یک توسعه‌دهندهٔ سینیور خوب باید بتونه نیازهای گفته‌شده و ناگفتهٔ مشتری رو شناسایی کنه.

۴. مهارت‌های فنی همیشه به‌روز: برای اینکه بتونیم توی تصمیم‌گیری‌ها شرکت کنیم، به افراد تازه‌کار راهنمایی بدیم و نیازهای مشتری رو به درستی شناسایی کنیم، داشتن اطلاعات به‌روز و آشنایی با جدیدترین تکنولوژی‌ها، ابزارها و تکنیک‌های برنامه‌نویسی، یکی از مهمترین ملاک‌های یک توسعه‌دهندهٔ سینیور به حساب میاد.

۵. تعادل کار و زندگی: همهٔ ما توی برهه‌ای از زندگی ممکنه دچار افراط و تفریط بشیم و هیچ خط وسطی وجود نداره. ولی تلاش برای حفظ و بازگشتن به نقطهٔ تعادل یکی از مهمترین کارهایی هست که باید آگاهانه انجامش بدیم. در غیر این صورت دچار فروپاشی خواهیم شد.

 

۷۴. منظور و هدف از Semantic HTML چیه؟

Semantic HTML یک روش نوشتن کدهای HTML با استفاده از تگ‌هایی مثل main و footer و header هست و هدف اون داشتن کدهایی هست که هم برای توسعه‌دهنده‌ها و هم برای ابزارهای تفسیرکنندهٔ کدها خواناتر و معنادارتر باشه.

توی روش‌های قدیمی رایج بود که از یک المنت نامربوط برای انجام بک کار استفاده بشه. مثلاً استفاده از المنت table برای قسمت‌بندی صفحه. اما این کار باعث سردرگمی توسعه‌دهنده‌ها و ابزارهایی مثل مرورگرها می‌شد. به همین دلیل المنت‌هایی با معنا معرفی شدن که به این المنت‌ها گفته میشه Semantic Elements.

توی Semantic HTML هر المنتی یک معنی اختصاصی داره. مثلاً تگ‌های h1 تا h6 که هر کدوم وظیفهٔ مشخصی دارن و باید توی جای درست ازشون استفاده بشه. اگه قصد داریم صفحه‌ای تمیزتر، خواناتر، Accessible و با سئوی بهتر داشته باشیم، یکی از مهمترین راه‌ها استفاده از Semantic HTML هست.

 

۷۵. توی تایپ‌اسکریپت تایپ‌های void و never چه فرقی باهم دارن؟

وقتی تابعی رو می‌بینیم که خروجی اون void هست، به این معنیه که اون تابع چیزی رو ریترن نمی‌کنه. بنابراین اگه تابعی داریم که هیچ چیزی ریترن نمی‌کنه، برای نوع خروجی اون از تایپ void استفاده می‌کنیم. اما بعضی توابع ممکنه اصلاً به مرحلهٔ ریترن کردن نرسن و کدهای توی این تابع هیچوقت به طول کامل - از ابتدا تا انتها - اجرا نشن. بنابراین از تایپ never برای تابعی استفاده می‌کنیم که حدس می‌زنیم به مرحلهٔ ریترن کردن نرسه.

جزییات بیشتر و مثال‌ها:

 

۷۶. چه زمانی از State management توی برنامه‌ها استفاده کنیم؟

با استفاده از ابزارهای State management مثل Redux و Pinia می‌تونیم راحت‌تر و منسجم‌تر اطلاعات و به قول معروف State های توی برنامه رو مدیریت کنیم.

State management کمک می‌کنه تا اطلاعات برنامه توی یک جای مشخص تعریف و متمرکز بشه و همچنین از راه‌های مشخص بشه به اونها دسترسی داشت و یا اونها رو تغییر داد. این کار باعث میشه اطلاعات امنیت بیشتری داشته باشن. همچنین باعث میشه برنامه‌ای خواناتر، ساده‌تر، Maintainable و Reusable داشته باشیم. اما State management ها مثل هر ابزار دیگه‌ای برای هر شرایطی مناسب نیستن و استفاده از اونها ممکن باعث پیچیدگی بیش‌از حد برنامه بشه. به طور کلی توی شرایط زیر بهتره از State management ها استفاده نکنیم:

۱. یک برنامهٔ کوچیک. شاید این مهمترین دلیلی باشه که از State management استفاده نکنیم. اگه برنامه و اطلاعات ما بزرگ نیست بهتره دنبال راه‌های دیگه برای مدیریت اطلاعات باشیم. مثل استفاده از Context و React Query توی ری‌اکت.

۲. اطلاعات Local یک کامپوننت: اگه اطلاعات یک کامپوننت فقط مربوط به همون کامپوننت هست، باید توی همون کامپوننت نگهداری بشه.

 

۷۷. چه تکنیک‌هایی برای نمایش بهینهٔ تصاویر توی صفحهٔ وب می‌شناسید؟

یک تصویر ممکنه حجمی به اندازهٔ کل جاوااسکریپت و CSS برنامه داشته باشه. پس بهینه کردن تصاویر تاثیر زیادی توی سرعت و عملکرد یک برنامه داره. با استفاده از تکنیک‌های زیر می‌تونیم تصاویر رو توی صفحه به شکل بهینه‌تر نمایش بدیم:

۱. استفاده از فرمت مناسب مثل JPG و WebP که می‌تونه حجم عکس رو تا حد زیادی کاهش بده.

۲. فشرده‌سازی تصاویر: ابزارهایی مثل  وجود دارن که با فشرده‌سازی عکس‌ها کمک می‌کنن حجم عکس‌ها رو تا حد زیادی بدون افت کیفیت کاهش بدیم.

۳. تصاویر Responsive: اگه برای اندازه‌های مختلف صفحه تصاویر مختلفی داریم، می‌تونیم از اتریبیوت srcset تگ img استفاده کنیم تا تصویر مناسبی رو برای اندازه‌های مختلف صفحه نمایش بدیم.

۴. استفاده از Lazy Loading: با استفاده از ابزارهای اختصاصی و همچنین اتریبیوت lazy روی تگ img می‌تونیم کاری کنیم که تصاویر فقط زمانی دانلود و نمایش داده بشن که توی محدودهٔ Viewport هستن.

۵. استفاده از CDN ها: CDN ها سرورهای اختصاصی، سریع و بهینه برای نگهداری و دانلود فایل‌های استاتیک مثل تصاویر و ویدئوها هستن.

۶. استفاده از Preload و Preconnect برای تصاویر

 

 

۷۸. Debounce و Throttle چه فرقی با هم دارن؟

وقتی بحث بهینگی و کارایی برنامه‌های فرانت‌اند میشه، دو تکنیک مهم ظاهر میشن: Throttle و Debounce. هر دو تکنیک شباهت‌های زیادی دارن و برای به تاخیر انداختن اجرای یک قطعه کد به کار میرن. به قول معروف برای Rate Limiting.

تابع Throttle مشخص میکنه که کدهای ما توی یک بازه زمانی مشخص فقط یک بار اجرا بشه. مثلاً می‌خوایم یک قطعه کد توی بازه زمانی ۱۰ ثانیه، حداکثر یک‌بار اجرا بشه. اینجا از تابع Throttle استفاده می‌کنیم.

با تابع Debounce می‌تونیم مطمئن بشیم که یک قطعه کد دوباره اجرا نمیشه مگر اینکه مقدار مشخصی از زمان گذشته باشه. معروف‌ترین مثال Debounce برای پیاده‌سازی جستجوی لحظه‌ای هست. ابتدا صبر می‌کنیم تا کاربر دست از تایپ کردن بکشه و بعد جستجو رو شروع می‌کنیم.

برای آشنایی کامل و نحوهٔ پیاده‌سازی این توابع توی جاوااسکریپت این پست رو ببینید:

 

 

۷۹. چرا پیشنهاد میشه از await توی حلقه‌ها استفاده نکنیم؟

شاید با چنین کدی مواجه شده باشیم:

for (const url of urls) {
  const response = <<await>> fetch(url);
}

اما این کد کاملاً غیر بهینه به حساب میاد. باید بدونیم که یکی از هدف‌های async/await پیاده‌سازی قابلیت پردازش موازی و مدیریت کردن عملیات ناهمگام هست. وقتی توی هر پیمایشِ حلقه از await استفاده می‌کنیم، یه جورایی مزیت‌های پردازش موازی رو نادیده گرفتیم. توی این شرایط، عملیات موازی نیست. بلکه متوالی هست. چونکه پیمایش بعدی باید صبر کنه تا عملیات ناهمگام فعلی تموم بشه. پس بهتره کاری کنیم که عملیات ناهمگام بصورت موازی اجرا بشن.

بهتره توی حقله منتظر نتیجهٔ عملیات ناهمگام نباشیم. تک تک این عملیات رو توی یک آرایه قرار بدیم و نهایتاً بیرون از حلقه از ()Promise.all استفاده کنیم تا از مزیت پردازش‌های موازی استفاده کرده باشیم:

(async () => {
  const responses = [];

  for (const url of urls) {
    const response = fetch(url);
    responses.push(response);
  }

  await Promise.all(responses);
})();

 

۸۰. چرا اجرای کد زیر هیچوقت به پایان نمیرسه؟

let x = true;

setTimeout(() => {
  x = false;
}, 0);

while (x === true) {
  console.log(`(ツ)`);
}

توی این کد باید بدونیم که حلقهٔ while هیچوقت به پایان نمی‌رسه. به این دلیل که هیچوقت نوبت به اجرای setTimeout نمی‌رسه که x رو false کنه. به بیان تخصصی‌تر، setTimeout یک Web API هست و اولویت پایین‌تری نسبت به کدهای داخلی زبان جاوااسکریپت دارن و زمانی اجرا میشن که Call Stack خالی باشه. پس به دلیل اینکه حلقهٔ while همچنان با x = true در حال اجرا هست، هیچوقت کارش تموم نمیشه و در نتیجه Call stack هم خالی نمیشه تا تابعی که به setTimeout پاس داده شده رو اجرا کنه.

 

خب دوستان، این هم از آخرین قسمت. ۸۰ سوال رو با هم بررسی کردیم. امیدوارم استفاده کنین و توی مسیر موفقیت و خوشبختی باشین. روزتون خوش 😉✌️

https://maximorlov.com/linting-rules-for-asynchronous-code-in-javascript/#2-no-await-in-loop

https://eslint.org/docs/rules/no-await-in-loop