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 ScopeFunction ScopeBlock 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 با مقدارundefinedlet/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 قابل تغییر نیست و یعنی داده تغییر نمیکند، بلکه نسخه جدید ساخته میشود.
PredictableStatePerformanceبهتر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, queueMicrotaskWeb APIs→ setTimeout، fetch، DOM EventsCallback Queue→ setTimeout, setIntervalEvent 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 و …).