۱۴ نوامبر ۲۰۲۲

‌کلمه‌ی "var" قدیمی

این مقاله برای فهمیدن اسکریپت‌های قدیمی است

اطلاعات این مقاله برای فهمیدن اسکریپت‌های قدیمی مفید است.

ما کدهای جدید را اینگونه نمی‌نویسیم.

دقیقا در فصل اول درباره متغیرها، ما سه راه تعریف متغیر را معرفی کردیم:

  1. let
  2. const
  3. 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 = "سلام" در خودش دو کار انجام می‌دهد:

  1. تعریف متغیر با var.
  2. مقداردهی متغیر با =.

تعریف متغیر در ابتدای اجرای تابع پردازش می‌شود («بالا می‌رود») اما مقداردهی همیشه در جایی که وجود دارد انجام می‌شود. پس کد بالا اساسا مانند کد پایین کار می‌کند:

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 دو تفاوت اصلی وجود دارد:

  1. متغیرهای var محدودیت بلوک ندارند، قابلیت رویت آن‌ها یا محدود به تابع کنونی است یا اگر بیرون از تابع تعریف شده باشند محدود به گلوبال است.
  2. تعریف متغیر با var در ابتدای اجرای تابع پردازش می‌شود (یا برای متغیرهای گلوبال در ابتدای اسکریپت).

یک تفاوت جزئی دیگر در رابطه با شیء گلوبال وجود دارد که در فصل بعدی آن را بیان می‌کنیم.

این تفاوت‌های در اکثر اوقات var را نسبت به let بدتر جلوه می‌دهند. متغیرهای سطح بلوک چیز خیلی خوبی هستند. به این دلیل است که خیلی قبل‌تر let در استاندارد معرفی شد و حالا برای تعریف متغیر روش اصلی است (در کنار const).

نقشه آموزش