44 - جاوااسکریپت: Immediately Invoked Functions Expressions

تفاوت بین function statement و function expression را دیدیم. function statement به عنوان یک statement جدید دیده میشود که کلمه function اولین کلمه آن است:// function statementfunction greet (name) { console.log('Hello '+name); }greet('John');جاوااسکریپت این کد را میبیند و آن را در حافظه قرار میدهد ولی وقتی به این خط کد میرسد در واقع چیزی اجرا نمیکند و برای اجرای آن باید تابع را invoke کنیم. اما میتوانیم از function expression برای ست کردن یک متغیر مساوی با آن استفاده کنیم و یک تابع anonymous داشته باشیم:// using a function expressionvar greetFunc = function(name) { console.log('Hello '+name); };greetFunc('John');در کد بالا قسمت function(name) {console.log('Hello '+name);} یک expression است و ابتدا در حافظه قرار نمیگیرد. موقع اجرا وقتی به این خط از کد میرسد، این شیء تابع را on the fly ایجاد میکند. در این کد قسمت function(name) {console.log('Hello '+name);} به نوعی function literal است و داریم on the fly تابع میسازیم. یک کار خاص که میتونیم با تابع ها انجام دهیم و آنها را یک شیء بخصوص کرده این است که یک ویژگی CODE دارند و میتوانیم آن را invoke کنیم. میتوانیم تابع را on the fly م invoke کنیم. یعنی میتوانیم در کد بالا به جای فراخوانی greetFunc بعد از تعریف تابع، میتوانیم یک مقدار را ست کنیم و همون موقع که تابع ایجاد میشود آن را فرخوانی کنیم:// using an Immediately Invoked Function IIFE)var greetFunc = function(name) { console.log('Hello '+name); }('John');به محض ایجاد شدن تابع، فراخوانی میشود. اگر داشته باشیم:var greetFunc = function(name) { return 'Hello '+name; };console.log(greeting);خروجی به ما function(name) {return 'Hello '+name;} را برمیگرداند. اگر بگوییم:var greetFunc = function(name) { return 'Hello '+name; };console.log(greeting('John')); خروجی Hello John را برمیگرداند. اگر داشته باشیم:var greeting = function(name) { return 'Hello '+name; }('John');console.log(greeting);آنچه اتفاق می افتد این است که با function expression بالا یک شیء تابع ایجاد میشود و سپس invoke میشود و مقدار آن برگردانده میشود و در greeting قرار داده میشود. بنابراین با اجرای کد بالا خروجی Hello John میدهد. بنابراین متغیر greeting مقدار تابع را ذخیره میکند، نه خود تابع را. اگر بخواهیم greeting را invoke کنیم به ما خطا میدهد: string is not a function.حتی میتوانیم IIFE را تنها هم داشته باشیم. اگر در کد بنویسیم:3;معتبر است. ; هم گذاشتیم تا مطمئن بشیم که syntax parser متوجه شده خط تمام شده است. این خط کد هیچ خطایی به ما نمیدهد. چون یک js expression معتبر است. expression فقط اجرا میشود و نیازی به ذخیره هیچ مقداری ندارد. این خط کد هم همین طور است. هیچ کاری باهاش نمیشه ولی معتبره. برای کد زیر حاوی یک رشته همین طور است:"I am a string";یا کد زیر حاوی یک شیء:{ name: 'John' };فقط یک خط کد است که اجرا میشود. در مورد تابع چطور؟ اگر فقط داشته باشیم:function(name) { return 'Hello'+name; }اگر بخواهم که این کد یک function expression باشید، باید on the fly ایجاد شود. اگر این کد را اجرا کنیم خطا میدهد: unexpected token (. چون syntax parser کلمه کلیدی function را اول یک خط جدید میبیند و انتظار دارد که این یک function statement باشید و منتظر یک اسم برای تابع است و نمیتواند anonymous باشد. بنابراین سینتکس بالا اشتباه است. اگر فقط یک تابع بنویسیم، یک function expression نیست بلکه یک function statement است. اما این چیزی نبود که میخواستیم. میخواستم مثل خط کد 3; فقط اونجا باشد. چطور میتوانیم به syntax parser بفهمانیم که قصد نوشتن یک function statement نداشته ایم؟ باید مطمئن شویم که کلمه function اولین چیزی که در خط جدید میبیند نباشد. چند راه برای این کار وجود دارد. اما پذیرفته شده ترین روش (به دلیل سادگی و کمترین میزان تایپ) این است که کل تابع را در پرانتز wrap کنیم:(function(name) { return 'Hello'+name; })این کار را زمانی انجام میدهیم که function expression میخواهیم. پرانتز در js عملگر است. از پرانتزها فقط با expression ها کار میکنیم که مقداری را برمیگردانند. هیچ وقت داخل پرانتز statement نمیگذاریم (مثلا (if(...)) را نداریم). چون موتور js میداند که هر چیزی که داخل پرانتز باید expression باشد، فرض میکند که این تابع یک function expression است که on the fly ایجادش کرده ایم. اگر کد بالا را اجرا کنیم خطایی نمیدهد. فقط یک تابع داریم که داخل کد قرار گرفته و از آن استفاده نمیکنیم. ولی سینتکسش معتبر است.(function(name) {var greeting = 'Hello';console.log(greeting + ' ' name);});حال اگر کد بالا را اجرا کنیم چه اتفاقی می افتد؟ قبلا که مثلا 3; را اجرا کردیم هیچ اتفاقی نیفتاد. اما در مورد شیء تابع چطور؟ ما میتوانیم آن را invoke کنیم. بنابراین بعد از تابع میتوانیم آن را invoke کنیم. این هم یک IIFE است و معمولا به همین شکل IIFE را میبینیم:var firstname = 'John';(function(name) {var greeting = 'Hello';console.log(greeting + ' ' name);}(firstname));یک function expression که در پرانتز wrap شده (موتور js میفهمد که یک function statement نیست و یک function expression است که وقتی کد اجرا شود on the fly ایجاد میشود) و سپس اضافه کردن () به آخر تا اجرا شود. یعنی کاملا همزمان یک تابع را ایجاد و اجرا کردیم. این یک مثال کلاسیک از IIFE است. تابع را داخل پرانتز گذاشتیم، آن را on the fly ایجاد کردیم و invoke کردیم. چنین فرمتی در هر framework یا کتاخانه اصلی استفاده میشود. این تابع on the fly ایجاد میشود و همان موقع اجرا میشود.در مورد کد بالا میتوانیم پرانتزهای مربوط به invoke کردن را داخل پرانتز اصلی یا بیرون از آن بگذاریم:(function(name) {...})(firstname);یا:(function(name) {...}(firstname));هر دوی این کدها مثل هم کار میکنند اما درست این است که قسمت invoke داخل پرانتز اصلی باشد. هر کدام از دو روش را که انتخاب میکنید، همیشه از همان استفاده کنید.45 - جاوااسکریپت: IIFEs and Safe Codeکد IIFE زیر را در نظر بگیرید:// IIFE(function(name) {var greeting = 'Hello';console.log(greeting + ' ' name);}('John')); // IIFEبا اجرای این کد خروجی Hello John را خواهیم داشت. میخواهیم ببنیم پشت صحنه چه اتفاقی می افتد. در execution stack چه اتفاقی می افتد؟ وقتی این کد تازه لود میشود global execution context داریم و هیچی داخل آن نیست چون هیچ متغیر یا function statement نداریم که hoisted شود. سپس وقتی به این خط از کد که یک IIFE است میرسد، به محض اینکه به قسمت function expression میرسد آن شیء تابع را در global execution context میسازد که anonymous است: () یک تابع بدون اسم با کدهایی داخلش است. سپس قسمت پرانتز ('John') را میبیند که دارد تابع را invoke میکند. پس یک execution context جدید برای تابعی که on the fly ایجاد شده درست میشود. سپس کد داخل آن تابع خط به خط اجرا میشود. متغیر greeting هم داخل execution context variable environment ش قرار میگیرد. این متغیر به قسمت global اضافه نمیشود. چون داریم تابع را اجرا میکنیم، execution context خود تابع را داریم. بنابراین هر متغیری که داخل تابع تعریف میشود به global ارتباطی ندارد. این ویژگی باعث میشود که IIFE روش خیلی مفیدی برای کدنویسی باشد.برای مثال:var greeting = 'Hola';(function(name) {var greeting = 'Hello';console.log(greeting + ' ' name);}('John')); این کد خروجی Hello John را میدهد. اگر در کنسول greeting را بنویسیم مقدار Hola را برمیگرداند. در این مثال global execution context بعد از اینکه اولین خط اجرا میشود، در حافظه مقدار متغیر greeting را Hola میگذارد. سپس کل IIFE دیده میشود و function expression باعث میشود که تابع ایجاد شود و ('John') باعث میشود که invoke شود و execution context جدید ایجاد میشود. وقتی به خط اول کد داخل تابع میرسیم، مقدار متغیر greeting داخل execution context تابع برابر با Hello میشود. پس دو تا متغیر greeting در قسمت های مجزا داریم. پس مطمئن هستیم که کدی که در IIFE م wrap شده، تناقض یا تاثیری روی هیچ کد دیگری در برنامه ندارد و کدمان safe است. وقتی به کد داخل framework ها و کتابخانه ها نگاه میکنیم، اولین چیزی که بالا میبینیم یک پرانتز و function است و انتهای کد پرانتز بسته شده. این کار برای این است که کل کد را در یک IIFE م wrap کند. ما هم میتوانیم همین روش را برای کد reusable خودمان استفاده کنیم تا مطمئن باشیم کدمان با کدهای دیگر تصادمی نخواهد داشت و مطمئن باشیم که اشتباهی چیزی را داخل global object قرار نداده ایم. اما اگر بخواهیم که چیزی را در global قرار دهیم چه؟ میتوانیم این کار را با پاس دادن پارامترها انجام دهیم.همان طور که قبلا گفته شد شیء ها by reference پاس داده میشوند. پس اگر میخواهیم که به global object دسترسی داشته باشیم، آن را به تابع مان پاس میدهیم:var greeting = 'Hola';(function(global, name) {var greeting = 'Hello';global.greeting = 'Hello';console.log(greeting + ' ' name);}(window, 'John')); پس در این کد یک reference به شیء global را به IIFE خود پاس دادیم. پس در این حالت میتوانیم آگاهانه و عمدی تغییری در global object ایجاد کنیم. اینجا اسمش را global گذاشتیم چون ممکن است بخواهیم که این کد روی سرور هم reusable باشد و global object همان window نباشد و چیز دیگری باشد. اسمش را global گذاشته ایم و یک reference به آن داریم. با اجرای کد بالا خواهیم داشت: Hello John و اگر در کنسول greeting را بنویسیم به ما خروجی Hello را میدهد. یعنی کد داخل تابع روی global تاثیر گذاشته. پس ممکن است شیء هایی داخل تابع ایجاد کنید و بخواهید همه جای کد قابل دسترس باشند. در این حالت میتوانیم آنها را به global object بچسبانیم، اما این کار را عمدی انجام میدهیم، نه به اشتباه. فرمت IIFE تاثیرگذاری روی global object را سخت و آگاهانه کرده تا کد ایمن باشد.