توابع بی نام و بستارها (closures) مفاهیم مفیدی هستند که در بیشتر زبانهای برنامه نویسی مدرن استفاده می شوند. مثل بسیاری دیگر از اجزای زبانهای برنامه نویسی مدرن بستارها ریشه در زبان برنامه نویسی لیسپ دارند.

توابع بی نام بصورت گسترده در جاوااسکریپت و بصورت ویژه در callback های موجود در بسیاری از فریمورکهای این زبان استفاده می شوند. در زبان روبی آنها را در فرمت بلوک می بینیم.

ابتدا نگاهی به توابع بی نام می اندازیم. یک تابع بی نام به برنامه نویس اجازه می دهد تا تابعی ایجاد نماید که نام  ندارد. به عبارت دیگر یک تابع بی نام برای نگه داشتن جزئی از عملکرد در یک متغیر و پاس دادن آن به عملکرد حول خودش استفاده می شود. با هم به مثالی از توابع بی نام در روبی و جاوااسکریپت نگاهی می اندازیم:

[1, 2, 3].map do |number_to_double|
  number_to_double * 
end
_.map([1, 2, 3], function(numberToDouble) {
  return numberToDouble * 2
})

در مثال بالا از متد map روی Array در روبی و از متد map موجود در کتابخانه Lo-Dash جاوااسکریپت استفاده کردیم. مثال روبی از از یک بلاک و مثال جاوااسکریپت از یک تابع استفاده می کند. این callback ها توابع  بی نام هستند. ما پاره ای از عملکرد را مشخص کرده ایم که در این مورد ضرب عدد ارایه شده در 2 و پاس دادن حاصل این عملکرد به متد map می باشد. در این مثال آرایه حاصل [2,4,6] می باشد. به علت اینکه علت اینکه اجازه داریم عملکرد مورد نیاز خود را برای تغییر عناصر آرایه اعمال کنیم ایجاد کد قابل استفاده مجدد راحت تر می شود. در مقابل به جای ایجاد تعدادی متد map متفاوت که هر کدام محتویات آرایه را با روش متقاوتی تغییر می دهند، ما فقط به یکی از آنها نیاز داریم تا عملکرد مورد نیاز ما را فراهم آورد.

بستارها با ارتباط بین عملکرد و متغیرهای در دسترس تعریف میشوند و می توان گفت عملکردی هستند که اجرا می شوند. وقتی بستاری ایجاد می گردد می تواند از هر متغیری که در حوزه معنایی (lexical) آن قرار دارد یا به عبارتی به آن نزدیک است استفاده کند. این سوال ایجاد می شود که حوزه معنایی چیست؟ حوزه معنایی به معنای این است که متغیرها به محیط محلی خود ارجاع می کنند.

برای شفاف سازی به مثال زیر نگاهی می اندازیم:

Ruby

def lexical_scope_example(multiplier)
  [1, 2, 3].map do |number|
    new_number_to_double = number * multiplier
    new_number_to_double * 2
  end
end
ruby > lexical_scope_example(5)

[10, 20, 30]

در مثال روبی ما دو حوزه معنایی داریم:

-تابع lexical_scope_example پارامتر multiplier را دارد

-بلوکی که به متد map پاس می دهیم می تواند به متغیرهای multiplier, number و new_number_to_double دسترسی داشته باشد

JavaScript

function lexicalScopeExample(multiplier) {
  return _.map([1, 2, 3], function(number) {
    var newNumberToDouble = number * multiplier
    return newNumberToDouble * 2
  });
}
javascript > lexicalScopeExample(5)

[10, 20, 30]

در مثال JavaScript ما دو حوزه معنایی داریم:

-تابع lexicalScopeExample پارامتر multiplier را دارد

-بلاکی که به متد map پاس می دهیم می تواند به متغیرهای multiplier, number و newNumberToDouble دسترسی یابد.

جالب است بدانید که حوزه معنایی بلاک شامل متغیرهایی می شود که به حوزه معنایی بوجود آورنده بلاک تعلق دارند. بر عکس این درست نیست. متغیرهایی که در داخل حوزه معنایی بلاک تعریف شده اند به در حوزه معنایی تابع قابل دسترس نمی باشند. اجازه دهید ببینیم چه اتفاقی می افتد اگر بخواهیم به متغیر new_number_to_double در خارج از حوزه معنایی بلاک دسترسی یابیم.

Ruby

def lexical_scope_example(multiplier)
  [1, 2, 3].map do |number|
    new_number_to_double = number * multiplier
    new_number_to_double * 2
  end

  puts new_number_to_double
end
ruby > lexical_scope_example(5)

NameError: undefined local variable or method `new_number_to_double' for main:Object

JavaScript

function lexicalScopeExample(multiplier) {
  return _.map([1, 2, 3], function(number) {
    var newNumberToDouble = number * multiplier
    return newNumberToDouble * 2
  });

  console.log(newNumberToDouble)
}
javascript > lexicalScopeExample(5)

ReferenceError: newNumberToDouble is not defined

حال که با حوزه معنایی آشنا شدیم اجازه دهید دوباره ارتباط آنها را با بستارها بررسی کنیم. بستارها به ما اجازه می دهند تا واحدهایی از عملکرد را با حفظ مرجع به تمام متغیرهایی که در حوزه معنایی که در آن تعریف شده اند را تعریف نماییم. این واحدهای واحدهای عملکرد می توانند حوزه های معنایی متفاوت اجرا شوند. با هم به یک مثال نگاه می کنیم.

Ruby

def scope_two
  yield
end

def scope_one
  closure_addend = 3

  scope_two do
    closure_addend += 5
  end

  puts "The variable in our main lexical scope after the method is executed equals #{closure_addend}"
end
ruby > scope_one

The variable in our main lexical scope after the method is executed equals 8

در مثال روبی سه حوزه معنایی داریم:
-تابع scope_one شامل متغیر closure_addend
-بلاکی که هنگام فراخوانی تابع  scope_two تعریف میکنیم شامل متغیرهای closure_adddend می شود
اجازه دهید مثال روبی را قدم به قدم بررسی کنیم:
-در irb (محیط روبی) تابع scope_one را فراخوانی می کنیم
-در این تابع متغیر closure_addend را تعریف و مقدار 3 را به آن تخصیص می دهیم
-در حوزه معنایی تابع scope_one یک بلاک تعریف می کنیم و آن را به تابع scope_two پاس می دهیم
-در داخل حوزه معنایی جدید تابع scope_two بستار خود را اجرا می کنیم
-در داخل بستار خود مقدار 5 را به متغیر closure_addend که در حوزه معنایی تابع scope_one تعریف کرده ایم اضافه میکنیم
-سپس به حوزه معنایی scope_one بر میگردیم و متغیر closure_addend را چاپ میکنیم که حالا به میزان 5 اضافه شده است
JavaScript

function scopeTwo(callback) {
  callback();
}

function scopeOne() {
  var closureAddend = 3;

  scopeTwo(function() {
    closureAddend = closureAddend + 5;
  });

  console.log("The variable in our main lexical scope after the method is executed " + closureAddend);
}
javascript > scopeOne()

The variable in our main lexical scope after the method is executed equals 8


در مثال جاوااسکریپت ما سه حوزه معنایی داریم:
-تابع  scpeOne شامل متغیر closureAddend می شود
-تابعی (بستاری) که در هنگام فراخوانی تابع scopeTwo پاس می دهیم شامل متغیرهای closureAddend می شود
با هم قدم به قدم کد جاوااسکریپت بالا را بررسی می کنیم:
-در کنسول مرورگر خود تابع scopeOne را فراخوانی می کنیم
-در داخل این تابع متغیر closureAddend را ایجاد و مقدار 3 را به آن تخصیص می دهیم
-در داخل حوزه معنایی تابع scopeOne یک تابع تعریف می کنیم و آن را به تابع scopeTwo پاس می دهیم
-در داخل حوزه معنایی جدید تابع scopeTwo بستار خود را که callback نامیده ایم اجرا می کنیم
-در داخل بستار خود مقدار 5 را به متغیر closureAddend که در داخل حوزه معنایی تابع scopeOne تعریف کرده ایم اضافه می کنیم
-سپس به حوزه معنایی تابع scopeOne بر می گردیم و از متغیر closureAddend خود در کنسول لاگ می گیریم (Console.log)، که به مقدار 5 عدد افزایش یافته است
قسمت جالب این است که بستار ما در یک حوزه معنایی کاملا متفاوت با حوزه ای که در آن ایجاد شده اجرا می شود ولی هنوز به متغیرهای حوزه اصلی دسترسی دارد. هم چنین مهم است که بدانید تغییرات که در حوزه معنایی جدید اتفاق می افتد روی مقادیر اصلی تاثیر می گذارد زیرا همان مقادیر هستند.
ببینیم چه اتفاقی می افتد اگر بخواهیم به متغیر closure_addend در حوزه معنایی تابع scope_two دسترسی یابیم.
Ruby

def scope_two
  yield
  puts "We just modified this variable in the block above #{closure_addend}"
end

def scope_one
  closure_addend = 3

  scope_two do
    closure_addend += 5
  end

  puts "The variable in our main lexical scope after the method is executed #{closure_addend}"
end

ruby > scope_one

`scope_two': undefined local variable or method `closure_addend' for main:Object (NameError)


JavaScript

function scopeTwo(callback) {
  callback();
  console.log("We just modified this variable in the block above " + closureAddend);
}

function scopeOne() {
  var closureAddend = 3;

  scopeTwo(function() {
    closureAddend = closureAddend + 5;
  });

  console.log("The variable in our main lexical scope after the method is executed " + closureAddend);
}

javascript > scopeOne()

ReferenceError: closureAddend is not defined

توانایی نگه داشتن ارجاعات به حوزه معنایی در زمان تعریف به خصوص در مورد callback ها مفید است، که بصورت گسترده در جاوااسکریپت استفاده می شود. خیلی رایج است که یک عمل غیرهمگام را با یک callback فراهم آوریم تا در هنگام تکمیل اجرا شود. حوزه معنایی که این callback اجرا می شود اغلب فریمورکی است که از آن استفاده می  کنیم.
منبع :Back to Basics: Anonymous Functions and Closures