48 – جاوااسکریپت: Function Factories

در این بخش در مورد استفاده از فواید closure برای ایجاد الگوهایی که بدون closure غیرممکن است صحبت میکنیم: function factories. تا به حال دیدم که چگونه میتوانیم از ویژگی های زبان js استفاده کنیم تا به تابع اجازه دهیم در شرایط مختلفی با مقادیر پیش فرض مختلف فراخوانی شود. در این مورد در بخش function overloading صحبت شد. اینجا میخواهیم در مورد الگوهای قوی تر و منعطف تری صحبت کنیم که از closure استفاده میکنند. یک factory یعنی تابعی که چیزهایی را برای ما ایجاد یا return میکند. در مثال زیر تابع makeGreeting تابعی را return میکند. میتوانیم در قسمت return به تابع اسم greet را هم بدهیم ولی نیازی نیست:function makeGreeting(language) {return function(firstname, lastname) {if (language==’en’) { console.log(‘Hello’+’ ‘+firstname+’ ‘+lastname); }if (language==’es’) { console.log(‘Hola’+’ ‘+firstname+’ ‘+lastname); }}}var greetEnglish = makeGreeting(‘en’);var greetSpanish = makeGreeting(‘es’);تفاوت این تابع با تابع مشابهی که قبلا گفته شد این است که به جای پاس دادن پارامتر language به تابع return شده، این پارامتر را به تابع بیرونی پاس داده ایم. پس language در closure میماند و زمانی که تابع return شده را اجرا میکنیم، مقدار language را با scope chain پیدا میکند حتی اگر اجرای تابع makeGreeting تمام شده باشد. وقتی کد بالا اجرا میشود و makeGreeting اجرا میشود، یک تابع را برمیگرداند که outer reference آن مقدار language را به ما نشان میدهد. دفعه دوم که makeGreeting اجرا میشود یک execution context دیگه ایجاد میشه که داخل آن مقدار language چیز دیگه ای هست. پس با اینکه از نظر lexical این دو تابع در یک جا نوشته شده اند، به دو جای مختلف از حافظه اشاره میکنند، چون در دو execution context مختلف ایجاد شده اند. پس greetEnglish یک شیء تابع است که closure آن به language=’en’ اشاره میکند و greetSpanish یک شیء تابع است که closure آن به یک execution context دیگر اشاره میکند و در آن language=’es’ است.بنابراین با اینکه یک تابع makeGreeting داریم، هر بار که اجرا میشود یک execution context جدید و حافظه جدید ایجاد میکند. پس اگر در انتهای کد اضافه کنیم:greetEnglish(‘John’, ‘Doe’);greetSpanish(‘John’, ‘Doe’);خروجی میشود:Hello John DoeHola John Doeتابع makeGreeting به عنوان یک function factory عمل میکند. در آن از خصوصیات closure ها برای ست کردن مقدار پارامتر در تابعی که return میکند استفاده کردیم. سپس تابع greetEnglish را که همیشه از مقدار ‘en’ استفاده میکند تعریف کردیم. اما موقع فراخوانی تابع greetEnglish دیگر به مقدار ‘en’ به طور مستقیم دسترسی نداریم. میتوانیم این کد را از دید دیگری بررسی کنیم:وقتی اجرای کد شروع میشود global execution context را داریم که در آن متغیرهای greetEnglish و greetSpanish و تابع makeGreeting() قرار دارد. وقتی به خط کد var greetEnglish = makeGreeting(‘en’); میرسیم برای تابع makeGreeting یک execution context ایجاد میشود که در آن language=’en’ پاس داده میشود. سپس یک تابع را برمیگرداند که در greetEnglish ذخیره میشود و execution contex آن حذف میشود ولی حافظه آن هنوز در دسترس است. سپس در خط کد var greetSpanish = makeGreeting(‘es’); تابع makeGreeting دوباره فراخوانی میشود و execution context ایجاد میشود که در آن language=’es’ است. دوباره تابعی برگردانده شده و تمام میشود و execution context حذف میشود. پس الان در حافظه دو جای مختلف داریم که مقدار language در آنها هست و قبلا در execution context ای بوده اند. پس وقتی به خط کد greetEnglish(‘John’, ‘Doe’); میرسیم، تابعی که return شده بود را فراخوانی میکنیم و یک execution context جدید ایجاد میشود که در حافظه آن firsname=’John’ و lastname=’Doe’ قرار دارد. موتور js میداند که outer reference این تابع به اولین اجرای تابع پدر برمیگردد، به جایی که در آن ایجاد شده است. موقع اجرای خط کد greetSpanish(‘John’, ‘Doe’); هم به همین ترتیب است. این تابع در دومین بار فراخوانی ایجاد شده، پس outer reference آن به دومین execution context برمیگردد و closure خودش را دارد.پس هر بار که تابعی اجرا میشود execution context مجزایی ایجاد میشود و هر تابعی که در آن ایجاد میشود به همان execution context اشاره میکند.این کد کار فراخوانی تابع را خیلی راحت کرده است. چون دیگر نیازی نیست که هر بار یک پارامتر یکسان را به تابع پاس دهیم. فقط با استفاده از closure ها تابع جدیدی ایجاد کردیم که پارامترهایی را به صورت پیش فرض دارد. پس تابعی که return شده به مقدار language در همان لحظه که تولید شده دسترسی دارد.49 – جاوااسکریپت: Closures, Callbacksوقتی داریم از تابعی مثل setTimeout یا jQuery event استفاده میکنیم، داریم تمام مدت از closure ها استفاده میکنیم. Function sayHiLater() {var greeting = ‘Hi’;setTimeout(function(){ console.log(greeting); }, 3000);}sayHiLater();تابع setTimeout یک تابع built-in در js است. وقتی بخواهیم چیزی بعد از مدتی اتفاق بیفتد از آن استفاده میکنیم. این تابع یک تابع میگیرد که چیزی که قرار است اتفاق بیفتد را تعیین میکند و همچنین مدت زمانی را که باید صبر کند میگیرد. این کد بعد از 3 ثانیه خروجی Hi را میدهد. تابع setTimeout به خارج از بروزر میرود و صبر میکند تا event اتفاق بیفتد (3 ثانیه بگذرد). سپس تمام میشود. سپس موتور js جستجو میکند که آیا تابع دیگری در حال listening هست یا نه. تابع function(){console.log(greeting);} را پیدا میکند و آن را اجرا میکند. اما مقدار متغیر greeting داخل این تابع وجود ندارد و sayHiLater هم تمام شده ولی چون در scope chian مان closure داریم، بعد از این 3 ثانیه هنوز به greeting دسترسی داریم. اجرای تابع sayHiLater فقط چند میلی ثانیه طول میکشد ولی حتی بعد از چندین ثانیه هنوز به مقدار greeting دسترسی داریم. پس در این مثال از closure و function expression استفاده کردیم.در jQuery هم همین طور است. برای مثال در کد زیر:// jQuery uses function expressions and first-class functions$(“button”).click(function(){});این click یک تابع است که یک تابع دیگر را میپذیرد تا وقتی event اتفاق افتاد آن را اجرا کند. پس در اینجا از first class function استفاده کرده ایم. یعنی تابع را به عنوان یک پارامتر پاس داده ایم. از function expression استفاده کرده ایم تا تابع مان را تعریف کنیم.زمانی که از jQuery یا js استفاده میکنیم از فواید first class function و closure ها استفاده میکنیم. تابع هایی مثل function(){ console.log(greeting); } که بعد از اجرای تابعی، کاری انجام میدهند و به تابع اول پاس داده میشوند callback نام دارند. تابع callback را به تابعی میدهیم تا وقتی کارش تمام شد، آن را اجرا کند.تعریف تابع callback: تابعی که به یک تابع دیگر داده میشود، تا بعد از تمام شدن تابع دیگر اجرا شود.function tellMeWhenDone(callback){var a = 1000; // some codes here to docallback();}tellMeWhenDone(function(){ console.log(‘I am done’); });tellMeWhenDone(function(){ alert(‘I am done’); });در مثال بالا یک تابع را به tellMeWhenDone پاس میدهیم تا وقتی تابع tellMeWhenDone را اجرا کردیم، خودش آن را فراخوانی کند.