Mojtaba Pourkhanlar
About meProjectsBlog

  • 👤About me
  • 🧰Projects
  • ✍️Blog

JavaScript Question

Array

  • یک تابع بنویس که مجموع اعداد یک آرایه را برگرداند.
function sum(arr) {
  return arr.reduce((a, b) => a + b, 0);
}

Conditions and loops

  • عددی را بگیر و مشخص کن زوج است یا فرد.
  • با for و while یک مثال بزن.
function isEven(num) {
  return num % 2 === 0 ? "زوج" : "فرد";
}

مثال for

for (let i = 1; i <= 5; i++) {
  console.log(i);
}

مثال while

let i = 1;
while (i <= 5) {
  console.log(i);
  i++;
}

Map Vs ForEach Vs Filter

  • تفاوت map ، forEach و filter چیست؟

  • یک آرایه را بدون استفاده از reverse برعکس کن.

  • map: آرایه جدید برمی‌گرداند

  • forEach: فقط اجرا می‌شود

  • filter: آرایه فیلترشده برمی‌گرداند

const nums = [1, 2, 3, 4]
;
nums.map(n => n * 2);
nums.filter(n => n > 2);
nums.forEach(n => console.log(n));

برعکس کردن آرایه بدون reverse

function reverseArray(arr) {
  const result = []
;
  for (let i = arr.length - 1; i >= 0; i--) {
    result.push(arr[i]
);
  }
  return result;
}

How to empty an array?

  • فرض کنید یه ارایه داریم:
let arr = [1, 2, 3]
;
  • چه‌طوری می‌توینم این ارایه رو خالی کنیم؟

  • به چند روش میشه ارایه رو خالی کرد که باید براساس شرایط تصمیم گرفت از کدومش استفاده کنیم:

  • روش اول: ارایه رو به مقدار [] مقداردهی (assign) کنیم.

arr = []
;
  • روش سریعیه ولی باید بدانیم که ما با assignکردن ارایه arr یعنی داریم مقدار جدیدی بهش میدیدم و اگر به arr اصلی یه ارایه دیگ بهش اشاره می‌کرد، اون ارایه دیگ، مقدارش خالی نمیشه:
let myArr = arr;
arr = []
;
console.log(myArr); //[1,2,3]
  • همانطور که می‌بینید ارایه myArr درست‌نخورده می‌مونه!
  • روش دوم: طول ارایه رو برابر صفر قرار بدیم.
arr.length = 0;
  • مقدار طول یا lengthِ هم قابلیت خوندن داره و هم میتونیم تغییرش بدیم. زمانی که طول ارایه رو برابر صفر قرار می‌دیم، تمام المنت‌های درون ارایه رو حذف می‌کنیم. که این روش، برعکس روش قبلی، ریفرنس‌هایی که به ارایه اصلی می‌شد رو هم اپدیت می‌کنه و اون‌ها رو هم خالی می‌کنه.
let myArr = arr;
arr.length = 0;
console.log(myArr); //[]
  • روش سوم: استفاده از متد ()splice.
arr.splice(0, arr.length);
  • متد splice، کلا وظیفه‌اش تغییر دادن محتوای اصلی ارایه‌است و این کار رو با جایگزینی یا حذف محتوای ارایه‌ و یا اضافه‌کردن ایتم جدید انجام می‌ده. پیاده‌کردن این متد رو ارایه، ارایه‌مون تغییر می‌کنه.

  • اینجا داریم به متد splice می‌گیم که از ایندکس 0 شروع کن و به اندازه طول ارایه که در اینجا 3 هست، برو جلو، دونه‌دونه المنت‌ها رو حذف کن.

  • تو این روش هم، ریفرنس‌هایی که به ارایه اصلی‌ میشه، اپدیت میشن:

let myArr = arr;
arr.splice(0, arr.length);
console.log(myArr); //[]
  • روش سوم: استفاد از متد ()pop
  • متد pop کارش حذف المنت از اخر ارایه هست به این صورت که با هر بار پیاده‌کردنش روی یک ارایه، اخرین المنت از ارایه حذف می‌شه.
while (arr.length > 0) {
  arr.pop();
}
  • تو این روش هم، ریفرنس‌هایی که به ارایه اصلی‌ میشه، اپدیت میشن ولی نسبت به روش‌های قبل، کارایی کمتری داره.

Rest

به معماری Request و Response رست میگیم

اما وقتی از این معماری استفاده میکنیم و HTTP متد ها رو بکار می بریم میشه RestFull.


در جاوااسکریپت چجوری 5چند تا api رو همزمان در صفحه کال کنیم؟

وقتی می‌خوای چندتا درخواست رو به صورت همزمان بفرستی و منتظر بمونی همگی برگردن، Promise.all بهترین انتخابه.

const fetchApis = async () => {
  try {
    const [api1, api2, api3, api4, api5]
 = await Promise.all([
      fetch("https://api.example.com/one").then(res => res.json()),
      fetch("https://api.example.com/two").then(res => res.json()),
      fetch("https://api.example.com/three").then(res => res.json()),
      fetch("https://api.example.com/four").then(res => res.json()),
      fetch("https://api.example.com/five").then(res => res.json()),
    ]);

    console.log("نتایج هر ۵ تا API رسید:", { api1, api2, api3, api4, api5 });
  } catch (err) {
    console.error("یکی از APIها خرابکاری کرد", err);
  }
};

fetchApis();

در اگه یکی از APIها ارور بده → کلش می‌ره تو catch در واقع همه یا هیچ

اما اگر میخوای یکی خراب شد، بقیه اجرا بشن ؟

  • از Promise.allSettled استفاده کن:
const results = await Promise.allSettled([
  fetch("https://api.example.com/one").then(res => res.json()),
  fetch("https://api.example.com/two").then(res => res.json()),
  fetch("https://api.example.com/three").then(res => res.json()),
  fetch("https://api.example.com/four").then(res => res.json()),
  fetch("https://api.example.com/five").then(res => res.json()),
]);

چرا Arrow Function برای متدهای Object مناسب نیستند؟

چون this رو از محیط بیرونی به ارث میبره و به آبجکت bind نمیشه

آبجکت آرگیومنت نداره و نمیشه با new صدا زد.


Event Delegation

به‌جای اینکه به هر عنصر فرزند یک رویداد اضافه کنیم، میایم رویداد رو روی والد‌شون ثبت می‌کنیم و از خاصیت Bubble شدن استفاده می‌کنیم تا بفهمیم کدوم فرزند کلیک شده یا تعامل انجام داده.

مزایا

کم شدن تعداد Event Listenerها

  • دیگه لازم نیست برای هر دکمه یا آیتم لیسنر جداگانه بنویسی.

افزایش Performance

  • مرورگر کمتر Event Handler ذخیره می‌کنه، حافظه سبک‌تر می‌شه

کار کردن با عناصر جدید (Dynamic)

  • یعنی حتی اگه بعداً المنت جدیدی به DOM اضافه بشه، Event Delegation همچنان کار می‌کنه.

روش معمول (بدون delegation)

const items = document.querySelectorAll("li");

items.forEach(item => {
  item.addEventListener("click", () => {
    console.log(`Clicked on ${item.textContent}`);
  });
});
  • اینجا اگه 100 تا li داشته باشی، 100 تا event listener هم داری

روش درست با Event Delegation

const list = document.querySelector("ul");

list.addEventListener("click", event => {
  if (event.target.tagName === "LI") {
    console.log(`Clicked on ${event.target.textContent}`);
  }
});

اینجا فقط یک لیسنر روی والد (ul) داری، ولی می‌تونه بفهمه کدوم فرزند (li) کلیک شده!

کاری که جاوااسکریپت پشت‌صحنه انجام می‌ده اینه که Event از عنصر فرزند (LI) «bubble» می‌کنه تا برسه به UL و اونجا هندل بشه.


Tree Shaking

خب Tree Shaking یعنی حذف کدهای استفاده‌نشده توی bundle.

  • باعث کاهش حجم بلید
  • افزایش سرعت لود
  • افزایش performance

اما همیشه کار نمی‌کنه. دلایلی داره:

استفاده از CommonJS

require / module.exports

این سیستم static نیست و Webpack نمی‌تونه درست تحلیل کنه.

const module = require("./x");

استفادهٔ داینامیک از import

const name = "utils";
import(`./${name}.js`);

اینو دیگه هیچ bundler نمی‌فهمه چی رو باید حذف کنه.

Side Effect داشتن فایل‌ها

اگر فایلی هنگام import شدن کار انجام بده:

console.log("I'm doing stuff!");

بنابراین حذفش خطرناک می‌شه → Bundler جرأت نمی‌کنه پاکش کنه.

و export نکردن به شکل “named”

export default function () {}

پس default export ها سخت‌تر optimize می‌شن نسبت به named export.

استفاده از inline require یا تابع‌های runtime

const mod = require(getModuleName());

یا کدی که bundler نتونه statically پیش‌بینی کنه.

تنظیمات اشتباه در bundler

  • mode = production نباشه
  • treeShaking خاموش
  • usedExports خاموش
  • sideEffects: false درست کانفیگ نشده

Memory Leak

زمانی رخ می‌دهد که حافظه‌ای که دیگر استفاده نمی‌شود آزاد نشود.

Memory Leak های رایج

  • Global Variables
  • Event Listener بدون Cleanup
  • setInterval بدون clear
  • Closure های غیرضروری

Virtualization

اگه 10 هزار آیتم داری، لازم نیست 10 هزار تا DOM بسازی. فقط اونایی رو که کاربر می‌بینه بساز.

نمونهٔ معروف؟ React Window، React Virtualized.

ایدهٔ اصلی:

  • Viewport رو می‌گیری
  • فقط المان‌های لازم رو render می‌کنی
  • بقیه‌ش تظاهر می‌کنه هست، ولی نیست
const container = document.querySelector("#list");
const items = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);

const itemHeight = 30;
const containerHeight = 300;

container.style.height = containerHeight + "px";
container.style.overflow = "auto";

container.addEventListener("scroll", () => {
  const start = Math.floor(container.scrollTop / itemHeight);
  const end = start + Math.floor(containerHeight / itemHeight);

  const visibleItems = items.slice(start, end);

  container.innerHTML = visibleItems
    .map(item => `<div style="height:${itemHeight}px">${item}</div>`)
    .join("");
});

// Trigger first render
container.dispatchEvent(new Event("scroll"));

DOM (Document Object Model)

یه مدل درختی از کل صفحه HTML که جاوااسکریپت می‌تونه بهش دست بزنه، تغییرش بده، نابودش کنه، یا حتی موجودات جدید بسازه بنداز توش

چرا Dom مهمه؟

چون جاوااسکریپت نمی‌تونه مستقیم HTML رو ببینه

باید یه نسخه تبدیل‌شده و قابل‌فهم داشته باشه → همون DOM.

این یعنی:

  • تغییر استایل
  • اضافه کردن/حذف کردن عناصر
  • واکنش به کلیک، اسکرول، کیبورد
  • ساخت رندر داینامیک مثل SPAها
  • کنترل Formها
  • و کلی کار خفن دیگه

همش توسط DOM انجام میشه.

روش دسترسی به دام:

document.getElementById("idName");
document.getElementsByClassName("className");
document.getElementsByTagName("div");
document.querySelector(".myClass");
document.querySelectorAll(".item[data-active]
");

Stack Vs Heap

خب Stack و Heap دو تا مدل انبار هستن که JavaScript (و همه زبان‌ها) برای مدیریت حافظه استفاده می‌کنن.

Stack

محل ذخیرهٔ چیزای کوچیک، سریع و قشنگ‌تر، مثل میز کار مرتب یک مهندس وسواسی.

ویژگی‌ها:

  • ذخیره: مقادیر ساده primitive type مثل number, string, boolean, null, undefined
  • ساختار: LIFO (آخرین وارد، اولین خارج)
  • سرعت: فوق‌العاده سریع
  • مدیریت حافظه: اتوماتیک
  • اندازه: محدود و کوچک
  • رفتار: هر فانکشن که کال میشه، یه “stack frame” جدید ایجاد میشه و متغیرها داخلش ذخیره می‌شن
let a = 10;
let b = "Mojtaba";

در اینجا a و b مستقیم داخل stack ذخیره می‌شن.

چرا؟

چون primitive هستن و اندازه‌شون معلومه → سریع قابل مدیریت هستن.

Heap

محل ذخیرهٔ چیزهای بزرگ، نامرتب و پر از سر و صدا

ویژگی‌ها:

  • ذخیره: object, array, function
  • ساختار: نامنظم، بدون ترتیب (unstructured memory)
  • سرعت: کندتر نسبت به stack
  • اندازه: بزرگ‌تر
  • رفتار: فقط reference‌ها داخل stack ذخیره می‌شن؛ خود object توی heap هست
const user = { name: "Mojtaba", level: "Front-end Developer" };

خب user خودش داخل stack ذخیره میشه

ولی مقدارش (اون object خوشگل) توی heap ساخته میشه.


Event Loop

در جاوااسکریپت، Event Loop چند نوع صف مهم داره که اولویت اجرای کدها رو کنترل می‌کنه.

Microtask Queue :

  • مهم‌ترین و سریع‌ترین صفه. Promiseها و queueMicrotask اینجا اجرا می‌شن.

  • همیشه بعد از کدهای sync و قبل از هر نوع task دیگه اجرا میشه.

Macro Task Queue (یا Task Queue) :

  • صف کارهای بزرگ‌تر مثل setTimeout، setInterval و I/O.

  • بعد از microtaskها پردازش میشه.

Animation Frames Queue :

  • مربوط به requestAnimationFrame

  • درست قبل از مرحله رندر اجرا میشه تا انیمیشن‌ها روان باشن

Render Queue (Update the Rendering / DOM Paint) :

  • مرورگر در این مرحله layout، paint و composite رو انجام میده.

  • این یک فاز مهم بین animation frame و نمایش فریم هست.

Idle Callback Queue :

  • مخصوص requestIdleCallback

  • فقط وقتی مرورگر وقت خالی داره اجرا میشه. برای کارهای low-priority.

Message Channel Task Queue :

  • پیام‌های MessageChannel اینجا قرار می‌گیرن.

  • سریع‌تر از setTimeout ولی هنوز بعد از microtask اجرا میشه.


به طور کلی، ترتیب اجرای چرخه معمولاً اینه:

Sync → Microtasks → Animation Frame → Render → Macrotasks → Idle.»


Microtask Queue

این مهم‌ترین و سریع‌ترین صفه.

هرچی داخلش قرار بگیره قبل از هر کار دیگری اجرا میشه.

چیا میرن microtask؟

  • Promise.then
  • Promise.catch
  • Promise.finally
  • MutationObserver
console.log("A");

Promise.resolve().then(() => console.log("Microtask"));

console.log("B");

خروجی:

A

B

Microtask

چرا؟

چون همیشه microtask بعد از اجرای sync و قبل از task اجرا میشه.

Macro Task Queue (Task Queue)

همه چیزهایی که دیرتر اجرا می‌شن، میان اینجا.

این همون چیزیه که معمولاً بهش می‌گیم “Callback Queue”.

چیا میان اینجا؟

  • setTimeout
  • setInterval
  • setImmediate
  • I/O callbacks
  • Web APIs مانند fetch response (ولی قبلش microtask هست)
setTimeout(() => console.log("macrotask"), 0);

پس macrotask همیشه بعد از microtask اجرا میشن.

Animation Frames Queue (requestAnimationFrame)

قبل از اینکه مرورگر بخواد فریم بعدی رو رندر کنه، requestAnimationFrame اجرا میشه.

این یعنی:

«اگه میخوای با انیمیشن و فریمینگ بازی کنی، بیا اول منو خبر کن!»

requestAnimationFrame(() => {
  console.log("Animation frame");
});

این صف دقیقاً قبل از مرحله‌ی رندر اجرا میشه.

Render Queue (Updating the Rendering Pipeline)

این صف واقعی یک “صف” مثل بقیه نیست، بلکه یک فاز در چرخه مرورگر است.

در این مرحله مرورگر می‌گوید:

«خب حالا DOM تغییر کرده؟ باید layout بزنم؟ repaint کنم؟ composite کنم؟»

مرحله Render شامل:

  • Style recalculation
  • Layout
  • Paint
  • Composite

این بخش بین Animation Frame و نمایش فریم اجرا می‌شود.

شبیه یک آشپز هست که قبل از سرو غذا ظاهرش رو مرتب می‌کنه

Idle Callback Queue

مربوط به:

requestIdleCallback

وقتی مرورگر کاری نداشته باشه و در حالت idle باشه، می‌گه:

«خب، کی چیزی داره که فوری نیست؟ بیاره من انجام بدم.»

مناسب برای:

  • کارهای کم‌اهمیت
  • analytics
  • pre-caching
  • low-priority calculations
requestIdleCallback(() => {
  console.log("Idle time task");
});

اگه CPU بیچاره شدیداً درگیر باشه، ممکنه خیلی دیر اجرا بشه!

Message Channel Task Queue

خب MessageChannel یک API هست که دو پورت پیام‌بر دارد و پیام‌ها سریع اما در macrotask سطح بالا اجرا می‌شوند.

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

const channel = new MessageChannel();
channel.port1.onmessage = () => console.log("MessageChannel");
channel.port2.postMessage("GO!");

این روش خیلی برای ساختن polyfillهای سریع استفاده می‌شود.

چرا JS، Single-Thread هستش؟

چون برای مدل اجرا در مرورگر طراحی شد تا روی مرورگر اجرا بشه و با DOM کار کنه و رفتار برنامه‌ها رو کنترل کنه

دام یه ساختار درختی هست که استیت داره و تغییرات روش باید منظم و غیرقابل پیش بینی نبودن باشن.

حالا تصور کن دو تا thread همزمان بخوان اجرا شن

  • یکی div رو حذف کنه
  • یکی اون div رو استایل بده

مرورگر رسماً منفجر می‌شد

پس جاواسکریپت single-thread هستش تا دام Thread-safe بمونه

نکته: جاوا اسکریپت single-thread هستش امامرورگر و node.js single-thread نیستن

چیزی که single-thread هست

  • Call Stack

چیزیکه multi-thread هست:

  • Web APIs
  • ThreadPool (در Node.js)
  • callback
  • Timerها
  • fetch
  • file system
  • crypto
  • image decoding
  • network stack

این یعنی جاوااسکریپت خودش single-thread اجرا می‌شه،

اما کارهای سنگین رو می‌سپره به پشت‌صحنه که multi‑thread هست.

بعد از اتمام کار، نتیجه رو با Event Loop برمی‌گردونه.

یه جورایی جاوااسکریپت یه مدیر حواس‌پرت ولی باهوشه:

کار سخت: بده دست بقیه

کار آسون: خودش انجام بده

نتیجه: برنامه سریع و responsive

Call Stack چیه و چرا single‑thread حساب می‌شه؟

خب Call Stack درواقع اون جاییه که جاوااسکریپت دستورهایی که باید الان و به ترتیب اجرا بشن رو نگه می‌داره.

وقتی JS یه تابع رو صدا می‌زنه، اون تابع می‌ره بالای Stack:

function a() {
  console.log("a start");
  b();
  console.log("a end");
}

function b() {
  console.log("b");
}

a();
زمان Call Stack اتفاق
شروع -- خالیه
اجرای a() [a]
    | تابع a روی Stack میره         |

| اجرای b() | [a,b] | تابع b هم بالای a قرار میگیره | | پایان b | [a] | حذف میشه | | پایان a | [] | Stack دوباره خالی میشه |

یعنی فقط یه مسیر اجرایی وجود داره.

جاوااسکریپت هیچ‌وقت نمی‌تونه همزمان دو تا Stack‌ رو اجرا کنه، چون فقط یه Thread داره که Stack‌ رو یکی یکی می‌خونه و اجرا می‌کنه.

Single Thread یعنی چی در اینجا؟

یعنی در هر لحظه، فقط یک دستور از Call Stack می‌تونه در حال اجرا باشه.

نه بیشتر، نه کمتر.

پس اگه یه تابع سنگین بنویسی که CPU رو درگیر کنه، چون فقط یه Thread داریم، بقیه کدها باید صبر کنن تا پشته خالی بشه (یعنی اصطلاحاً UI فریز می‌کنه ).


چرا جاوااسکریپت نیاز به async داره؟

جاوااسکریپت Single‑thread هستش.

یعنی فقط یه دونه Call Stack داره و همه‌چیز پشت سر هم اجرا می‌شه.

حالا تصور کن:

  • درخواست شبکه (fetch)
  • تایمرها (setTimeout)
  • آپلود/دانلود فایل
  • خواندن فایل از دیسک
  • رمزنگاری و پردازش سنگین CPU
  • کوئری دیتابیس در Node.js

اگه اینا Synchronous بودن، UI یا Event Loop رسماً می‌مردن

کاربر هم کلیک می‌کرد، جواب نمی‌گرفت، مرورگر فریز می‌کرد.

پس چرا async داریم؟

برای اینکه جاوااسکریپت:

  • قفل نشه ✔️
  • و UI responsive بمونه ✔️
  • عملیات کند رو بده به Web API / ThreadPool ✔️
  • وقتی کار تموم شد، نتیجه رو بفرستن توی Event Loop ✔️

پس async یعنی

این کار زمان‌بره، نذار بقیه کدها منتظر بمونن، برو انجامش بده، بعداً بیا نتیجه رو تحویل بده.

مثل اینکه به شاگرد آشپزت بگی:

این سیب‌زمینی‌ها رو پوست بگیر، من دارم بقیهٔ غذا رو آماده می‌کنم.

وقتی تموم شد صِدام کن!


Web Worker

خب Web Worker یعنی ایجاد یک «نخ جدا» در مرورگر برای اجرای کد جاوااسکریپت خارج از Call Stack اصلی.

در اصل: تو می‌تونی یه فایل JS جدا رو در یک Thread جدا اجرا کنی،

بدون اینکه Event Loop اصلی (و UI) قفل بشه.

  • نمی‌تونه به DOM دست بزنه ❌

    • چون اون Thread جدا به document و window اصلی دسترسی نداره.
  • با Main Thread از طریق پیام (message passing) حرف می‌زنه

    • داده‌ها بینشون با event ارسال می‌شن (خیلی شبیه chat بین Worker و مرورگره 😄).
  • برای کارای CPU‑سنگین عالیه

    • مثل پردازش داده، رمزنگاری، resize عکس، تحلیل JSONهای بزرگ.

یه مثال واقعی:

// main.js
const worker = new Worker("worker.js");

worker.postMessage({ numbers: [1, 2, 3, 4, 5]
 });

worker.onmessage = e => {
  console.log("Result from worker:", e.data);
};

و در فایل worker.js:

onmessage = event => {
  const result = event.data.numbers.reduce((a, b) => a + b, 0);
  postMessage(result);
};

اینجا:

  • خب Main Thread کاربر و UI رو نگه می‌داره
  • و Worker داره جمع اعداد رو حساب می‌کنه تو یه Thread دیگه
  • بعدش نتیجه رو برمی‌گردونه با postMessage()

هیچ‌کدوم مزاحم هم نمی‌شن!


تصور کن JS یه آشپز تنهاست

و Call Stack یعنی آشپز خودش پشت سر هم غذا درست می‌کنه.

و Web Worker یعنی یه شاگرد جدید میاد تو آشپزخونه نمی‌تونه مستقیماً سراغ اجاق آشپز بره،ولی می‌تونه دستور بگیره، یه خوراک درست کنه و نتیجه رو بده دستش!


نکته

در Node.js هم یه نسخهٔ مشابه داریم به نام Worker Threads

که توی backend همین کار رو می‌کنن، بدون قفل کردن Event Loop اصلی Node.