یکی از چالش های جالب تو زبان های برنامه نویسی مثل Javascript، اینه که ما براساس شناختمون از اون زبان برنامه نویسی ، خروجی یه قطعه کدی رو تو ذهنمون حدس میزنیم ، اما خروجی اصلی با چیزی که ما فکر میکنیم متفاوت هست .
قصد دارم تا طی 28 تا مطلب ، 28 مورد از این کدهای خاص رو در زبان Javascript باهم بررسی کنیم و ببینیم چقدر رو این زبان شناخت داریم و اگر خروجی رو اشتباه حدس زدیم ، دلیل این حدس اشتباه چی بوده .
این رو هم بگم که این 28 مطلب از کتاب Javascript Brain Teasers برداشته میشه و اینطور نیست که بگم خودم خلقشون کردم یا بهشون رسیدم
پازل 1 javascript
کد زیر رو بررسی کنین و قبل از خوندن ادامه ی مطلب ، سعی کنین خروجیش رو حدس بزنین !
let temp = 25;
function displayTemperature() {
console.log(`Current temperature: ${temp} °C`);
}
function forecastTemperature() {
console.log(`Expected temperature: ${temp} °C`);
var temp = 28;
}
displayTemperature();
forecastTemperature();

خوب، بریم سراغ بررسی خروجی کد بالا. احتمالا انتظار داشتین که خروجی کد بالا به شکل زیر باشه:
Current temperature: 25 °C
Expected temperature: 25 °C
اما خروجی درست اینه:
Current temperature: 25 °C
Expected temperature: undefined °C
حالا باهم بررسی میکنیم که چرا خروجی به این شکل شده.
اگر مقاله ی آشنایی با Execution Context در جاوا اسکریپت خونده باشین ، در اونجا عملکرد جاوا اسکریپت در اجرای کدها رو بررسی کردیم.
عملکرد موتور جاوا اسکریپت بدین شکل هست که در ابتدا در فاز compilation کد ها رو بررسی میکنه و متغییرها/توابع و … تعریف شده در Execution Context مرتبط با کد اجرایی قرار میده که بهش میگیم hoisting و بعد از اون در مرحله ی execution مقادیر این متغیر ها و … رو تخصیص میده .
مثال زیر رو در نظر بگیرین:
var a = 10;
در مرحله ی compilation ، موتور جاوا اسکریپت کد رو به شکل زیر در میاره:
var a;
a = 10;
وقتی شما متغییری رو با استفاده از var تعریف میکنین و مقدار دهی میکنین، تعریف متغییر به ابتدای scope اجرایی منتقل میشه (به این عمل میگن Hoisting) و مقدار دهی متغییر در جایی که بود باقی میمونه .
این عملکرد میتونه باعث خروجی های غیرمنتظره مثل چیزی که در مثال ابتدایی داریم بشه . تعریف هر متغییری که در داخل یک تابع داریم ، در مرحله ی compilation به ابتدای تابع منتقل میشه . اگر یک متغییر در Scope بالاتر با همان نام متغییر موجود در تابع وجود داشته باشه ، از نظر تابع مخفی میمونه. برای مثال:
var a = 10;
function fn() {
console.log(a); // undefined
var a = 20;
console.log(a); // 20
}
fn();
در نگاه اول ، ممکنه بگین که خروجی کد بالا در ابتدا 10 هست و سپس 20. اما اتفاق اصلی که صورت میگیره به شکل زیر هست:
var a = 10;
function fn(){
var a;
console.log(a); // undefined
a = 20;
console.log(a); // 20
}
fn();
برای جلوگیری از بروز چنین مشکلاتی ، در استاندارد ES2015 که بروزرسانی برای زبان Javascript هست ، راهکار تعریف متغییر ها با استفاده از دستور let جهت تعریف متغییر ها رو داده .
در صورتی که متغییر ها رو با استفاده از let تعریف کنیم ، بازهم عمل hoisting اتفاق می افته ، اما بجای اینکه خروجی undefined بگیریم ، با خطایی روبرو میشیم که این متغییر اصلا تعریف نشده .
مثال زیر رو در نظر بگیرید:
function fn(){
console.log(a); // => undefined
console.log(b); // => ReferenceError: b is not defined
var a = 20;
let b = 20;
}
fn();
پس اگر ما متغیری رو با استفاده از var تعریف کرده باشیم و قبل از تعریفش بخوایم ازش استفاده کنیم ، با خروجی undefined روبرو میشیم و خطایی رخ نمیده .
اما اگر متغییری رو با استفاده از let تعریف کرده باشیم ، با وجود اینکه مشابه var ، عمل hoisting اتفاق می افته ، بجای undefined با خطای ReferenceError مواجه میشیم و این مسئله debug کردن برنامه ها رو خیلی آسونتر میکنه .
حالا سوال پیش میاد که چطور ممکنه با وجود اینکه در هر دو حالت تعریف متغییر ، عملیات hoisting اتفاق می افته ، اما فقط تو حالت استفاده از let ما خطا میگیریم !؟
در جامعه ی Javascript ، اصطلاحی داریم به اسم Temporal dead Zone یا (TDZ) که توضیح میده که این مسئله رو توضیح میده .
یک بازه زمانی کوتاهی بین “ورود موتور جاوا اسکریپت به scope اجرایی ” و مقدار دهی متغییر های داخل اون هست که بهش میگن TDZ و تا زمانی که ما در اون بازه هستیم، در صورتی که به متغیرهایی که با استفاده از let یا const تعریف شده باشن بخوایم دسترسی داشته باشیم با خطای ReferenceError مواجه میشیم.
بزودی پازل دوم رو با هم بررسی میکنیم !