Mojtaba Pourkhanlar
About meProjectsBlog

  • 👤About me
  • 🧰Projects
  • ✍️Blog

JavaScript structure


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

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

Primitive Value Vs Reference Value

جاوااسکریپت دو مدل “نوع داده” داره، که رفتارهاشون زمین تا آسمون فرق می‌کنه.

Primitive Value

  • string

  • number

  • boolean

  • null

  • undefined

  • symbol

  • bigint

  • خب Immutable (واقعاً تغییر نمی‌کنن)

  • در Stack ذخیره می‌شن

  • با کپی شدن مقدارشون منتقل می‌شه

let a = 10;
let b = a;
b = 20;

console.log(a); // 10

چون b یک کپی مستقل از مقدار a گرفته.

Reference Value

  • object

  • array

  • function

  • خب Mutable (تغییرپذیر)

  • در Heap ذخیره می‌شن

  • متغیر فقط آدرس رو نگه می‌داره، نه داده رو

let obj1 = { name: "Mojtaba" };
let obj2 = obj1;

obj2.name = "Batman 😎";

console.log(obj1.name); // Batman

چون هر دو به یک آدرس اشاره می‌کنن.


var

  • خب Function Scope دارد
  • و همینطور Hoist می‌شود و مقدار اولیه‌اش undefined است
  • مستعد Bug (ترجیحاً استفاده نشود)

let

  • خب Block Scope دارد
  • و همینطور Hoist می‌شود اما قبل از تعریف قابل دسترسی نیست (TDZ)

const

  • خب Block Scope دارد
  • مقدار Reference قابل تغییر نیست
  • انتخاب پیش‌فرض برای متغیرها

Scope

  • ببین Scope مشخص می‌کند یک متغیر کجا قابل دسترسی است:

  • Global Scope

  • Function Scope

  • Block Scope

let x = 10;
if (true) {
  let x = 20;
  console.log(x); // 20
}
console.log(x); // 10

Hoisting

خب Hoisting یعنی انتقال Declaration‌ها به ابتدای Scope در زمان Compile.

  • Function Declaration → کامل Hoist می‌شود
  • var → Hoist با مقدار undefined
  • let / const → در Temporal Dead Zone
console.log(a); // undefined
var a = 10;

console.log(b); // ReferenceError
let b = 20;

Closure

  • در Closure یعنی تابع به متغیرهای Scope بیرونی خود دسترسی دارد حتی بعد از پایان اجرای آن Scope.
function counter() {
  let count = 0;
  return function () {
    count++;
    return count;
  };
}

const inc = counter();
inc(); // 1
inc(); // 2

کاربردها:

  • Data Privacy
  • Function Factory
  • Memoization
  • React Hooks

اما Closure فقط «دسترسی به Scope بیرونی» نیست؛

  • بلکه نگه داشتن Reference در Heap است.
function heavy() {
  const bigData = new Array(1000000).fill("🔥");
  return () => bigData.length;
}
  • حواست باشه Closureهای بزرگ === Memory Leak بالقوه

  • راه‌حل:

  • Cleanup و Scope Control


?=

قبلاً اگر می خواستيم از fetch يا هر تابع async استفاده كنيم، مجبور بوديم به شكل زير بنويسيم. تعداد بلاک هاى try-catch زياد بود وخوانايى كم.

// BeFore
try {
  const res = await fetch("https://api.aban.dev");
  const data = await res.json();
  console.log(data);
} catch (err) {
  console.error("خطا رخ داد:", err);
}

// After
const [err, res]
 ?= await fetch('https://api.aban.dev');
if (err) {
  console.error('Error:', err);
} else {
  const data = await res. json( );
  console.log('Result:', data);
}

// Not Err Handler
async function getData() {
  const [, data]
 ?=
  await fetch('https://api.aban.dev');
  return data;
}

== and ===

  • == → loose equality: فقط مقدار را مقایسه می‌کند (type coercion انجام می‌دهد).
  • === → strict equality: مقدار و نوع داده هر دو باید برابر باشند.
0 == "0"; // true
0 === "0"; // false

null and undefined

  • undefined → مقدار پیش‌فرض (سیستم)
  • null → نبود مقدار (تصمیم برنامه‌نویس)

تفاوت || با ؟؟

عملگر || (Logical OR)

وقتی می‌گی A || B در واقع داری می‌گی:

«اگه سمت چپ (A) Falsy بود، دومی (B) رو برگردون.»

مشکل کجاست؟ در جاوااسکریپت، این مقادیر Falsy هستن:

  • false
  • 0
  • "" (رشته خالی)
  • null
  • undefined
  • NaN

فرض کن داری تعداد لایک‌های یک پست رو نشون می‌دی. اگه لایک صفر باشه، 0 یک مقدار معتبره، ولی || اون رو Falsy می‌بینه و می‌پره روی مقدار پیش‌فرض!

let likes = 0;
let displayLikes = likes || 10;

console.log(displayLikes); // خروجی: 10

عملگر ?? (Nullish Coalescing)

این عملگر که در ES2020 معرفی شد، خیلی دقیق‌تر و باکلاس‌تر عمل می‌کنه. وقتی می‌گی A ?? B داری می‌گی:

«فقط و فقط اگه سمت چپ (A) برابر با null یا undefined بود، دومی (B) رو برگردون.»

این یعنی ?? با بقیه مقادیر Falsy (مثل 0 یا "" یا false) کاری نداره و اون‌ها رو به عنوان یک مقدار معتبر قبول می‌کنه.

let likes = 0;
let displayLikes = likes ?? 10;

console.log(displayLikes); // خروجی: 0

Shallow , Deep , Structured Clone Copy

Shallow Copy

  • فقط Reference را کپی می‌کند
  • سريع وبراى داده هاى ساده مناسب است.

Deep Copy

  • براى كپى مستقل از دادههاى بيچيده ضروری است
  • تمام لایه‌ها را کپی می‌کند

Structured Clone

  • بهترين انتخاب براى داده هاى بيچيده و ساختارهاى پيشرفته در جاوا اسكريپت مدرن و مروركر هاى جديد .
const obj = { a: 1, b: { c: 2 } };

const shallow = { ...obj };
const deep = JSON.parse(JSON.stringify(obj));

shallow.b.c = 5;

console.log(obj.b.c); // 5

// Structured Clone
let original = {
  name: "Ali",
  date: new Date(),
  map: new Map([["key", "value"]
]),
};

let cloned = structuredClone(original);

console.log(cloned); // يك كيى عميق از original
console.log(cloned.date === original.date); // false
console.log(cloned.map === original.map); // false

Synchronous Vs Asynchronous

کد همزمان (Sync)

  • کد خط به خط اجرا می‌شه.

  • هر خط باید کامل بشه بعد خط بعد اجرا می‌شه.

  • مثل صف نونوایی:

  • تا نفر جلو نون نگیره، تو جلو نمی‌ری

console.log(1);
console.log(2);
console.log(3);

خروجی:

1
2
3

کد غیرهمزمان (Async)

  • کد طولانی رو باید بفرستی پشت صحنه تا مزاحم Call Stack نشه.

  • اینجا Event Loop وارد بازی می‌شه.

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

console.log(1);
setTimeout(() => console.log(2), 0);
console.log(3);

خروجی:

1
3
2

چون setTimeout پشت صحنه می‌ره، و نتیجه بعداً برمی‌گرده به Event Loop.


Promise

خب Promise درجاواسكرييت يه شىء براى مديريت عمليات هاى غير همزمان هست. ميتونيم از then و catch براى كنترل نتيجه موفقيت آمیز یا خطا استفاده کنیم. این به ما کمک میکنه که از callbackهاى تو در تو جلوگیری کنیم.


Promise.all vs Promise.race

  • Promise.all:
    • منتظر می‌ماند تا همه promiseها resolve شوند (یا reject اولین).
  • Promise.race:
    • اولین promise (resolve یا reject) نتیجه را برمی‌گرداند.
Promise.all([p1, p2]
).then(console.log); // همه باید موفق شوند
Promise.race([p1, p2]
).then(console.log); // اولین نتیجه

Promise Chaining

امکان اجرای Promiseها به‌صورت زنجیره‌ای.

fetch("/api")
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

Async/Await

  • راهی خواناتر و تمیزتر برای کار با Promiseها.

سوال؟ Async/Await چگونه Error Handling دارد؟

با try/catch.

async function getData() {
  try {
    const res = await fetch("/api");
    const data = await res.json();
    return data;
  } catch (err) {
    console.error(err);
  }
}

نکته: Async/Await فقط Syntax Sugar است، نه چیز جادویی!


await Vs .then()

await

  • فقط داخل تابع async قابل استفاده‌ست
  • مثل “Pause” می‌مونه → تا نتیجه Promise بیاد وایمیسته (بدون بلاک کردن Thread!)
  • کدت خواناتر، خطی‌تر می‌شه
  • و Error handling با try/catch خیلی تمیزه
async function getData() {
  try {
    const res = await fetch("/api");
    const json = await res.json();
    console.log(json);
  } catch (err) {
    console.log("Error:", err);
  }
}

.then()

  • همیشه قابل استفاده است (تابع async لازم نیست)
  • و callback chain ایجاد می‌کنه
  • دستی باید مدیریت کنی
  • ممکنه تو کدهای زیاد، بهم‌ریخته بشه
fetch("/api")
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.log("Error:", err));

نکته

از لحاظ عملکرد، await و then هیچ تفاوت “فنی” در زمان اجرا ندارن.

خب await فقط یک syntax شیرین و modern برای .then() هست.


Prototype

خب Prototype مکانیزمی برای ارث‌بری در JavaScript است.

function Person(name) {
  this.name = name;
}

Person.prototype.sayHi = function () {
  console.log("Hi " + this.name);
};

const p1 = new Person("Sara");
p1.sayHi();

Immutable Data

داده Immutable قابل تغییر نیست و یعنی داده تغییر نمی‌کند، بلکه نسخه جدید ساخته می‌شود.

  • Predictable State
  • Performance بهتر
  • Debug آسان‌تر
const arr = [1, 2, 3]
;
const newArr = [...arr, 4]
;

// Immutability یعنی Reference جدید، نه Deep Copy کامل

const state = {
  user: { name: "Mojtaba" },
  posts: []
,
};

const nextState = {
  ...state,
  user: { ...state.user, name: "Ali" },
};
  • فقط nodeهای تغییر کرده، Reference جدید می‌گیرند.

Throttle

هر 1 ثانیه فقط یکبار اجرا میشه

درواقع میگه

داداش، یه کم آروم! هر چقدر هم صدام بزنی، من فقط هر X میلی‌ثانیه یا ثانیه یک بار جواب می‌دم

موقع استفاده؟

  • اسکرول
  • resize
  • رویدادهای پرتکرار موس
function throttle(fn, delay) {
  let lastCall = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastCall > delay) {
      lastCall = now;
      fn(...args);
    }
  };
}

const onScroll = throttle(() => {
  console.log("Scrolling... but calmly 😎");
}, 500);

window.addEventListener("scroll", onScroll);

Debounce

وقتی کلیک یا سرچ تموم بشه، حالا اجرا میشه

درواقع میگه

هرچقدر هم صدایم بزنی، تا وقتی که صدا زدنت تمام نشه من هیچ کاری نمی‌کنم!

مثل:

  • سرچ در input
  • auto-save
  • ول کن دیگه تایپ رو، بذار من کارمو بکنم
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

const onSearch = debounce(value => {
  console.log("Searching for:", value);
}, 400);

document.querySelector("input").addEventListener("input", e => {
  onSearch(e.target.value);
});

متدهای call، apply، bind

این سه متد برای تعیین مقدار this استفاده می‌شن.

متد call

فلان تابع رو اجرا کن، ولی this اش رو خودم بهت میدم

function sayHello(city) {
  console.log("Hi I'm " + this.name + " from " + city);
}

const user = { name: "Mojtaba" };

sayHello.call(user, "Tehran");

// خروجی
// Hi I'm Mojtaba from Tehran

call → آرگومان‌ها جدا جدا می‌آن.

متد apply

مثل call هست ولی آرگومان‌ها رو به صورت آرایه می‌ده.

sayHello.apply(user, ["Shiraz"]
);

// خروجی
// Hi I'm Mojtaba from Shiraz

فرقش با call:

  • call(arg1, arg2, arg3)
  • apply([arg1, arg2, arg3] )

متد bind

تابع رو اجرا نمی‌کنه!

فقط یه نسخه جدید از تابع می‌سازه که this همیشه ثابت باشه.

function greet() {
  console.log("Hello " + this.name);
}

const user2 = { name: "Mojtaba" };
const greetMojtaba = greet.bind(user2);

greetMojtaba(); // Hello Mojtaba

///////////
// مثال واقعی

const counter = {
  value: 0,
  increase() {
    console.log(++this.value);
  },
};

setTimeout(counter.increase.bind(counter), 1000);

// خروجی
// 1

چرا لازمه؟

چون توی جاهایی مثل:

  • event listenerها
  • callbackها
  • setTimeout
  • Promiseها
  • کلاس‌ها

خب this گم می‌شه! bind میاد this رو نگه میداره


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]
");

Event Loop

  • جاوااسکریپت Single Thread است، اما با کمک Event Loop میتواند هملیات غیرهمرمان را مدیریت کند بدون اینکه ui فریز شود

  • اجزای اصلی:

  • Call Stack → اجرای توابع

  • Microtask Queue → Promise, queueMicrotask

  • Web APIs → setTimeout، fetch، DOM Events

  • Callback Queue → setTimeout, setInterval

  • Event Loop → هماهنگ‌کننده‌ی همه‌چیز

یادت باشه که Microtask Queue همیشه اولویت بالاتری نسبت به Callback Queue دارد.

console.log("A");

setTimeout(() => {
  console.log("B");
}, 0);

Promise.resolve().then(() => {
  console.log("C");
});

console.log("D");

خروجی:

A
D
C
B

Event Bubbling and Capturing

  • Capturing (trickle down):
    • رویداد از ریشه DOM به سمت پایین حرکت می‌کند.
  • Bubbling:
    • رویداد از عنصر هدف به سمت بالا (document) حباب می‌زند.
element.addEventListener("click", handler, true); // capturing
element.addEventListener("click", handler, false); // bubbling

Memorization

وقتی یه تابع خیلی سنگین و زمان‌بره، و ممکنه چندین بار با ورودی‌های یکسان صدا زده بشه، چرا دوباره حساب کنیم؟

خب Memorization کاری می‌کنه که:

  • اولین بار که تابع با یه ورودی خاص صدا زده شد، نتیجه رو حساب کنه.
  • اون نتیجه رو ذخیره کنه (معمولاً توی یه object یا Map).
  • هر بار بعدی که تابع با همون ورودی صدا زده شد، مستقیم نتیجهٔ ذخیره شده رو برگردونه، بدون اینکه دوباره محاسبات رو انجام بده.

فرض کن یه تابع داری که فیبوناچی رو حساب می‌کنه (خیلی کند!)

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2); // این قسمت خیلی تکراری حساب می‌شه!
}
// console.log(fibonacci(40)); // خیلی طول می‌کشه!

حالا با memorization:

function memoizedFibonacci() {
  const cache = {}; // اینجا نتایج رو نگه می‌داریم

  return function fib(n) {
    if (n in cache) {
      // اگه نتیجه قبلاً حساب شده، برگردون
      console.log(`Returning from cache for ${n}`);
      return cache[n]
;
    } else {
      // اگه نه، حساب کن، ذخیره کن، بعد برگردون
      console.log(`Calculating for ${n}`);
      if (n <= 1) {
        cache[n]
 = n;
        return n;
      }
      const result = fib(n - 1) + fib(n - 2);
      cache[n]
 = result;
      return result;
    }
  };
}

const fib = memoizedFibonacci();

console.log(fib(10));
console.log(fib(10)); // این دفعه از کش برمی‌گرده!
console.log(fib(5));
console.log(fib(10)); // اینم از کش برمی‌گرده!

کاربردها:

  • توابع ریاضی سنگین
  • محاسبات پیچیده
  • مواقعی که سرعت خیلی مهمه و ورودی‌های تکراری زیادن.

Singleton

الگوی Singleton یک design pattern هست که تضمین می‌کنه از یک کلاس یا ماژول، فقط یک نمونه در کل برنامه ساخته بشه.

و همه جای برنامه از همان یه نمونه استفاده میشه

این الگو معمولاً برای چیزهایی مثل:

  • مدیریت global state
  • و connection pool
  • و config های ثابت

استفاده میشه.

در جاوااسکریپت، چون ما module system داریم، هر ماژول به صورت طبیعی Singleton عمل می‌کنه چون importها cache می‌شن

Babel

خب Babel یک transpiler جاوااسکریپت هست که کدهای مدرن ES6+ رو به نسخه‌های قدیمی‌تر سازگار با مرورگرهای کهن‌سال تبدیل می‌کنه.

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

Babel همچنین:

  • و polyfill اضافه می‌کنه
  • و syntax جدید رو به syntax قدیمی‌تر تبدیل می‌کنه
  • در build chain اکثر پروژه‌ها حضور داره (Webpack, Vite و …).