برنامه نویسی و طراحی سایت

Closure در جاوا اسکریپت چیست؟ – توضیح کلوژر به زبان ساده + مثال و کد

Closure در جاوا اسکریپت چیست؟ – توضیح کلوژر به زبان ساده + مثال و کد

Closure در جاوا اسکریپت نوعی مفهوم اساسی است که هر برنامه‌نویسی باید به طور کامل آن را درک کند. درک عملکرد «Closure» به توسعه‌دهندگان این امکان را می‌دهد تا تسلط بیش‌تری بر ابزارهای خود داشته باشند. در این مطلب از «مجله تم آف» به زبان ساده این پرسش را پاسخ می‌دهیم که Closure در جاوا اسکریپت چیست و برای درک بهتر این مفهوم، مثال‌هایی نیز به همراه کدهای مربوطه ارائه شده‌اند.

فهرست مطالب این نوشته
Closure در جاوا اسکریپت چیست؟

مثال Closure در جاوا اسکریپت

مفاهیم سطح بالا در مبحث Closure ها

Execution Context چیست؟

مثال Execution Context

محیط واژگانی

جنبه های کلیدی محیط واژگانی

مثالی از مفهوم محیط واژگانی

زنجیره محدوده

محدوده ایستا و محدوده پویا

آموزش Closure در جاوا اسکریپت

مثال هایی برای Closure در جاوا اسکریپت

مثال ١: ایجاد زمینه محصور کننده برای توابع

مثال ٢: ایجاد Closure جداگانه

مثال ۳: دسترسی به متغیرهای محیطی

مثال ٤: ارجاع خصوصی به متغیر در محدوده بیرونی

Closures و حلقه ها در جاوا اسکریپت

کلمه کلیدی let و Closure در جاوا اسکریپت

IIFE و Closure در جاوا اسکریپت

Closure در جاوا اسکریپت چگونه ایجاد می شود؟

کاربرد Closure در Javascript چیست؟

چگونه Closure را عمیق یاد بگیریم؟

سخن پایانی

faradars mobile

Closure در جاوا اسکریپت چیست؟

«بستار» (Closure) نوعی ویژگی بسیار تاثیرگذار است که در جاوا اسکریپت و همچنین بسیاری از زبان‌های برنامه نویسی دیگر یافت می‌شود. طبق تعریف ارائه شده به وسیله «MDN»، کلوژرها توابعی هستند که به متغیرهای مستقل ارجاع می‌دهند. به این متغیرها، متغیرهای آزاد نیز می‌گویند. به عبارت دیگر در جاوا اسکریپت، Closure‌ها به عنوان شکلی از «تعیین محدوده واژگانی» (Lexical scoping) برای حفظ متغیرها از محدوده بیرونی تابع در محدوده درونی آن استفاده می‌شوند. محدوده واژگانی، محدوده متغیر را بر اساس موقعیت آن در کد منبع تعیین می‌کند.

مجموعه آموزش جاوا اسکریپت (JavaScript)
فیلم مجموعه آموزش جاوا اسکریپت (JavaScript) در تم آف

کلیک کنید

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

closure در جاوا اسکریپت چیست
  • نکته: توجه به این نکته مهم است که متغیرهای آزاد متغیرهایی هستند که نه به صورت محلی در تابع اعلان می‌شوند و نه به عنوان پارامتر ارسال خواهند شد.

مثال Closure در جاوا اسکریپت

برای درک بهتر مفهوم Closure در زبان برنامه نویسی جاوا اسکریپت در ادامه ۲ مثال از این مبحث ارائه خواهد شد. قطعه کد مثال اول به صورت زیر است.

function numberGenerator() {
  // Local “free” variable that ends up within the closure
  var num = 1;
  function checkNumber() { 
    console.log(num);
  }
  num++;
  return checkNumber;
}

var number = numberGenerator();
number(); // 2

در مثال داده شده تابعی به نام numberGenerator

 وجود دارد. در این تابع، متغیر محلی به نام num

 به عنوان نوعی متغیر آزاد تعریف می‌شود. در کنار آن، تابع دیگری به نام checkNumber

 اعلان شده است که مقدار num

را در کنسول چاپ می‌کند.

اگرچه checkNumber

هیچ متغیر محلی برای خود ندارد، اما بنا بر مفهوم Closure در جاوا اسکریپت، می‌تواند به متغیرهای تابع بیرونی خود، یعنی numberGenerator

دسترسی داشته باشد. در نتیجه، checkNumber

می‌تواند حتی پس از اتمام اجرای numberGenerator

، به طور موثر از متغیر num

استفاده کند که در numberGenerator

اعلان شده است. این امر هنگام فراخوانی num

مشهود خواهد بود که ارجاع به تابع checkNumber

را نگه می‌دارد و در نتیجه مقدار 2

 در کنسول ثبت می‌شود. حال قطعه کد مثال دوم در ادامه آمده است.

function sayHello() {
  var say = function() { console.log(hello); }
  // Local variable that ends up within the closure 
  var hello = 'Hello, world!';
  return say;
}
var sayHelloClosure = sayHello(); 
sayHelloClosure(); // ‘Hello, world!’

در مثال فوق، هدف نشان دادن این است که Closure در جاوا اسکریپت تمام متغیرهای محلی اعلان شده در تابع محصور بیرونی خود را در بر می‌گیرد. قطعه کد بالا تابعی به نام sayHello

 را تعریف می‌کند. در داخل این تابع، نوعی متغیر محلی به نام say

 وجود دارد که «تابعی ناشناس» (Anonymous function) به آن اختصاص داده شده است. این تابع ناشناس مقدار متغیر hello

 را در کنسول ثبت می‌کند.

خود متغیر hello

بعد از تابع ناشناس در همان تابع محصور اعلان می‌شود. با وجود این، تابع ناشناس همچنان می‌تواند به متغیر hello

دسترسی داشته باشد و از آن استفاده کند. دلیلش این است که در زمان ایجاد تابع ناشناس، متغیر hello

قبلاً در «محدوده» (Scope) تابع تعریف شده بود که به آن اجازه می‌داد زمانی که تابع ناشناس در نهایت اجرا می‌شود، در دسترس باشد.

مفاهیم سطح بالا در مبحث Closure ها

برای به دست آوردن درک عمیق‌تر از مفهوم Closure در جاوا اسکریپت، یادگیری مفاهیم مرتبط که زمینه لازم برای درک مفهوم نام برده را فراهم می‌کند، بسیار مهم است. در این مطلب ابتدا از مفاهیم پیشرفته شروع می‌کنیم. برای این هدف ابتدا باید با مفهوم «زمینه اجرایی» (Execution Context) آشنا شد که به معنای محیطی است که تابع در آن اجرا می‌شود.

آموزش پروژه محور جاوا اسکریپت، CSS و HTML – طراحی صفحه فرود واکنشگرا
فیلم آموزش پروژه محور جاوا اسکریپت، CSS و HTML – طراحی صفحه فرود واکنشگرا در تم آف

کلیک کنید

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

Execution Context چیست؟

مفهوم زمینه اجرا یا «Execution Context» نوعی مفهوم انتزاعی است که به وسیله مشخصات «ECMAScript» برای ردیابی ارزیابی زمان اجرای کدها مورد استفاده می‌گیرد. این مفهوم، محیطی را نشان می‌دهد که کدها در آن اجرا می‌شوند. توجه به تصویر زیر و توضیحات بعدی آن، برای درک «Execution Context» اهمیت دارد.

Execution context چیست

در جاوا اسکریپت، تنها یک زمینه اجرا می‌تواند در زمانی معین فعال باشد و این ویژگی آن را به نوعی زبان «تک‌رشته‌ای» (Single-Threaded) تبدیل می‌کند. این بدان معنا است که در هر لحظه فقط یک فرمان، قابل پردازش خواهد بود. مرورگرها معمولاً زمینه‌های اجرایی را با استفاده از ساختمان داده‌ای به نام «پشته» (Stack) حفظ می‌کنند. پشته بر اساس «ورودی آخر، خروجی اول» (Last In First Out) یا به اختصار «LIFO» عمل می‌کند که در آن آخرین موجودیتی که به پشته وارد شده است، اولین موردی خواهد بود که خارج می‌شود. دلیلش این است که عناصر را فقط می‌توان از بالای پشته وارد یا خارج کرد.

آموزش ساختمان داده ها
فیلم آموزش ساختمان داده ها در تم آف

کلیک کنید

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

زمینه اجرایی تعلیق شده امکان دارد بعداً از جایی که متوقف شده است، اجرای خود را از سر بگیرد. هر زمان که زمینه اجرایی فعلی با دیگری جایگزین شود، نوعی زمینه اجرایی جدید ایجاد و به پشته وارد خواهد شد. در این حالت، زمینه اجرایی وارد شده به زمینه اجرای فعلی تبدیل می‌شود. توجه به تصویر زیر برای درک توضیحات بالا مهم است:

Execution context در برنامه نویسی

مثال Execution Context

برای درک بهتر مفهوم Execution Context در جاوا اسکریپت در ادامه مثالی ارائه شده است.

var x = 10;
function foo(a) {
  var b = 20;

  function bar(c) {
    var d = 30;
    return boop(x + a + b + c + d);
  }

  function boop(e) {
    return e * -1;
  }

  return bar;
}

var moar = foo(5); // Closure  
/* 
  The function below executes the function bar which was returned 
  when we executed the function foo in the line above. The function bar 
  invokes boop, at which point bar gets suspended and boop gets push 
  onto the top of the call stack (see the screenshot below)
*/
moar(15); 

تصویر زیر از صفحه مربوط به کدهای بالا در کنسول مرورگر در ادامه آمده است.

آموزش کلوزر در جاوا اسکریپت

در کدهای فوق، هنگامی که اجرای تابع boop

 کامل شد، از بالای پشته حذف می‌شود. در نتیجه، تابع bar

 از سر گرفته شده و جای خود را به عنوان زمینه اجرای در حال اجرا، تثبیت می‌کند که تصویر زیر مربوط به این مورد است.

آموزش جاوا اسکریپت پیشرفته تم آف

هنگامی که چندین زمینه اجرایی به طور متوالی در حال اجرا هستند، اغلب متوقف خواهند شد و بعداً از سر گرفته می‌شوند. در این وضعیت، نیاز به حفظ وضعیت وجود دارد تا نظم و اجرای این زمینه‌ها به طور موثر مدیریت شود. با توجه به مشخصات «ECMAScript»، هر زمینه اجرا دارای مولفه‌های حالت مختلفی است که پیشرفت کدها را در آن زمینه اجرایی دنبال می‌کند. این مولفه‌ها به صورت موارد زیر هستند:

  1. «وضعیت ارزیابی کد» (Code evaluation state): وضعیت لازم برای انجام، تعلیق و از سرگیری ارزیابی کد مرتبط در زمینه اجرا است.
  2. «تابع» (Function): شی تابعی که به وسیله زمینه اجرا ارزیابی می‌شود (یا اگر زمینه متعلق به اسکریپت یا ماژول باشد، null است).
  3.  «قلمرو» (Realm): مجموعه‌ای از اشیای داخلی، نوعی محیط سراسری ECMAScript، همه کدهای ECMAScript بارگذاری شده در محدوده آن محیط سراسری و سایر وضعیت‌ها و منابع مرتبط.
  4. «محیط واژگانی» (Lexical Environment): به منظور حل ارجاعات شناسه ساخته شده به وسیله کد در زمینه اجرا استفاده می‌شود.
  5. «محیط متغیر» (Variable Environment): نوعی محیط واژگانی که «رکورد محیطی» (Environment Record) آن حاوی پیوندهایی بوده که به وسیله «Variable Statements» در زمینه اجرا ایجاد شده است.
آموزش پروژه محور جاوا اسکریپت – پیاده سازی سبد خرید فروشگاهی با JavaScript
فیلم آموزش پروژه محور جاوا اسکریپت – پیاده سازی سبد خرید فروشگاهی با JavaScript در تم آف

کلیک کنید

در حالی که مولفه‌های بالا ممکن است پیچیده به نظر برسند، متغیر «Lexical Environment» به خصوص به بحث Closure در جاوا اسکریپت مرتبط و یادگیری آن لازم است. این به صراحت ابراز می‌دارد که «ارجاعات شناسه» (Identifier References) ساخته شده به وسیله کد را در زمینه اجرا حل می‌کند. به عبارت ساده‌تر، می‌توان این «شناسه‌ها» را به عنوان متغیر در نظر گرفت.

  • توجه: از نظر فنی، هم محیط متغیر و هم محیط واژگانی برای اجرای Closure در جاوا اسکریپت استفاده می‌شوند. با این حال، برای سادگی، به آن‌ها به طور جمعی به عنوان «محیط» (Environment) اشاره می‌کنیم.

محیط واژگانی

محیط واژگانی نوعی مفهوم است که برای ایجاد رابطه بین شناسه‌ها (متغیرها و توابع) و پیوندهای خاص آن‌ها در کد «ECMAScript» استفاده می‌شود. این محیط از ۲ جزء اصلی تشکیل شده است، یکی «رکورد محیطی» (Environment Record) و دیگری نوعی ارجاع بالقوه تهی به محیط واژگانی بیرونی است. هر زمان که ساختارهای کد خاصی مانند «FunctionDeclaration» ،«BlockStatement» یا «Catch clause» به عنوان یک «TryStatement» ارزیابی شوند، نوعی محیط واژگانی جدید برای مدیریت شناسه‌های مرتبط ایجاد می‌شود.

آموزش برنامه نویسی تایپ اسکریپت TypeScript در جاوا اسکریپت
فیلم آموزش برنامه نویسی تایپ اسکریپت TypeScript در جاوا اسکریپت در تم آف

کلیک کنید

جنبه های کلیدی محیط واژگانی

از مهم‌ترین جنبه‌های کلیدی محیط واژگانی می‌توان به موارد زیر اشاره کرد:

  • برای تعریف ارتباط شناسه‌ها استفاده می‌شود: هدف اولیه محیط واژگانی، ایجاد معنی یا ارتباط شناسه‌ها در کدها است. این مولفه زمینه و اهمیت را برای متغیرها و توابع فراهم می‌کند. به عنوان مثال، در خط کد console.log(x/10)

     ، متغیر (یا شناسه) x

     بدون مکانیزمی برای تعریف معنای آن، بی‌معنی خواهد بود. محیط واژگانی این نقش را به کمک «Environment Record» خود انجام می‌دهد.

  • محیط واژگانی از نوعی رکورد محیطی تشکیل شده است: محیط مسئول نگهداری رکوردی از همه شناسه‌ها و پیوندهای آن‌ها در محیط واژگانی خاص است. هر محیط واژگانی دارای رکورد محیطی اختصاصی خودش است که اطلاعات لازم را برای وضوح شناسه در خود دارد.
  • «ساختار لانه‌سازی واژگانی» (Lexical nesting structure): این جنبه رابطه سلسله مراتبی بین محیط‌های واژگانی را برجسته می‌کند. یک محیط «درونی» (Inner) به محیط «بیرونی» (Outer) اشاره دارد که آن را در بر می‌گیرد و این محیط بیرونی به نوبه خود می‌تواند محیط بیرونی خاص خودش را داشته باشد. بنابراین، محیط می‌تواند به عنوان محیط بیرونی برای چندین محیط درونی عمل کند. محیط «سراسری یا جهانی» (Global) تنها محیط واژگانی است که فاقد محیط بیرونی خواهد بود. برای تجسم این موضوع، می‌توان محیط‌های واژگانی را لایه‌هایی از پیاز در نظر گرفت که محیط جهانی بیرونی‌ترین لایه است. هر لایه بعدی نشان دهنده نوعی محیط تودرتو در داخل خواهد بود.
آموزش کلوزر در جاوا اسکریپت

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

مثالی از مفهوم محیط واژگانی

مثال زیر برای درک مفهوم محیط واژگانی مهم است. محیط واژگانی به صورت زیر ساخته خواهد شد:

LexicalEnvironment = {
  EnvironmentRecord: {
  // Identifier bindings go here
  },
  
  // Reference to the outer environment
  outer: 
};

توجه به این نکته، مهم است که هر بار کد مربوطه ارزیابی می‌شود، نوعی محیط واژگانی جدید ایجاد خواهد شد. این نه تنها در مورد توابع، بلکه در مورد سایر ساختارهای کد مانند دستورات بلوک یا عبارات catch

 نیز صدق می‌کند. با این حال، برای سادگی، در طول این بحث بر روی محیط‌هایی که به وسیله توابع ایجاد می‌شوند تمرکز خواهیم کرد.

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

زنجیره محدوده

در جاوا اسکریپت، مفهوم «دامنه یا محدوده» (Scope) ارتباط نزدیکی با تودرتویی سلسله مراتبی محیط‌های واژگانی دارد و از پیش‌نیازهای درک و یادگیری مفهوم Closure یا بستار در جاوا اسکریپت است.

آموزش ویو جی اس – فریم ورک Vue.js در جاوا اسکریپت JavaScript
فیلم آموزش ویو جی اس – فریم ورک Vue.js در جاوا اسکریپت JavaScript در تم آف

کلیک کنید

هر محیطی به محیط والد خود دسترسی داشته که آن هم به نوبه خود به محیط والد خود دسترسی دارد و زنجیره‌ای از محیط‌ها را تشکیل می‌دهد که به «زنجیره محدوده» (Scope Chain) معروف است. مثال زیر این موضوع را نشان می‌دهد:

var x = 10;

function foo() {
  var y = 20; // free variable
  function bar() {
    var z = 15; // free variable
    return x + y + z;
  }
  return bar;
}

تصویر زیر درک بهتری را از مفهوم مثال مربوطه انتقال می‌دهد.

زنجیره محدوده در جاوا اسکریپت

در نمودار داده شده بالا، تابع bar

 درون تابع foo

 تودرتو قرار دارد و رابطه سلسله مراتبی آن‌ها را نشان می‌دهد. زنجیره محدوده، همچنین به عنوان زنجیره‌ای از محیط‌های مرتبط با تابع شناخته می‌شود، زمانی که شی تابع ایجاد شد، زنجیره محدوده به وجود می‌آید و به آن متصل خواهد شد. این زنجیره محدوده نشان دهنده محدوده ایستا است که به وسیله مکان توابع در کد منبع تعیین می‌شود.

محدوده ایستا و محدوده پویا

زبان‌های دارای دامنه پویا، مانند زبان‌هایی که پیاده‌سازی‌های مبتنی بر پشته دارند، متغیرهای محلی و آرگومان‌های تابع را در پشته ذخیره می‌کنند. وضعیت فعلی پشته برنامه تعیین می‌کند که کدام متغیر در زمانی معین ارجاع داده می‌شود.

آموزش پروژه محور ری اکت جی اس – طراحی وب اپلیکیشن پیشرو PWA با React.js
فیلم آموزش پروژه محور ری اکت جی اس – طراحی وب اپلیکیشن پیشرو PWA با React.js در تم آف

کلیک کنید

در مقابل، دامنه استاتیک بر اساس متغیرهایی است که در زمان ایجاد به آن‌ها ارجاع داده شده که به وسیله ساختار «کد منبع» (Source Code) برنامه تعیین می‌شود. متغیرهایی که مراجع به آن‌ها اشاره می‌کنند بر اساس سلسله مراتب واژگانی کدها ثبت می‌شوند. برای نشان دادن تفاوت بین دامنه پویا و استاتیک، مثال‌های زیر را در نظر بگیرید:

var x = 10;

function foo() {
  var y = x + 5;
  return y;
}
 
function bar() {
  var x = 2;
  return foo();
}
 
function main() {
  foo(); // Static scope: 15; Dynamic scope: 15
  bar(); // Static scope: 15; Dynamic scope: 7
  return 0;
}

در مثال فوق، مشاهده می‌شود که دامنه استاتیک و داینامیک نتایج متفاوتی را هنگام فراخوانی تابع bar

 به دست می‌دهند. با دامنه ایستا، مقدار بازگشتی bar

بر اساس مقدار x

 در زمان ایجاد foo

 است. این به دلیل ساختار ایستا و واژگانی کد منبع است که برای x

در آغاز 10

 و در نتیجه 15

 خواهد بود.

از سوی دیگر، دامنه داینامیک یا پویا با مجموعه‌ای از تعاریف متغیر عمل می‌کند که در زمان اجرا ردیابی می‌شوند. تعیین اینکه از کدام x

استفاده شود بستگی به متغیرهای تعریف شده به صورت داینامیک در محدوده فعلی در زمان اجرا دارد. هنگام اجرای تابع مربوطه، x = 2

 به بالای پشته منتقل می‌شود که منجر به بازیابی خروجی 7

 خواهد شد. حال مثال زیر هم برای درک این مفهوم ضرورت دارد و قطعه کد آن در ادامه آمده است.

var myVar = 100;
 
function foo() {
  console.log(myVar);
}
 
foo(); // Static scope: 100; Dynamic scope: 100
 
(function () {
  var myVar = 50;
  foo(); // Static scope: 100; Dynamic scope: 50
})();

// Higher-order function
(function (arg) {
  var myVar = 1500;
  arg();  // Static scope: 100; Dynamic scope: 1500
})(foo);

در مثال فوق و در دامنه داینامیک یا پویا، متغیر myVar

 بر اساس مقدار آن در مکانی که تابع فراخوانی می‌شود، بازیابی خواهد شد. از طرف دیگر در دامنه استاتیک myVar

را به متغیری که در محدوده دو تابع «IIFE» در هنگام ایجاد ذخیره شده بود، بازیابی می‌کند. محدوه داینامیک اغلب ابهام ایجاد خواهد کرد، زیرا مشخص نیست که متغیر آزاد از کدام محدوده بازیابی می‌شود. در ادامه این مطلب در رابطه با IIFE توضیحاتی ارائه خواهد شد.

آموزش Closure در جاوا اسکریپت

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

آموزش JavaScript ES6 جاوا اسکریپت
فیلم آموزش JavaScript ES6 جاوا اسکریپت در تم آف

کلیک کنید

بینش کلیدی در این رابطه این است که تابع ارجاعی به محیط (یا محدوده) خود دارد که این ویژگی آن را قادر می‌سازد تا آن محیط و متغیرهای تعریف شده در آن را به خاطر بسپارد. مثال زیر برای درک این مفهوم بیان شده مهم است:

var x = 10;

function foo() {
  var y = 20; // free variable
  function bar() {
    var z = 15; // free variable
    return x + y + z;
  }
  return bar;
}

var test = foo();

test(); // 45

بر اساس درک کاربر از محیط‌ها، می‌توان تعریف‌های محیط را برای این مثال به صورت زیر نمایش داد:

GlobalEnvironment = {
  EnvironmentRecord: { 
    // built-in identifiers
    Array: '',
    Object: '',
    // etc..
    
    // custom identifiers
    x: 10
  },
  outer: null
};
 
fooEnvironment = {
  EnvironmentRecord: {
    y: 20,
    bar: ''
  }
  outer: GlobalEnvironment
};

barEnvironment = {
  EnvironmentRecord: {
    z: 15
  }
  outer: fooEnvironment
};

در قطعه کد بالا وقتی تابع test

 فراخوانی می‌شود، مقدار بازگشتی 45

 را بازیابی می‌کند. این به این دلیل است که تابع test

تابع bar

 را فراخوانی خواهد کرد که حتی پس از بازگشت تابع foo

 به متغیر آزاد y دسترسی دارد. تابع bar

دسترسی به y را به وسیله محیط بیرونی خود که محیط foo است حفظ می‌کند. علاوه بر این، تابع bar

می‌تواند به متغیر جهانی x دسترسی داشته باشد، زیرا محیط foo

به محیط جهانی دسترسی دارد. این ساز و کار به عنوان «بازرسی زنجیره‌ای محدوده» (Scope-Chain Lookup) شناخته می‌شود.

مثال هایی برای  Closure در جاوا اسکریپت

در خصوص دامنه داینامیک در مقابل دامنه استاتیک، Closure را نمی‌توان با استفاده از محدوده داینامیک به وسیله پشته داینامیک برای ذخیره متغیرها پیاده‌سازی کرد. اگر از محدوده داینامیک استفاده می‌شد، متغیرها از پشته خارج می‌شدند و با بازگشت تابعی دیگر قابل دسترسی نبودند که این امر در تضاد با تعریف اولیه Closure در جاوا اسکریپت است.

مثال هایی برای Closure در جاوا اسکریپت

در ادامه ٣ مثال از مفهوم Closure در جاوا اسکریپت برای درک بهتر مفاهیم بیان شده رائه می‌شود.

آموزش ویو جی اس – فریم ورک Vue.js در جاوا اسکریپت JavaScript
فیلم آموزش ویو جی اس – فریم ورک Vue.js در جاوا اسکریپت JavaScript در تم آف

کلیک کنید

مثال ١: ایجاد زمینه محصور کننده برای توابع

یکی از نمونه‌های رایجی که در آن خطا اتفاق می‌افتد، زمانی است که کاربر سعی می‌کند متغیر شمارنده را در حلقه For در جاوا اسکریپت با تابعی در داخل حلقه for

مرتبط کند که کد زیر این مفهوم را نشان می‌دهد:

var result = [];
 
for (var i = 0; i 

درک مفاهیمی که تاکنون ذکر شد، تشخیص اشتباه را در اینجا آسان‌تر می‌کند. به طور انتزاعی، محیط در انتهای حلقه for

را می‌توان به صورت زیر نشان داد:

environment: {
  EnvironmentRecord: {
    result: [...],
    i: 5
  },
  outer: null,
}

اشتباه در این است که فرض می‌شود هر تابع در آرایه result

 دارای محدوده جداگانه است. در واقع، هر پنج تابع دارای محیط یا محدوده یکسان هستند. بنابراین، هر زمان که متغیر i

 افزایش یابد، دامنه اشتراک‌گذاری شده را به‌روزرسانی می‌کند و در نتیجه تمام توابع به مقدار نهایی i

دسترسی دارند که با خروج از حلقه for

برابر با 5

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

var result = [];
 
for (var i = 0; i 

با تغییر بالا، مشکل برطرف خواهد شد. روش هوشمندانه دیگر، استفاده از let

 به جای var

 در تعریف متغیرهای جاوا اسکریپت است، زیرا let

دارای محدوده بلوکی است و برای هر بار پیمایش با حلقه for

نوعی شناسه جدید اتصال ایجاد خواهد کرد. کدهای زیر مربوط به این مسئله است.

var result = [];
 
for (let i = 0; i 

مثال ٢: ایجاد Closure جداگانه

در مثال زیر، بررسی خواهد شد که چگونه هر فراخوانی تابع نوعی Closure در جاوا اسکریپت به طور جداگانه ایجاد می‌کند که قطعه کد آن به صورت زیر است.

function iCantThinkOfAName(num, obj) {
  // This array variable, along with the 2 parameters passed in, 
  // are 'captured' by the nested function 'doSomething'
  var array = [1, 2, 3];
  function doSomething(i) {
    num += i;
    array.push(num);
    console.log('num: ' + num);
    console.log('array: ' + array);
    console.log('obj.value: ' + obj.value);
  }
  
  return doSomething;
}

var referenceObject = { value: 10 };
var foo = iCantThinkOfAName(2, referenceObject); // closure #1
var bar = iCantThinkOfAName(6, referenceObject); // closure #2

foo(2); 
/*
  num: 4
  array: 1,2,3,4
  obj.value: 10
*/

bar(2); 
/*
  num: 8
  array: 1,2,3,8
  obj.value: 10
*/

referenceObject.value++;

foo(4);
/*
  num: 8
  array: 1,2,3,4,8
  obj.value: 11
*/

bar(4); 
/*
  num: 12
  array: 1,2,3,8,12
  obj.value: 11
*/

در مثال فوق، می‌توان مشاهده کرد که هر فراخوانی به تابع iCantThinkOfAName

 نوعی Closure

 مجزا ایجاد می‌کند که با foo

 و bar

 نشان داده می‌شود. وقتی این توابع Closure در جاوا اسکریپت متعاقباَ فراخوانی می‌شوند، آن‌ها متغیرها را در Closureهای مربوطه خود به‌روزرسانی می‌کنند. این نشان می‌دهد که متغیرهای هر Closure باقی می‌مانند و حتی پس از بازگشت iCantThinkOfAName

 به تابع doSomething iCantThinkOfAName

 در دسترس خواهند بود.

آموزش کتابخانه Redux در جاوا اسکریپت JS برای مدیریت وضعیت برنامه ها
فیلم آموزش کتابخانه Redux در جاوا اسکریپت JS برای مدیریت وضعیت برنامه ها در تم آف

کلیک کنید

مثال ۳: دسترسی به متغیرهای محیطی

قطعه کد زیر را برای دسترسی به متغیرهای محیطی در نظر می‌گیریم:

function mysteriousCalculator(a, b) {
  var mysteriousVariable = 3;
  return {
    add: function() {
      var result = a + b + mysteriousVariable;
      return toFixedTwoPlaces(result);
    },
    subtract: function() {
      var result = a - b - mysteriousVariable;
      return toFixedTwoPlaces(result);
    }
  };
}

function toFixedTwoPlaces(value) {
  return value.toFixed(2);
}

var myCalculator = mysteriousCalculator(10.01, 2.01);
myCalculator.add(); // 15.02
myCalculator.subtract(); // 5.00

در مثال فوق، تابع MysteriousCalculator

 در محدوده سراسری تعریف شده است و شیئی را با ۲ متد add

 و subtract

 برمی‌گرداند. به طور انتزاعی، محیط‌های این مثال را می‌توان به صورت زیر نشان داد:

GlobalEnvironment = {
  EnvironmentRecord: {
    // built-in identifiers
    Array: '',
    Object: '',
    // etc...

    // custom identifiers
    mysteriousCalculator: '',
    toFixedTwoPlaces: ''
  },
  outer: null
};

mysteriousCalculatorEnvironment = {
  EnvironmentRecord: {
    a: 10.01,
    b: 2.01,
    mysteriousVariable: 3
  },
  outer: GlobalEnvironment
};

addEnvironment = {
  EnvironmentRecord: {
    result: 15.02
  },
  outer: mysteriousCalculatorEnvironment
};

subtractEnvironment = {
  EnvironmentRecord: {
    result: 5.00
  },
  outer: mysteriousCalculatorEnvironment
};

با داشتن ارجاع به محیط تابع MysteriousCalculator

، متدهای add

و subtract

می‌توانند به متغیرهای موجود در آن محیط (a ,b ,mysteriousVariable)

 برای انجام محاسبات خود دسترسی داشته باشند.

فیلم آموزش کلوزر در جاوا اسکریپت

مثال ٤: ارجاع خصوصی به متغیر در محدوده بیرونی

مثال آخر برای نشان دادن کاربرد مهم Closure در جاوا اسکریپت، حفظ ارجاع خصوصی به متغیر در محدوده بیرونی است که برای آن مثالی در ادامه آمده است.

function secretPassword() {
  var password = 'xh38sk';
  return {
    guessPassword: function(guess) {
      if (guess === password) {
        return true;
      } else {
        return false;
      }
    }
  }
}

var passwordGame = secretPassword();
passwordGame.guessPassword('heyisthisit?'); // false
passwordGame.guessPassword('xh38sk'); // true

در مثال فوق، تابع secretPassword

 شیئی را با متد guessPassword

 برمی‌گرداند. متغیر password

 در محدوده تابع secretPassword

تعریف شده است و مستقیماً از خارج قابل دسترسی نیست. تابع guessPassword

به متغیر password

دسترسی دارد و به آن اجازه می‌دهد حدس ارائه شده را با رمز عبور مخفی مقایسه کند. این تضمین می‌کند که رمز عبور خصوصی باقی می‌ماند و نمی‌توان از خارج از Closure در جاوا اسکریپت به آن دسترسی داشت.

Closures و حلقه ها در جاوا اسکریپت

هنگام کار با حلقه‌ها، ایجاد Closure در جاوا اسکریپت می‌تواند منجر به رفتار غیرمنتظره شود.

آموزش ویو جی اس – فریم ورک Vue.js در جاوا اسکریپت JavaScript
فیلم آموزش ویو جی اس – فریم ورک Vue.js در جاوا اسکریپت JavaScript در تم آف

کلیک کنید

برای مثال فرض می‌شود قطعه کد زیر از setTimeout

 در حلقه استفاده می‌کند:

for (var id = 0; id 

در مثال فوق، حلقه سه بار اجرا می‌شود و تابع setTimeout

برای اجرای کد ارائه شده پس از تاخیر مشخص تنظیم شده است. امکان دارد کاربر انتظار داشته باشد که کدها سه بار اجرا شوند و مقدار id مربوط به هر پیمایش حلقه را به صورت زیر چاپ کند.

"seconds: 0"
"seconds: 1"
"seconds: 2"
	

با این حال، به دلیل ماهیت ناهمزمان setTimeout

و closure

 ، رفتار متفاوت است. تابع setTimeout

ارجاع به متغیر id

را از محدوده بیرونی خود می‌گیرد، به این معنی که هر سه تابع setTimeout

ایجاد شده در حلقه، closure

یکسانی دارند. در نتیجه، زمانی که توابع setTimeout

در نهایت اجرا می‌شوند، به مقدار id

 در آن زمان دسترسی پیدا می‌کنند که حداکثر مقداری خواهد بود که پس از حلقه به آن رسیده است.

"seconds: 3"
"seconds: 3"
"seconds: 3"

خروجی ثبت شده در کنسول آن گونه‌ نیست که انتظار می‌رود. در عوض، مقدار id

مقدار نهایی است که در حلقه وجود داشت.

کلمه کلیدی let و Closure در جاوا اسکریپت

برای رسیدگی به مشکل مثال قبل، همان‌طور که پیش از این نیز در این مطلب به آن اشاره شد، می‌توان از کلمه کلیدی let

معرفی شده در «جاوا اسکریپت ES6» استفاده کرد. با استفاده از let

 ، می‌توان نوعی محدوده بلوک جدید برای هر پیمایش حلقه ایجاد و از رفتار مورد انتظار اطمینان حاصل کرد.

آموزش JavaScript ES6 جاوا اسکریپت
فیلم آموزش JavaScript ES6 جاوا اسکریپت در تم آف

کلیک کنید

مثال زیر نحوه انجام این کار را بیشتر شرح می‌دهد.

for (let id = 0; id 

در کد بالا، کلمه کلیدی let

برای اعلان متغیر id

در داخل حلقه for

 استفاده می‌شود. این کلمه کلیدی نوعی محدوده بلوک جدید برای هر پیمایش ایجاد می‌کند و به تابع setTimeout

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

"seconds: 0"
"seconds: 1"
"seconds: 2"

با استفاده از کلمه کلیدی let

، اطمینان حاصل می‌شود که هر Closure در جاوا اسکریپت به وسیله تابع setTimeout

کپی خود را از مقدار id

دریافت می‌کند و اثرات نامطلوب اشتراک‌گذاری محدوده در همه پیمایش‌ها از بین می‌رود.

مقالات تخصصی جاوا اسکریپت

IIFE و Closure در جاوا اسکریپت

نوعی رویکرد جایگزین برای مدیریت Closure در جاوا اسکریپت در حلقه و اجتناب از مشکلی که در بالا ذکر شد، استفاده از سینتکس IIFE مخفف «Immediately Invoked Function Expression» به معنای «گزاره فراخوانی فوری تابع» است. با قرار دادن کد در تابع و فراخوانی فوری آن، می‌توان اطمینان حاصل کرد که هر پیمایش حلقه نوعی محدوده تابع مجزا با متغیرهای خاص خود را ایجاد می‌کند.

آموزش مقدماتی نود جی اس Node.js
فیلم آموزش مقدماتی نود جی اس Node.js در تم آف

کلیک کنید

این کار به تابع setTimeout

اجازه می‌دهد تا مقدار صحیح متغیر id

را در هر پیمایش دریافت کند. مثال زیر برای درک راه‌حل «IIFE» مهم است:

for (var id = 1; id 

در کد بالا، تابع (function(id) { … })(id)

 نشان دهنده IIFE است. این رویکرد فوراً تابع را با پارامتر id

فراخوانی کرده و محدوده تابع جدید را برای هر پیمایش حلقه ایجاد می‌کند. سپس تابع setTimeout

در داخل IIFE مقدار صحیح id

را در closure

 خود می‌گیرد.

در حالی که رویکرد IIFE می‌تواند در سناریوهای خاصی موثر باشد، شایان ذکر است که راه‌حل «ES6» با استفاده از let

 راه‌حل تمیزتر و مختصرتری برای مشکل ذکر شده ارائه می‌دهد. کلمه کلیدی let به طور خودکار محدوده بلوک را ایجاد می‌کند و در بیشتر موارد نیاز به IIFE را از بین می‌برد. با این حال، ممکن است شرایطی وجود داشته باشد که رویکرد IIFE بهتر باشد که بحث در مورد آن بسیار تخصصی است.

Closure در جاوا اسکریپت چگونه ایجاد می شود؟

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

آموزش آلپاین در جاوا اسکریپت – فریم ورک Alpine در JavaScript
فیلم آموزش آلپاین در جاوا اسکریپت – فریم ورک Alpine در JavaScript در تم آف

کلیک کنید

کاربرد Closure در Javascript چیست؟

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

Closure در Javascript

چگونه Closure را عمیق یاد بگیریم؟

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

مجموعه آموزش طراحی و برنامه ‌نویسی سایت
فیلم مجموعه آموزش طراحی و برنامه ‌نویسی سایت در تم آف

کلیک کنید

سخن پایانی

در این مطلب از «مجله تم آف» در رابطه با Closure در جاوا اسکریپت و ابعاد مختلف پیرامون این مفهوم اطلاعاتی مطلوب به همراه مثال‌های عملی ارائه شد.

مجموعه آموزش جاوا اسکریپت (JavaScript)
فیلم مجموعه آموزش جاوا اسکریپت (JavaScript) در تم آف

کلیک کنید

در این مطلب، Closureها در جاوا اسکریپت در سناریوهای مختلفی به کار گرفته شدند و در کنار آن، مفاهیم زمینه‌های اجرا، محیط‌ها، مفاهیم سطح بالا و غیره نیز ارائه شد.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.