مقدمه
به این کد نگاه کنید — آیا فکر میکنید خطا میدهد؟
console.log(name); // ؟
var name = 'رضا';
console.log(name); // ؟
جواب: خط اول undefined چاپ میکند، نه خطا! چرا؟ چون Hoisting اتفاق افتاده.
یا این کد:
greet('رضا'); // ✅ کار میکند — قبل از تعریف!
function greet(name) {
console.log('سلام ' + name + '!');
}
اینجا تابع را قبل از تعریفش فراخواندیم و کار کرد! این هم Hoisting است.
Hoisting یکی از رفتارهایی است که جاوااسکریپت را از اکثر زبانهای دیگر متمایز میکند و درک آن برای جلوگیری از باگهای پنهان ضروری است.
Hoisting چیست؟
Hoisting (بالا بردن) یک رفتار جاوااسکریپت است که در مرحله Compilation (قبل از اجرا) رخ میدهد. در این مرحله، JavaScript Engine کد را اسکن میکند و:
تعریف متغیرها را به بالای Scope منتقل میکند
Function Declaration ها را کاملاً به بالا منتقل میکند
نکته مهم: فقط تعریف بالا میرود، نه مقداردهی!
// کدی که مینویسید:
console.log(x);
var x = 5;
console.log(x);
// JavaScript Engine اینطور میبیند:
var x; // ← تعریف به بالا رفت
console.log(x); // undefined — تعریف شده اما مقدار ندارد
x = 5; // ← مقداردهی سر جایش ماند
console.log(x); // 5
Hoisting با var
// ─── مثال ۱: سادهترین حالت ───
console.log(city); // undefined — نه خطا!
var city = 'تهران';
console.log(city); // 'تهران'
// ─── مثال ۲: داخل تابع — Function Scope ───
function showInfo() {
console.log(language); // undefined
console.log(age); // undefined
var language = 'JavaScript';
var age = 5;
console.log(language); // 'JavaScript'
console.log(age); // 5
}
showInfo();
// ─── مثال ۳: var از بلاک خارج میشود ───
if (true) {
var insideIf = 'من اینجام';
let blockOnly = 'فقط اینجا';
}
console.log(insideIf); // ✅ 'من اینجام'
console.log(blockOnly); // ❌ ReferenceError
// ─── مثال ۴: var در حلقه — باگ کلاسیک ───
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// خروجی: 3, 3, 3 — نه 0, 1, 2!
// چون var hoist میشود و یک i مشترک وجود دارد
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100);
}
// خروجی: 0, 1, 2 ✅
Temporal Dead Zone (TDZ)
let و const هم Hoisting دارند، اما رفتارشان با var کاملاً متفاوت است. آنها به Temporal Dead Zone (TDZ) میروند — یعنی تعریف شدهاند اما قابل دسترسی نیستند تا خط تعریفشان اجرا شود.
// ─── let ───
console.log(score); // ❌ ReferenceError: Cannot access 'score' before initialization
let score = 100;
console.log(score); // 100
// ─── const ───
console.log(PI); // ❌ ReferenceError: Cannot access 'PI' before initialization
const PI = 3.14;
console.log(PI); // 3.14
// ─── TDZ به صورت بصری ───
{
// ─── شروع TDZ برای x ───────────────────────
// x تعریف شده اما قابل دسترسی نیست
console.log(x); // ❌ ReferenceError
console.log(typeof x); // ❌ ReferenceError (برخلاف var که 'undefined' میدهد)
// ─── پایان TDZ ─────────────────────────────
let x = 10;
// ─── از اینجا x قابل دسترسی است ────────────
console.log(x); // ✅ 10
}
// تفاوت با var:
console.log(typeof undeclaredVar); // 'undefined' — نه خطا!
console.log(typeof letVar); // ❌ ReferenceError — در TDZ است
let letVar = 5;
مقایسه var، let و const در Hoisting
ویژگی | var | let | const |
|---|---|---|---|
Hoisting | ✅ بله | ✅ بله (TDZ) | ✅ بله (TDZ) |
مقدار اولیه | undefined | ❌ (TDZ خطا) | ❌ (TDZ خطا) |
قبل از تعریف | undefined | ReferenceError | ReferenceError |
Scope | Function | Block | Block |
امنیت | کم — باگزا | زیاد | زیاد |
توصیه | ❌ استفاده نکنید | ✅ | ✅ پیشفرض |
Hoisting با Function Declaration
Function Declaration کاملاً Hoist میشود — هم تعریف و هم بدنه تابع. به همین دلیل میتوانید قبل از تعریف، تابع را فراخوانی کنید.
// ✅ قبل از تعریف کار میکند
const result = add(3, 4);
console.log(result); // 7
function add(a, b) {
return a + b;
}
// ─── چون JavaScript Engine این را میبیند: ───
function add(a, b) { return a + b; } // ← به بالا رفت
const result = add(3, 4);
// ─── تو در تو هم کار میکند ───
outer();
function outer() {
inner(); // ✅ inner هم hoist شده
function inner() {
deepest(); // ✅ deepest هم hoist شده
function deepest() {
console.log('من از اعماق آمدم!');
}
}
}
// 'من از اعماق آمدم!'
// ─── اما نه با شرط ─── (رفتار نامطمئن)
if (true) {
function conditionalFn() { return 'true branch'; }
} else {
function conditionalFn() { return 'false branch'; }
}
// ⚠️ این رفتار در محیطهای مختلف متفاوت است — از این کار پرهیز کنید
Hoisting با Function Expression و Arrow Function
// Function Expression مثل متغیر رفتار میکند
// ─── با var — undefined میشود ───
console.log(multiply); // undefined — نه تابع!
multiply(2, 3); // ❌ TypeError: multiply is not a function
var multiply = function(a, b) { return a * b; };
// JavaScript Engine این را میبیند:
var multiply; // undefined
console.log(multiply); // undefined
multiply(2, 3); // ❌ TypeError
multiply = function(a, b) { return a * b; };
// ─── با let/const — TDZ ───
console.log(divide); // ❌ ReferenceError
const divide = (a, b) => a / b;
// ─── Arrow Function — مثل متغیر ───
greetUser('رضا'); // ❌ ReferenceError (با let/const) یا TypeError (با var)
const greetUser = (name) => 'سلام ' + name;
Hoisting در Class
// Class Declaration هم Hoist میشود — اما با TDZ
const p = new Person('رضا'); // ❌ ReferenceError — TDZ
class Person {
constructor(name) { this.name = name; }
greet() { return 'سلام ' + this.name; }
}
// ✅ بعد از تعریف درست است
const p2 = new Person('علی');
console.log(p2.greet()); // 'سلام علی'
// ─── Class Expression — مثل Function Expression ───
console.log(Animal); // undefined (با var) یا ReferenceError (با let/const)
var Animal = class {
constructor(name) { this.name = name; }
};
باگهای رایج ناشی از Hoisting
/* ─── باگ ۱: سایهزنی (Shadowing) ─── */
var username = 'رضا';
function showUser() {
console.log(username); // undefined — نه 'رضا'!
var username = 'علی'; // این var داخل تابع hoist میشود
console.log(username); // 'علی'
}
showUser();
// انتظار داشتیم اول 'رضا' چاپ شود اما var داخلی shadow کرد
// ─── JavaScript این را میبیند: ───
function showUser() {
var username; // hoist شد — undefined
console.log(username); // undefined
username = 'علی';
console.log(username); // 'علی'
}
/* ─── باگ ۲: function و var همنام ─── */
console.log(typeof foo); // 'function' — function بر var غلبه میکند!
var foo = 'متغیر';
function foo() { return 'تابع'; }
console.log(typeof foo); // 'string' — var مقداردهی کرد
/* ─── باگ ۳: var در حلقه + async ─── */
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100 * i);
}
// 3, 3, 3 — باگ کلاسیک
// ✅ با let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100 * i);
}
// 0, 1, 2 ✅
/* ─── باگ ۴: فراموش کردن TDZ ─── */
function calculate() {
const result = value * 2; // ❌ ReferenceError — TDZ
const value = 10;
return result;
}
// ✅ درست:
function calculateFixed() {
const value = 10;
const result = value * 2; // ✅
return result;
}
/* ─── باگ ۵: var در if ─── */
function getDiscount(isPremium) {
if (isPremium) {
var discount = 0.2;
}
// var هم در if بود، hoist شد — undefined اگر isPremium = false
return discount; // undefined اگر isPremium = false — نه خطا!
}
console.log(getDiscount(false)); // undefined — نه error!
// ✅ با let — خطای واضحتر:
function getDiscountFixed(isPremium) {
if (isPremium) {
let discount = 0.2;
return discount;
}
return 0; // مقدار صریح برای حالت false
}
مثال کامل واقعی — سیستم مقالات
// ─── نسخه مشکلدار با var ───
function ArticleManagerBad() {
console.log(articleCount); // undefined — نه خطا! پنهانترین باگ
console.log(isLoading); // undefined
var articles = [];
var articleCount = 0;
var isLoading = false;
// باگ Shadowing
var siteName = 'codeloop.ir';
function getFullTitle(title) {
var siteName = 'سایت دیگر'; // این var local است — shadowing!
return siteName + ': ' + title;
}
// getFullTitle('مقاله') → 'سایت دیگر: مقاله' — نه 'codeloop.ir: مقاله'!
// باگ حلقه با var
var categories = ['js', 'react', 'css'];
for (var i = 0; i < categories.length; i++) {
setTimeout(function() {
console.log(categories[i]); // undefined, undefined, undefined!
}, i * 100);
}
}
// ─── نسخه درست با let/const ───
function ArticleManagerGood() {
// ✅ TDZ — اگر قبل از تعریف استفاده کنید، خطای واضح میدهد
const articles = [];
let articleCount = 0;
let isLoading = false;
const siteName = 'codeloop.ir';
const getFullTitle = (title) => siteName + ': ' + title; // ✅ Lexical scope
// ✅ با let — هر iteration مقدار جداگانه
const categories = ['js', 'react', 'css'];
for (let i = 0; i < categories.length; i++) {
setTimeout(() => {
console.log(categories[i]); // 'js', 'react', 'css' ✅
}, i * 100);
}
const addArticle = (title, content) => {
// ✅ با const — تغییر تصادفی ممکن نیست
const article = {
id: ++articleCount,
title,
content,
slug: title.toLowerCase().replace(/s+/g, '-'),
createdAt: new Date(),
};
articles.push(article);
return article;
};
return { addArticle, getArticles: () => [...articles], getCount: () => articleCount };
}
const manager = ArticleManagerGood();
manager.addArticle('Hoisting در جاوااسکریپت', 'محتوا...');
manager.addArticle('Closure چیست', 'محتوا...');
console.log(manager.getCount()); // 2
💡 برای تمرین عملی
با استفاده از API رایگان فارسی کدلوپ تفاوت var و let را در عمل ببینید:
تمرین ۱ — مشکل var در async
// ❌ با var — همه درخواستها page=4 میفرستند
async function loadWithVar() {
for (var page = 1; page <= 3; page++) {
setTimeout(async () => {
const res = await fetch('https://freeapi.codeloop.ir/products?page=' + page);
const data = await res.json();
console.log('صفحه ' + page + ':', data.length); // همیشه page=4!
}, page * 500);
}
}
// ✅ با let — هر iteration مقدار جداگانه
async function loadWithLet() {
for (let page = 1; page <= 3; page++) {
setTimeout(async () => {
const res = await fetch('https://freeapi.codeloop.ir/products?page=' + page);
const data = await res.json();
console.log('صفحه ' + page + ':', data.length); // 1، 2، 3 ✅
}, page * 500);
}
}
loadWithVar();
loadWithLet();
تمرین ۲ — Hoisting در عمل
// کد زیر را قبل از اجرا پیشبینی کنید:
console.log('۱:', typeof a); // ؟
console.log('۲:', typeof b); // ؟
console.log('۳:', typeof c); // ؟
console.log('۴:', typeof greet); // ؟
var a = 1;
let b = 2;
const c = 3;
function greet() { return 'سلام'; }
// جواب:
// ۱: 'undefined' — var hoist شد با undefined
// ۲: ReferenceError — let در TDZ است
// ۳: ReferenceError — const در TDZ است
// ۴: 'function' — Function Declaration کاملاً hoist شد
تمرین ۳ — اصلاح باگ Hoisting
// این کد را پیدا کنید و اصلاح کنید:
async function fetchAndProcess() {
// ❌ باگ ۱: استفاده قبل از تعریف
processData(rawData); // rawData هنوز تعریف نشده
var rawData = await fetch('https://freeapi.codeloop.ir/products').then(r => r.json());
// ❌ باگ ۲: var در حلقه
for (var i = 0; i < rawData.length; i++) {
setTimeout(() => {
console.log(rawData[i]); // undefined!
}, i * 10);
}
function processData(data) {
return data; // فقط برای نمایش
}
}
// ✅ نسخه درست:
async function fetchAndProcessFixed() {
const rawData = await fetch('https://freeapi.codeloop.ir/products').then(r => r.json());
processData(rawData); // ✅ بعد از تعریف rawData
for (let i = 0; i < rawData.length; i++) { // ✅ let
setTimeout(() => {
console.log(rawData[i].name); // ✅
}, i * 10);
}
function processData(data) {
console.log('پردازش ' + data.length + ' محصول');
}
}
fetchAndProcessFixed();
خلاصه — چه چیزی چطور Hoist میشود
نوع | Hoisting | قبل از تعریف |
|---|---|---|
var | ✅ کامل (فقط تعریف) | undefined |
let | ✅ (TDZ) | ❌ ReferenceError |
const | ✅ (TDZ) | ❌ ReferenceError |
function declaration | ✅ کامل (تعریف + بدنه) | ✅ کار میکند |
function expression (var) | ✅ (فقط متغیر) | ❌ TypeError |
function expression (let/const) | ✅ (TDZ) | ❌ ReferenceError |
arrow function | مثل متغیر | ❌ خطا |
class declaration | ✅ (TDZ) | ❌ ReferenceError |
نکات مهم
فقط تعریف hoist میشود — مقداردهی سر جایش میماند. این اصلیترین نکته Hoisting است.
TDZ برای let/const — این متغیرها hoist میشوند اما تا خط تعریف در Temporal Dead Zone هستند و دسترسی به آنها ReferenceError میدهد.
Function Declaration کامل hoist میشود — هم تعریف و هم بدنه. به همین دلیل قبل از تعریف قابل استفاده است.
از var پرهیز کنید — در کد مدرن همیشه از
constو در صورت نیاز به تغییر ازletاستفاده کنید.Hoisting یک مکانیزم است، نه ویژگی — طراحی شده برای اینکه Function Declaration ها در کل فایل قابل دسترسی باشند، نه اینکه متغیرها قبل از تعریف استفاده شوند.
نتیجهگیری
Hoisting یکی از آن رفتارهای جاوااسکریپت است که اگر ندانید، باگهای عجیب و غریب ایجاد میکند. اما وقتی درکش کردید، ابزار قدرتمندی در اختیار دارید — Function Declaration ها را هر جای فایل بنویسید و از هر جا استفاده کنید.
قانون عملی:
همیشه
constوletاستفاده کنید — TDZ باگهای پنهان را آشکار میکندمتغیرها را بالای Scope تعریف کنید — کد خواناتر و قابل پیشبینیتر میشود
Function Declaration را میتوانید پایین فایل بگذارید — اما برای خوانایی معمولاً بالا بهتر است
یکی از بهترین تصمیمات ES6 معرفی TDZ بود. به جای اینکه undefined بدون خطا برگردد، ReferenceError صریح میگوید «این متغیر هنوز مقدار ندارد» — و این باگهای پنهان را به سطح میآورد.
مقالات مرتبط
متغیر در جاوااسکریپت — تفاوت var، let و const که مستقیماً با Hoisting مرتبط است
اسکوپ و کلوژر در جاوااسکریپت — Hoisting در هر Scope جداگانه اتفاق میافتد
تابع در جاوااسکریپت — تفاوت Function Declaration و Expression در Hoisting
ES6 در جاوااسکریپت چیست؟ — let و const راهحل مشکلات var هستند
Arrow Function در جاوااسکریپت — رفتار Arrow Function در Hoisting

