واژهی کلیدی let
تاکنون به کمک واژهی کلیدی var امکان تعریف متغیرها در جاوا اسکریپت مهیا بودند. برای نمونه در مثال زیر، متغیر x داخل بدنهی if با استفاده از var تعریف شدهاست:
var doWork = function(flag){ if(flag){ var x = 3; } return x; };
زمانیکه از var استفاده میشود، برای یک متغیر دو نوع میدان دید را میتوان متصور شد:
- اگر خارج از بدنهی تابع تعریف شود، این متغیر عمومی خواهد بود.
- اگر داخل بدنهی تابع تعریف شود، میدان دید آن محدود به همان بدنهی تابع میشود. در این حالت چیزی به نام block scope بیمفهوم است. در متد doWork فوق، هرچند متغیر x داخل بدنهی بلاک if تعریف شدهاست، اما این x در کل بدنهی تابع در دسترس است و نه صرفا داخل بلاک if. این مورد تا پیش از ES 6 منشاء بسیاری از باگها بودهاست.
بنابراین در اینجا چون x تعریف شده، میدان دیدی در سطح متد دارد، return x معتبر بوده و در حالت دریافت پارامتر true، مقدار 3 را بر میگرداند و در حالت false هم همچنان مقداری را دریافت خواهیم کرد و این مقدار undefined است (اما پیام خطای عدم دسترسی به x را دریافت نمیکنیم).
به این رفتار اصطلاحا hoisting میگویند. در این حالت موتور جاوا اسکریپت، تمام متغیرهای تعریف شدهی توسط var را به صورت ضمنی به ابتدای تعریف متد منتقل کرده و آنها را در آنجا تعریف میکند. به همین جهت است که return x تعریف شدهی در انتهای متد، قابلیت دسترسی به x داخل بدنهی if را دارد.
در ES 6 برای رفع این مشکل، واژهی کلیدی جدیدی به نام let معرفی شدهاست و هدف آن مهیا کردن block scoping تعریف متغیرها است:
var doWork = function(flag){ if(flag){ let x = 3; } return x; };
بله. همانطور که مشاهده میکنید، اینبار میدان دید x به if block تعریف شدهی در آن محدود گشته و دیگر خارج از آن مفهومی ندارد و تعریف نشدهاست. به همین جهت زمانیکه به return x میرسیم، پیام تعریف نشده بودن x را دریافت خواهیم کرد. برای اینکه قطعه کد فوق کار کند، نیاز است return x را به داخل بدنهی قطعهی if تعریف شده، انتقال داد.
این block scoping مهیا شدهی توسط let، با حلقهی for نیز کار میکند:
var doWork = function(){ for(let i = 0; i< 10; i++){ } /* return i won't work */ return 0; };
یک نکته
مفهوم block scoping با تعریف {} معنا پیدا میکند. بنابراین میتوانید یک قطعهی دلخواه را با تعریف {} نیز مشخص کنید:
و یا در مثال ذیل چندین قطعهی تو در تو را مشاهده میکنید:
let outer = 'I am so eccentric!' { let inner = 'I play with neighbors in my block and the sewers' { let innermost = 'I only play with neighbors in my block' } // accessing innermost here would throw } // accessing inner here would throw // accessing innermost here would throw
نمونهی دیگر آن تعریف یک متد داخل یک بلاک است:
{ let _nested = 'secret' function nested () { return _nested } } console.log(nested())
در ES 6 نمیتوان به متغیرهای تعریف شدهی توسط let داخل یک بلاک، در خارج از آن دسترسی یافت. اگر میخواهید سطح دسترسی به متد را افزایش دهید، نیاز است به شکل ذیل عمل کنید و متد را خارج از بدنهی بلاک با سطح دسترسی بیشتری تعریف نمائید:
var nested; { let _nested = 'secret' nested = function () { return _nested } } console.log(nested()) // <- 'secret'
واژهی کلیدی const
در ES 6 برای ایجاد و مقدار دهی متغیرهای فقط خواندنی، واژهی کلیدی const افزوده شدهاست. در اینجا const نیز مانند let دارای block scoping است.
doWork = function() { const value = 10; value = 11; return value; }
در ES 6، انتساب یک مقدار به یک const، پس از تعریف آن، منجر به بروز خطای syntax error خواهد شد. همچنین تعریف مجدد آن نیز چنین خطایی را سبب خواهد شد.
یک نکته
هر چند const سبب read only شدن یک متغیر میشود، اما آنرا immutable نمیکند:
const items = { people: ['you', 'me'] } items.people.push('test') console.log(items)
همانطور که مشاهده میکنید، هنوز هم میتوان به شیء تعریف شده، آیتمی را اضافه کرد (در اینجا test به آرایهی people اضافه شدهاست).
آشنایی با مفهوم shadowing
همان مثال ابتدای بحث را در نظر بگیرید:
var doWork = function(flag){ if(flag){ let x = 10; var x = 3; return x; } };
let x = 10; var doWork = function(flag){ if(flag){ var x = 3; return x; } };
مثال ذکر شده، با مثال ذیل که یک بلاک را توسط {} ایجاد کردهایم، یکی است:
let x = 10; { let x = 3; console.log(x); } console.log(x);
در اینجا نیز ابتدا مقدار 3 که مرتبط با بلاک داخلی است چاپ خواهد شد و سپس مقدار 10 که مرتبط است به بلاک خارجیتر.