مقدمه

یکی از قوی‌ترین مفاهیم توابع در جاوااسکریپت، closure است. با closure می‌توانید تابعی بسازید که به متغیرهای محیط خارجی خود (محیطی که در آن تعریف شده) حتی بعد از اتمام اجرای آن محیط دسترسی دارد. این مفهوم اساس برنامه‌نویسی functional و بسیاری از تکنیک‌های حرفه‌ای است. closure راه‌حلی برای ساخت داده‌های private، پیاده‌سازی factory function، ساخت callbackهای سفارشی و کنترل دقیق state است.

تعریف closure و مثال ساده

closure تابعی است که می‌تواند به متغیرهای تعریف‌شده در محدوده‌ی (scope) والد خودش حتی پس از پایان اجرای والد دسترسی داشته باشد. در کد زیر، تابع makeCounter یک closure می‌سازد که مقدار count را نگه می‌دارد.

Copy Icon JAVASCRIPT
function makeCounter() {
  let count = 0;
  return function() {
    count++;
    return count;
  }
}
let counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2

در اینجا تابع counter که توسط makeCounter ساخته شده، به متغیر count که در محیط والدش تعریف شده دسترسی دارد. هر بار که counter اجرا می‌شود، مقدار count را افزایش می‌دهد و مقدار جدید را برمی‌گرداند. این یعنی حتی پس از اتمام اجرای makeCounter، تابع برگشتی همچنان به count دسترسی دارد و مقدار آن را حفظ می‌کند.

کاربرد closure برای داده‌های private

یکی از مهم‌ترین کاربردهای closure، ساخت متغیرهای خصوصی است. به کمک closure می‌توانید داده‌هایی بسازید که فقط از طریق توابع خاص قابل دسترسی باشند.

Copy Icon JAVASCRIPT
function Person(name) {
    let _name = name;
    return {
        getName: function() { return _name; },
        setName: function(newName) { _name = newName; }
    };
}
let p = Person('Ali');
console.log(p.getName()); // Ali
p.setName('Sara');
console.log(p.getName()); // Sara
                        

در این مثال، متغیر _name فقط از طریق متدهای getName و setName قابل دسترسی است و از بیرون شیء قابل مشاهده نیست.

کاربرد closure در ساخت توابع سازنده

closure برای ساخت factory function یا توابعی که مقدار اولیه دریافت و بعداً رفتار سفارشی بر اساس آن اجرا می‌کنند، عالی است. به مثال زیر دقت کنید.

Copy Icon JAVASCRIPT
function multiplyBy(n) {
  return function(x) {
    return x * n;
  }
}
let triple = multiplyBy(3);
console.log(triple(10)); // 30

در اینجا تابع multiplyBy یک مقدار اولیه (مثلاً ۳) می‌گیرد و یک تابع جدید می‌سازد که هر عددی را در آن مقدار ضرب می‌کند. این یعنی مقدار n در حافظه closure باقی می‌ماند و هر بار که تابع برگشتی اجرا شود، به همان مقدار اولیه دسترسی دارد. این تکنیک برای ساخت توابع سفارشی و قابل استفاده مجدد بسیار کاربردی است.

مشکل رایج با closure در حلقه‌ها

در درس قبل در مورد تفاوت‌ let و var صحبت کردیم و دیدیم که دلایل زیادی وجود دارد که همیشه از let به جای var استفاده کنیم. حالا یک دلیل دیگر هم اضافه می‌کنیم. به مثال زیر توجه کنید.

Copy Icon JAVASCRIPT
let funcs = [];
for (var i = 0; i < 3; i++) {
  funcs.push(function() { return i; });
}
console.log(funcs[0]()); // 3!
console.log(funcs[1]()); // 3
console.log(funcs[2]()); // 3

funcs = [];
for (let i = 0; i < 3; i++) {
  funcs.push(function() { return i; });
}
console.log(funcs[0]()); // 0
console.log(funcs[1]()); // 1
console.log(funcs[2]()); // 2

در حالت اول، به دلیل scope سراسری var، هر تابع مقدار نهایی i را دریافت می‌کند، اما با let در هر تکرار یک scope جدید ساخته می‌شود و مقدار صحیح حفظ می‌شود.

closure یکی از مفاهیم کلیدی جاوااسکریپت است که به شما اجازه می‌دهد به متغیرهای محیط والد حتی پس از پایان اجرای آن دسترسی داشته باشید. این ویژگی برای ساخت داده‌های خصوصی، توابع سازنده، مدیریت state و بسیاری از الگوهای پیشرفته کاربرد دارد. درک درست closure به شما کمک می‌کند کدهای حرفه‌ای‌تر و قابل اطمینان‌تری بنویسید.