در جاوااسکریپت، هر تابع برای خودش یک دیوارهای دفاعی یا محدوده (Scope) میسازه. این یعنی متغیرهایی که داخل یک تابع تعریف میکنی، کاملاً خصوصی هستند و هیچکس از بیرونِ تابع نمیتونه بهشون دسترسی داشته باشه.
اما این جاده یکطرفهست! توابع داخلی به راحتی به متغیرهای بیرون از خودشان دسترسی دارند. قانون ارثبری محدوده اینطوریه:
یک تابع که در فضای سراسری (Global) ساخته شده، به تمام متغیرهای سراسری دسترسی دارد.
یک تابع که داخل یک تابع دیگه ساخته شده، نه تنها به متغیرهای تابع خودش، بلکه به تمام متغیرهای تابعِ مادر (پدر) و هر چیزی که تابع مادر بهش دسترسی داشته هم دسترسی دارد.
در مقابل، تابع مادر به هیچکدام از متغیرها و توابعِ داخلِ تابع فرزندش دسترسی ندارد! این ویژگی باعث میشه دیتای تابع داخلی کاملاً کپسولهسازی (Encapsulated) و امن بمونه.
بیا با مثال ببینیم:
// این متغیرها در فضای سراسری (Global) تعریف شدهاند
const num1 = 20;
const num2 = 3;
const name = "Chamakh";
function multiply() {
return num1 * num2; // به متغیرهای سراسری دسترسی دارد
}
console.log(multiply()); // 60
// مثال تابع تو در تو
function getScore() {
const num1 = 2; // متغیر تابع مادر
const num2 = 3;
function add() {
// تابع داخلی به name (سراسری) و num1 و num2 (تابع مادر) دسترسی دارد
return `${name} scored ${num1 + num2}`;
}
return add();
}
console.log(getScore()); // "Chamakh scored 5"
مفهوم کلوژر (Closure) یکی از قشنگترین رفتارهای جاوااسکریپت هست. به زبان خیلی ساده:
کلوژر یعنی یک تابع، متغیرهای اطرافش رو «به خاطر میسپاره» و مچاله میکنه میگذاره توی جیبش؛ حتی بعد از اینکه کارِ اون محیط بیرونی تمام شد و از بین رفت!
از نظر فنی، تمام توابع در جاوااسکریپت کلوژر میسازند، اما یک کلوژرِ واقعی و کاربردی زمانی شکل میگیره که ۳ تا شرط زیر برقرار باشه:
یک محدوده مادر (مثل یک تابع) داشته باشیم که چندتا متغیر توش باشه و یک روزی کارش تموم بشه.
یک تابع داخلی داشته باشیم که از متغیرهای اون تابع مادر استفاده کنه.
این تابع داخلی موفق بشه بیشتر از تابع مادر زنده بمونه! (مثلاً تابع مادر، تابع داخلی رو return کنه و ما اون رو بیرونِ تابع توی یک متغیر ذخیره کنیم).
بیا یک مثال کلاسیک ببینیم:
const pet = function (name) {
const getName = function () {
return name; // تابع داخلی به متغیر name دسترسی دارد
};
return getName; // تابع داخلی رو زنده میفرستیم بیرون
};
const myPet = pet("Vivie"); // کارِ تابع pet اینجا تمام شد و مرد!
console.log(myPet()); // خروجی: "Vivie"
اینجا چه جادویی رخ داد؟ وقتی خط const myPet = pet("Vivie") اجرا شد، کارِ تابع pet رسماً تموم شد و قاعدتاً متغیر name باید از حافظه پاک میشد. اما چون تابعِ داخلی (getName) رو ریختیم داخل متغیر myPet، اون تابع متغیر name رو با خودش به بیرون قاچاق کرد و تا ابد یادش میمونه که مقدارش "Vivie" بود!
با کلوژر میتونی کاری کنی که هیچکس نتونه دیتای متغیرهات رو دستکاری کنه، مگر اینکه خودش از متدهایی که براش گذاشتی استفاده کنه. به این سیستمِ مدیریت حیوان خانگی نگاه کن:
const createPet = function (name) {
let sex; // این متغیر کاملاً مخفی و خصوصیه
const petObj = {
setName(newName) { name = newName; },
getName() { return name; },
getSex() { return sex; },
setSex(newSex) {
if (typeof newSex === "string" && (newSex.toLowerCase() === "male" || newSex.toLowerCase() === "female")) {
sex = newSex;
}
}
};
return petObj; // شیء حاوی متدها رو میفرستیم بیرون
};
const myPet = createPet("Vivie");
console.log(myPet.getName()); // Vivie
myPet.setName("Oliver");
myPet.setSex("male");
console.log(myPet.getSex()); // male
console.log(myPet.getName()); // Oliver
توی این کد، متغیرهای name و sex درونِ یک گاوصندوقِ امن قرار دارند. هیچکس بیرون از این تابع نمیتونه بنویسه myPet.sex = "something"؛ تنها راه دسترسی، همین متدهای کلوژری هستند که براش تعریف کردیم.
میتونی از الگوی IIFE استفاده کنی تا یک دیتای حساس (مثل توکن یا کلید API) رو جوری قفل کنی که از هیچجای برنامه قابل تغییر نباشه:
const getCode = (function () {
const apiCode = "0]Eal(eh&2"; // کدی که نمیخوایم کسی تغییرش بده
return function () {
return apiCode; // این تابع ناشناس کد رو قاچاق میکنه بیرون
};
})(); // فوراً اجرا میشه
console.log(getCode()); // "0]Eal(eh&2"
جاوااسکریپت هیچ محدودیتی برای تو در تو نوشتن توابع نداره. فرض کن تابع A داخل خودش تابع B رو داره، و تابع B هم داخل خودش تابع C رو داره:
تابع B یک کلوژر نسبت به A میسازه (به متغیرهای A دسترسی داره).
تابع C یک کلوژر نسبت به B میسازه (به متغیرهای B دسترسی داره).
چون C به B دسترسی داره و B هم به A دسترسی داره، در نتیجه تابع C به متغیرهای تابع A هم دسترسی دارد!
به این رابطه زنجیرهای میگن زنجیره محدوده (Scope Chaining):
function A(x) {
function B(y) {
function C(z) {
console.log(x + y + z); // به همه متغیرهای لایههای بالا دسترسی داره!
}
C(3);
}
B(2);
}
A(1); // خروجی در کنسول: 6 (که حاصل 1 + 2 + 3 هست)
نکته: مسیر برگشت کاملاً مسدوده! یعنی تابع A به هیچ عنوان نمیتونه به متغیرهای داخل B یا C دسترسی داشته باشه. تابع C فقط برای B و داخلِ B خصوصی باقی میمونه.
اگر در لایههای مختلفِ یک زنجیره کلوژر، دو تا متغیر یا پارامتر همنام داشته باشیم چی میشه؟ به این حالت میگن تداخل نام (Name Conflict).
قانون جاوااسکریپت خیلی سادهست: اولویت با داخلیترین (نزدیکترین) لایه است. هر چقدر یک محدوده تو در تو تر باشه، زورش بیشتره و روی متغیرهای همنامِ لایههای بیرونیتر میافته (سایه میاندازه). به این زنجیره که از داخل به خارج حرکت میکنه میگن زنجیره اسکوپ.
بیا با این مثال بررسی کنیم:
function outside() {
const x = 5; // متغیر لایه بیرونی
function inside(x) {
return x * 2; // اینجا x اشاره به کدام x داره؟
}
return inside;
}
console.log(outside()(10)); // خروجی میشه: 20
زنجیره جستجوی جاوااسکریپت برای پیدا کردن متغیر x اینطوری حرکت میکنه: inside ──> outside ──> Global
وقتی در خط return x * 2 جاوااسکریپت به کلمه x میرسه، اول لایه خودش یعنی تابع inside رو نگاه میکنه. اونجا میبینه که یک پارامتر به اسم x ورودی گرفته (که ما عدد ۱۰ رو بهش دادیم). پس همان ۱۰ رو برمیداره و ضربدر ۲ میکنه. متغیر x = 5 که در لایه بیرونی بود، چون همنام بود، کلاً نادیده گرفته میشه!
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://developer.mozilla.org/en-US/docs/Web/JavaScript