مقدمه
اگر با JavaScript کار کرده باشید، حتماً با سه کلمه کلیدی var، let و const روبرو شدهاید. هر سه برای تعریف متغیر استفاده میشوند — اما رفتارشان در شرایط مختلف کاملاً متفاوت است.
این تفاوتها یکی از رایجترین منابع باگ در JavaScript هستند. یک اشتباه در انتخاب بین این سه میتواند باعث رفتارهای عجیب و غیرقابل پیشبینی در کدتان شود — رفتارهایی که ساعتها وقت debug کردن میبرند.
در این مقاله با مثالهای واقعی و قابل اجرا، تفاوت دقیق این سه را یاد میگیرید و میدانید در هر موقعیت کدام را انتخاب کنید.
تاریخچه کوتاه — چرا سه روش داریم؟
در JavaScript اصلی (ES5 و قبل از آن) فقط var وجود داشت. اما var مشکلات جدی داشت که باعث باگهای پنهان میشد.
در سال ۲۰۱۵، با معرفی ES6 (ES2015)، دو کلمه کلیدی جدید اضافه شد: let و const. این دو برای رفع مشکلات var طراحی شدند و امروز استاندارد نوشتن JavaScript مدرن هستند.
// ES5 — فقط var
var name = 'رضا';
// ES6+ — let و const اضافه شدند
let age = 25;
const PI = 3.14;مقایسه سریع — جدول تفاوتها
ویژگی | var | let | const |
|---|---|---|---|
Scope | Function | Block | Block |
Hoisting | بله (undefined) | بله (TDZ) | بله (TDZ) |
قابل تغییر مقدار | بله | بله | خیر |
تعریف مجدد | بله | خیر | خیر |
Global object | اضافه میکند | اضافه نمیکند | اضافه نمیکند |
توصیه مدرن | ❌ اجتناب کنید | ✅ وقتی نیاز است | ✅ پیشفرض |
تفاوت اول: Scope (محدوده)
مهمترین تفاوت بین var با let و const، نوع Scope آنهاست. Scope تعیین میکند یک متغیر در کجا قابل دسترسی است.
var — Function Scope
var فقط به تابع احترام میگذارد — نه به block هایی مثل if، for یا آکولادهای ساده:
function testVar() {
if (true) {
var message = 'سلام';
console.log(message); // 'سلام' ✅
}
console.log(message); // 'سلام' ✅ — از block خارج شد اما هنوز در دسترس است!
}
testVar();
console.log(message); // ReferenceError ❌ — از تابع خارج شد// مشکل کلاسیک با var در حلقه
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // انتظار: 0, 1, 2
}, 100);
}
// واقعیت: 3, 3, 3 — چون var function-scoped است!
// تا setTimeout اجرا شود، حلقه تمام شده و i = 3 استlet و const — Block Scope
let و const به هر block (هر جفت آکولاد {}) احترام میگذارند:
function testLet() {
if (true) {
let message = 'سلام';
console.log(message); // 'سلام' ✅
}
console.log(message); // ReferenceError ❌ — خارج از block
}
// مشکل حلقه با let حل میشود
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 0, 1, 2 ✅ — هر iteration یک i جداگانه دارد
}, 100);
}// مثال واقعی — شرطها
function getUserLabel(user) {
let label;
if (user.isPremium) {
let badge = '⭐'; // فقط داخل این if
label = `${badge} ${user.name}`;
} else {
label = user.name;
}
// console.log(badge); // ReferenceError — درست! badge اینجا معنا ندارد
return label; // label اینجا در دسترس است
}تفاوت دوم: Hoisting
Hoisting رفتاری در JavaScript است که تعریف متغیرها و توابع را به بالای scope میبرد — قبل از اجرای کد. اما هر سه کلمه کلیدی رفتار متفاوتی دارند:
var — Hoisting با مقدار undefined
console.log(name); // undefined — نه خطا!
var name = 'رضا';
console.log(name); // 'رضا'
// JavaScript این کد را اینطور میبیند:
var name; // ← hoist شده به بالا
console.log(name); // undefined
name = 'رضا';
console.log(name); // 'رضا'این رفتار خطرناک است — میتوانید متغیر را قبل از تعریفش استفاده کنید و به جای خطا، undefined میگیرید. باگهای پنهان ایجاد میکند.
let و const — Temporal Dead Zone (TDZ)
console.log(name); // ReferenceError: Cannot access 'name' before initialization
let name = 'رضا';
console.log(PI); // ReferenceError: Cannot access 'PI' before initialization
const PI = 3.14;let و const هم hoist میشوند — اما در Temporal Dead Zone (TDZ) قرار میگیرند. یعنی از ابتدای block تا خط تعریف، دسترسی به آنها خطا میدهد.
این رفتار بهتر است — خطا فوری است و واضح، نه یک undefined پنهان.
// TDZ در عمل
{
// ── شروع TDZ برای count ──
console.log(typeof count); // ReferenceError — حتی typeof هم کار نمیکند!
let count = 0;
// ── پایان TDZ ──
console.log(count); // 0 ✅
}تفاوت سوم: قابلیت تغییر مقدار (Reassignment)
// var — قابل تغییر
var score = 10;
score = 20; // ✅ مجاز
score = 'عالی'; // ✅ حتی تغییر نوع هم مجاز!
// let — قابل تغییر
let count = 0;
count = 1; // ✅ مجاز
count++; // ✅ مجاز
// const — غیرقابل تغییر (Reassignment)
const MAX = 100;
MAX = 200; // ❌ TypeError: Assignment to constant variableنکته مهم: const فقط از reassignment جلوگیری میکند — نه از تغییر محتوای آبجکت یا آرایه:
// const با آبجکت
const user = { name: 'رضا', age: 25 };
user.age = 26; // ✅ مجاز! محتوای آبجکت تغییر کرد
user.city = 'تهران'; // ✅ مجاز! property جدید اضافه شد
user = { name: 'علی' }; // ❌ خطا! خود متغیر نمیتواند تغییر کند
// const با آرایه
const courses = ['HTML', 'CSS'];
courses.push('JavaScript'); // ✅ مجاز! آرایه تغییر کرد
courses[0] = 'Git'; // ✅ مجاز!
courses = ['React']; // ❌ خطا!
// برای آبجکت واقعاً ثابت، از Object.freeze استفاده کنید
const C Object.freeze({
API_URL: 'https://api.codeloop.ir',
VERSION: '2.0',
});
CONFIG.API_URL = 'other'; // در strict mode خطا، وگرنه بیاثرتفاوت چهارم: تعریف مجدد (Redeclaration)
// var — تعریف مجدد مجاز است (و خطرناک!)
var user = 'رضا';
var user = 'علی'; // ✅ خطا نمیدهد — مقدار قبلی بیسروصدا از بین میرود
console.log(user); // 'علی'
// let — تعریف مجدد ممنوع
let score = 10;
let score = 20; // ❌ SyntaxError: Identifier 'score' has already been declared
// const — تعریف مجدد ممنوع
const PI = 3.14;
const PI = 3; // ❌ SyntaxError: Identifier 'PI' has already been declaredممنوع بودن redeclaration در let و const یک محافظ مهم است — جلوگیری از اینکه تعریف دوم یک متغیر، ناخواسته تعریف اول را از بین ببرد.
تفاوت پنجم: Global Object
// var در سطح global به window اضافه میشود (مرورگر)
var globalVar = 'من global هستم';
console.log(window.globalVar); // 'من global هستم' ✅
// let و const به window اضافه نمیشوند
let globalLet = 'من global هستم';
console.log(window.globalLet); // undefined
const globalC 'من global هستم';
console.log(window.globalConst); // undefinedآلوده کردن global object با var میتواند باعث تداخل با کدهای دیگر یا کتابخانههای third-party شود.
مشکلات واقعی var — چرا باید از آن اجتناب کنید
بریم چند باگ واقعی که var ایجاد میکند را ببینیم:
باگ ۱: حلقه و رویداد
// ❌ با var — باگ کلاسیک
const butt document.querySelectorAll('.btn');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log('دکمه شماره', i);
// همیشه آخرین مقدار i را چاپ میکند!
// مثلاً اگر ۳ دکمه داشته باشید: همیشه ۳ چاپ میشود
});
}
// ✅ با let — درست
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log('دکمه شماره', i);
// هر دکمه i درست خودش را دارد: 0، 1، 2
});
}باگ ۲: تعریف تصادفی
// ❌ با var — redeclaration بیسروصدا
function processUsers(users) {
// ۵۰۰ خط کد...
var result = [];
// ۲۰۰ خط کد دیگر...
var result = {}; // ← تعریف مجدد! مقدار قبلی از بین رفت
// JavaScript خطا نمیدهد — اما result دیگر آرایه نیست
return result.map(u => u.name); // TypeError: result.map is not a function
}
// ✅ با let — خطا فوری و واضح
function processUsers(users) {
let result = [];
// ...
let result = {}; // SyntaxError: Identifier 'result' has already been declared
// خطا در ویرایشگر کد قبل از اجرا نمایش داده میشود
}باگ ۳: استفاده قبل از تعریف
// ❌ با var — undefined پنهان
function calculateTotal(price, discount) {
console.log('تخفیف:', discount); // undefined — نه خطا!
if (price > 100) {
var discount = price * 0.1;
}
return price - (discount || 0);
}
calculateTotal(50, 0.2);
// انتظار: 50 - (0.2) = 49.8
// واقعیت: 50 - 0 = 50 — چون discount هوئیست شد و undefined بود
// ✅ با let — خطای واضح
function calculateTotal(price, discount) {
console.log('تخفیف:', discount); // ✅ پارامتر تابع — مقدار واقعی
if (price > 100) {
let localDiscount = price * 0.1; // اسم متفاوت، scope جدا
}
return price - discount;
}const — چرا باید پیشفرض شما باشد
یک اصل مهم در برنامهنویسی: کمترین قدرت لازم را استفاده کن. اگر یک متغیر نیاز به تغییر ندارد، آن را const تعریف کنید. این کار:
نیت کد را واضح میکند: هر کسی کد را میخواند میفهمد این مقدار تغییر نمیکند.
از تغییر تصادفی جلوگیری میکند: اگر اشتباهاً reassign کنید، خطا میگیرید.
بهینهسازی موتور JS: موتور میداند این مقدار ثابت است و میتواند بهینهتر عمل کند.
// ✅ const برای مقادیری که تغییر نمیکنند
const API_URL = 'https://api.codeloop.ir';
const MAX_RETRY = 3;
const user = await fetchUser(id); // آبجکت — مجاز
const articles = await fetchArticles(); // آرایه — مجاز
const btn = document.querySelector('#submit');
const handleClick = (e) => { /* ... */ };
// ✅ let فقط وقتی واقعاً نیاز به تغییر هست
let currentPage = 1;
let isLoading = false;
let retryCount = 0;
// در حلقه
for (let i = 0; i < items.length; i++) { }
// accumulator
let total = 0;
prices.forEach(price => { total += price; });قانون طلایی — کِی از کدام استفاده کنیم؟
// ─────────────────────────────────────────
// قانون ساده سه مرحلهای:
// ─────────────────────────────────────────
// مرحله ۱: همیشه اول const امتحان کن
const name = 'رضا';
// مرحله ۲: اگر نیاز به تغییر داری، let استفاده کن
let count = 0;
count++;
// مرحله ۳: هرگز var استفاده نکن (مگر legacy code)
// var x = 1; ← فراموش کنید
// ─────────────────────────────────────────
// موارد خاص:
// ─────────────────────────────────────────
// ✅ const — مقادیر پیکربندی
const C {
baseURL: 'https://api.codeloop.ir',
timeout: 5000,
headers: { 'Content-Type': 'application/json' },
};
// ✅ const — توابع (همیشه ثابتند)
const fetchArticle = async (slug) => {
const resp await fetch(`${CONFIG.baseURL}/articles/${slug}`);
return response.json();
};
// ✅ const — عناصر DOM (reference تغییر نمیکند)
const modal = document.querySelector('.modal');
const overlay = document.querySelector('.overlay');
const closeBtn = document.querySelector('.close-btn');
// ✅ let — state که تغییر میکند
let isOpen = false;
let pageIndex = 0;
let searchQuery = '';
// ✅ let — در حلقهها
for (let i = 0; i < 10; i++) {
// ...
}
// ✅ let — وقتی مقدار اولیه ندارید
let errorMessage;
try {
await saveData();
} catch (err) {
errorMessage = err.message;
}
if (errorMessage) showError(errorMessage);در دنیای واقعی — پروژه کدلوپ
بریم یک مثال واقعی از پروژه ببینیم — سیستم pagination مقالات:
// ✅ کد مدرن و درست با const و let
const ARTICLES_PER_PAGE = 12; // const: ثابت
const grid = document.querySelector('.articles-grid'); // const: تغییر نمیکند
const paginati document.querySelector('.pagination');
let currentPage = 1; // let: تغییر میکند
let totalPages = 1; // let: بعداً از API میآید
let isLoading = false; // let: toggle میشود
let allArticles = []; // let: پر میشود
async function loadPage(page) {
if (isLoading) return;
isLoading = true;
const resp await fetch( // const: یک بار assign
`/api/articles?page=${page}&limit=${ARTICLES_PER_PAGE}`
);
const data = await response.json(); // const: یک بار assign
allArticles = data.articles; // let reassign ✅
totalPages = data.totalPages; // let reassign ✅
currentPage = page; // let reassign ✅
renderArticles(allArticles);
renderPagination(currentPage, totalPages);
isLoading = false; // let reassign ✅
}
function renderArticles(articles) {
// const داخل تابع — هر بار تابع اجرا میشود، const جدید
const fragment = document.createDocumentFragment();
articles.forEach(article => {
const card = document.createElement('article'); // const: تغییر نمیکند
const link = `
<a href="/articles/${article.slug}">
<h2>${article.title}</h2>
</a>
`;
card.innerHTML = link;
fragment.appendChild(card);
});
grid.innerHTML = '';
grid.appendChild(fragment);
}
loadPage(1);var در legacy code — چه کار کنیم؟
اگر با کد قدیمی کار میکنید که پر از var است، این راهنما را داشته باشید:
// ❌ کد قدیمی
var apiUrl = 'https://api.example.com';
var userData = null;
var isAuthenticated = false;
function login(username, password) {
var token = null;
// ...
var token = getToken(username, password); // redeclaration — باگ احتمالی
userData = fetchUser(token);
isAuthenticated = true;
}
// ✅ تبدیل به مدرن — قانون ساده:
// var که تغییر نمیکند → const
// var که تغییر میکند → let
// var که redeclare میشود → دو متغیر جداگانه با let
const API_URL = 'https://api.example.com'; // const — تغییر نمیکند
let userData = null; // let — تغییر میکند
let isAuthenticated = false; // let — تغییر میکند
async function login(username, password) {
const token = await getToken(username, password); // const — یک بار assign
userData = await fetchUser(token); // reassign ✅
isAuthenticated = true; // reassign ✅
}TypeScript — const و let حتی قویتر
اگر از TypeScript استفاده میکنید، const حتی قویتر عمل میکند — نوع را هم inference میکند:
// TypeScript
const name = 'رضا'; // نوع: string literal 'رضا' — نه فقط string
let age = 25; // نوع: number
// با const، TypeScript نوع دقیقتری میداند
const direction = 'right'; // نوع: 'right' (literal type)
let directi 'right'; // نوع: string (گستردهتر)
// این در Union Type مهم میشود
type Direction = 'left' | 'right' | 'up' | 'down';
const move = (dir: Direction) => { /* ... */ };
const dir1 = 'right'; // ✅ TypeScript میداند 'right' است
move(dir1); // ✅ کار میکند
let dir2 = 'right'; // نوع: string — نه literal
move(dir2); // ❌ خطا! string به Direction قابل assign نیستنکات مهم که باید حفظ کنید
constپیشفرض باشد: اگر نمیدانید از کدام استفاده کنید، ازconstشروع کنید. اگر خطا گرفتید که نیاز به reassign دارید، بهletتغییر دهید.letبرای state: هر چیزی که در طول اجرا تغییر میکند — counter، flag، مقدار فرم، صفحه جاری.varرا فراموش کنید: هیچ موقعیتی وجود ندارد کهvarانتخاب بهتری ازletیاconstباشد در کد مدرن.constبا آبجکت یعنی reference ثابت، نه محتوا: میتوانید property های آبجکتconstرا تغییر دهید — اما نمیتوانید آبجکت جدید assign کنید.TDZ یعنی متغیر را بالای scope تعریف کنید:
letوconstرا جایی تعریف کنید که اولین بار استفاده میشوند — نه لزوماً بالای تابع.
یکی از سادهترین راهها برای حرفهایتر شدن کدتان: همین امروز
varرا از واژهنامهتان حذف کنید. هر بار که میخواهید متغیر تعریف کنید، ازconstشروع کنید — اگر نیاز به تغییر داشت،let. این یک عادت کوچک است که کیفیت کد شما را به شکل چشمگیری بالا میبرد. اگر میخواهید JavaScript را از پایه یاد بگیرید، مقاله JavaScript چیست نقطه شروع خوبی است.
نتیجهگیری
تفاوت var، let و const در سه محور اصلی خلاصه میشود: Scope، Hoisting و Reassignment.
var با Function Scope، hoisting آزاد و اجازه redeclaration، منبع بیشماری از باگهای پنهان است. let و const با Block Scope و TDZ، کد را قابل پیشبینیتر و امنتر میکنند.
قانون عملی: همیشه const، وقتی نیاز به تغییر است let، و هرگز var. این یک اصل ساده است که کدنویسی JavaScript شما را به سطح بالاتری میبرد.

