اطلاعات این مقاله برای فهمیدن اسکریپتهای قدیمی مفید است.
ما کدهای جدید را اینگونه نمینویسیم.
دقیقا در فصل اول درباره متغیرها، ما سه راه تعریف متغیر را معرفی کردیم:
let
const
var
تعریف متغیر توسط var
مانند let
است. اکثر اوقات ما میتوانیم let
را با var
جایگزین کنیم یا برعکس و توقع داشته باشیم که دستورات کار کنند:
var message = "سلام";
alert(message); // سلام
اما از درون، var
گونهای بسیار متفاوت است که از زمانهای قدیم وجود دارد. به طور کلی در اسکریپتهای جدید استفاده نمیشود ولی هنوز هم در اسکریپتهای قدیمی کمین کرده است.
اگر شما قرار نیست چنین اسکریپتهایی را ببینید میتوانید این فصل را رد کنید یا مطالعه آن را عقب بیاندازید.
از سویی دیگر، زمانی که اسکریپتهای قدیمی را از var
به let
کوچ میدهیم، دانستن تفاوتها برای جلوگیری از ارورهای عجیب مهم است.
کلمهی “var” محدوده بلوک ندارد
متغیرهایی که با var
تعریف شدهاند، یا محدوده تابع دارند یا محدوده گلوبال. آنها در این بلوکها قابل رویت هستند.
برای مثال:
if (true) {
var test = true; // استفاده کنید "var" از "let" به جای
}
alert(test); // true ،هم باقی میماند if متغیر بعد از
چون var
بلوکهای کد را نادیده میگیرد، ما یک متغیر گلوبال test
خواهیم داشت.
اگر ما به جای var test
از let test
استفاده میکردیم، سپس متغیر فقط درون if
قابل رویت بود:
if (true) {
let test = true; // "let" استفاده از
}
alert(test); // ReferenceError: test is not defined
همین مورد برای حلقهها هم صدق میکند: var
نمیتواند در محدوده بلوک حلقه باشد:
for (var i = 0; i < 10; i++) {
var one = 1;
// ...
}
alert(i); // 10 ،بعد از حلقه قابل رویت است چون یک متغیر گلوبال است "i"
alert(one); // 1 ،بعد از حلقه قابل رویت است چون یک متغیر گلوبال است "one"
اگر یک بلوک کد درون تابع باشد، سپس var
یک متغیر در سطح تابع میشود:
function sayHi() {
if (true) {
var phrase = "سلام";
}
alert(phrase); // کار میکند
}
sayHi();
alert(phrase); // ReferenceError: phrase is not defined
همانطور که میبینیم، var
از درون if
، for
یا بقیه بلوکهای کد بیرون میآید. به این دلیل که در زمانهای قدیم، بلوکها در جاوااسکریپت محیطهای لغوی نداشتند و var
یک باقیمانده از آن است.
کلمه “var” تعریفهای دوباره را قبول میکند
اگر ما با let
متغیری یکسان را دوبار در محدوده بلوک یکسان تعریف کنیم، یک ارور ایجاد میشود:
let user;
let user; // SyntaxError: 'user' has already been declared
با var
ما میتوانیم یک متغیر را هر چند بار که بخواهیم دوباره تعریف کنیم. اگر ما از var
همراه با یک متغیر از قبل تعریف شده استفاده کنیم، نادیده گرفته میشود:
var user = "Pete";
var user = "John"; // کاری انجام نمیدهد (از قبل تعریف شده) "var" این
// ...اروری ایجاد نمیکند
alert(user); // John
متغیرهای “var” میتوانند پایین محل استفادهشان تعریف شوند
متغیرهای تعریف شده با var
زمانی که اجرای تابع شروع میشود (یا برای متغیرهای گلوبال زمانی که اسکریپت شروع میشود) پردازش میشوند.
به عبارتی دیگر، متغیرهای var
بدون توجه به محل تعریف آنها، از زمانی که اجرای تابع شروع میشود تعریف میشوند (با فرض اینکه تعریف کردن درون تابع تودرتو نیست).
پس این کد:
function sayHi() {
phrase = "سلام";
alert(phrase);
var phrase;
}
sayHi();
…از لحاظ فنی با این کد برابر است (عبارت var phrase
را بالا بردیم):
function sayHi() {
var phrase;
phrase = "سلام";
alert(phrase);
}
sayHi();
…حتی با این هم برابر است (به یاد داشته باشید که بلوکهای کد نادیده گرفته میشوند):
function sayHi() {
phrase = "سلام"; // (*)
if (false) {
var phrase;
}
alert(phrase);
}
sayHi();
افراد به آن «بالا بردن» هم میگویند چون تمام var
ها به بالای تابع «سعود میکنند».
پس در مثال بالا، شاخه if (false)
هیچوقت اجرا نمیشود اما اصلا مهم نیست. var
که درون آن است در ابتدای اجرای تابع پردازش میشود پس هنگام اجرای (*)
متغیر وجود دارد.
تعریف متغیر بالا میرود اما مقداردهیها نه.
این موضوع یک مثال بهتر نمایش داده میشود:
function sayHi() {
alert(phrase);
var phrase = "سلام";
}
sayHi();
خط var phrase = "سلام"
در خودش دو کار انجام میدهد:
- تعریف متغیر با
var
. - مقداردهی متغیر با
=
.
تعریف متغیر در ابتدای اجرای تابع پردازش میشود («بالا میرود») اما مقداردهی همیشه در جایی که وجود دارد انجام میشود. پس کد بالا اساسا مانند کد پایین کار میکند:
function sayHi() {
var phrase; // ...تعریف متغیر در ابتدا انجام میشود
alert(phrase); // undefined
phrase = "Hello"; // ...مقداردهی - زمانی که اجرا به آن میرسد
}
sayHi();
چون تمام تعریف متغیرهای var
در ابتدای تابع پردازش میشوند، ما میتوانیم به آنها در هر زمانی رجوع کنیم. اما متغیرها تا زمان مقداردهی برابر با undefined هستند.
در هر دو مثال بالا، alert
بدون هیچ اروری اجرا میشود چون متغیر phrase
وجود دارد. اما مقدار آن هنوز تخصیص داده نشده است پس undefined
را نشان میشود.
روش IIFE
در گذشته، چون فقط var
وجود داشت و قابلیت رویت در بلوک کد را ندارد، برنامهنویسان برای تقلید آن راهی ایجاد کردند. کاری کردند را «فراخوانی بلافاصلهی function expressionها (immediately-invoked function expressions)» است (خلاصه شده به عنوان IIFE).
امروزه از این روش نباید استفاده کنیم اما میتوانید آنها را در اسکریپتهای قدیمی پیدا کنید.
یک IIFE اینگونه به نظر میرسد:
(function() {
var message = "سلام";
alert(message); // سلام
})();
اینجا، یک Function Expression ساخته شده و بلافاصله فراخوانی شده است. پس کد هر چه سریعتر اجرا میشود و متغیرهای مخصصوص خودش را دارد.
Function Expression درون پرانتز قرار گرفته است (function {...})
چون زمانی که موتور جاوااسکریپت در کد اصلی با "function"
مواجه میشود، آن را به عنوان ابتدای یک Function Declaration فرض میکند. اما یک Function Declaration باید اسم داشته باشد پس کدی به این شکل ارور ایجاد میکند:
// سعی میکنیم یک تابع را تعریف و بلافاصله فراخوانی کنیم
function() { // <-- SyntaxError: Function statements require a function name
var message = "سلام";
alert(message); // سلام
}();
حتی اگر بگوییم: «مشکلی نیست، بیایید یک اسم اضافه کنیم» باز هم کار نمیکند چون جاوااسکریپت اجازه نمیدهد که Function Declarationها بلافاصله فراخوانی شوند:
// به دلیل وجود پرانتزهای پایین ارور سینتکس دریافت میکنیم
function go() {
}(); // <-- را بلافاصله فراخوانی کرد Function Declaration نمیتوان
پس پرانتزهای دور تابع یک ترفند است تا به جاوااسکریپت نشان دهیم که تابع در زمینهی عبارتی دیگر ساخته شده و از این رو یک Function Expression است: به اسم نیازی ندارد و میتواند بلافاصله فراخوانی شود.
برای اینکه به جاوااسکریپت بگوییم که منظورمان یک Function Expression است راههای دیگری در کنار پرانتزها وجود دارد:
// IIFE راههایی برای ایجاد
(function() {
alert("پرانتزهای دور تابع");
})();
(function() {
alert("پرانتزهای دور تمام عبارت");
}());
!function() {
alert("عملگر بیتی NOT عبارت را آغاز میکند");
}();
+function() {
alert("عملگر مثبت یگانه عبارت را آغاز میکند");
}();
در تمام موارد بالا ما یک Function Expression تعریف میکنیم و آن را بلافاصله فراخوانی میکنیم. بیایید دوباره به این موضوع توجه کنیم: امروزه هیج دلیلی برای نوشتن چنین کدی وجود ندارد.
خلاصه
بین var
و let/const
دو تفاوت اصلی وجود دارد:
- متغیرهای
var
محدودیت بلوک ندارند، قابلیت رویت آنها یا محدود به تابع کنونی است یا اگر بیرون از تابع تعریف شده باشند محدود به گلوبال است. - تعریف متغیر با
var
در ابتدای اجرای تابع پردازش میشود (یا برای متغیرهای گلوبال در ابتدای اسکریپت).
یک تفاوت جزئی دیگر در رابطه با شیء گلوبال وجود دارد که در فصل بعدی آن را بیان میکنیم.
این تفاوتهای در اکثر اوقات var
را نسبت به let
بدتر جلوه میدهند. متغیرهای سطح بلوک چیز خیلی خوبی هستند. به این دلیل است که خیلی قبلتر let
در استاندارد معرفی شد و حالا برای تعریف متغیر روش اصلی است (در کنار const
).