hoisting در جاوااسکریپت چیست و چطور کار میکنه ؟
زمان مطالعه:10 دقیقه

hoisting در جاوااسکریپت چیست و چطور کار میکنه ؟

hoisting در جاوااسکریپت به پروسه ایی گفته میشه که در آن مفسر کد جاوااسکریپت قبل از اجرای کد و بصورت ظاهری محل تعریف شدن یک فانکشن، متغیر یا کلاس رو به بالای scope منتقل میکنه و این رفتار پیشفرضی که جاوااسکریپت از خودش نشون میده باعث میشه تا ما بتونیم حتی قبل از محل نوشته شدن یک فانکشن از اون استفاده کنیم.

نکته: جاوااسکریپت بصورت ظاهری فقط محل تعریف شدن یک متغیر را به بالای scope منتقل میکنه، نه مقدار اون متغیر را.

همچنین جاوااسکریپت قبل از اجرا شدن کد، memory لازم را به فانکشن ها و متغیرها اختصاص میده.

ترتیب و چرخه متغیرها در جاوااسکریپت

در مواجه با متغیرها ما یک تعریف متغیر، مقداردهی متغیر و درنهایت استفاده از اون متغیر رو داریم، یعنی چیزی مثل کد زیر:

let a;                  // تعریف متغیر
a = 100;            // مقدار دهی متغیر
console.log(a);  // استفاده از متغیر

در مثال بالا صرفا برای نشون دادن چرخه و ترتیب متغیرها در جاوااسکریپت از تعریف و مقداردهی جداگانه استفاده کردیم و بجای کد بالا میتونیم همزمان متغیر را ایجاد و مقداردهی کنیم:

let a = 100;

البته این نکته را به خاطر داشته باشید که جاوااسکریپت در پشت صحنه و قبل از اجرا شدن کد همانند مثال اول ابتدا متغیر را تعریف و سپس مقدارد دهی میکنه.

یک حالت متفاوتی هم وجود داره، فرض کنید شما هیچوقت متغیری با نام a بوسیله var یا let یا const تعریف نکردید، ولی در جایی از کد خودتون به شکل زیر a را مقدار دهی میکنید:

function hoist() {
  a = 20;
  var b = 100;
}

hoist();

console.log(a); 
/* 
خروجی : 20
be onvane yek variable global dar nazar gerefte mishe
*/

console.log(b); 
/*
خروجی: ReferenceError: b is not defined
chon ba kalame var daroone function hoist tarif shode. scope oonham mahdood be function hast
*/

در کد بالا متغیر a به عنوان یک متغیر global در نظر گرفته میشه، بنابر این اینگونه متغیرهایی که قبلا تعریف نشدند به عنوان متغیرهای global شناخته میشن و این یکی از عجیب ترین کارهایی هست که جاوااسکریپت در مواجه با متغیرها انجام میده و از اینرو توصیه میشه تا همیشه متغیر مدنظر خودتون را در scope مدنظرتون تعریف کنید تا هم خوانایی کد بالاتر بره و هم از صحت و عملکرد کد خیالتون راحت باشه!

hoisting متغیرها در جاوااسکریپت

var hoisting

scope متغیرهای var زمینه اجرای فعلی اونهاست. یعنی چی ؟ یعنی اگر متغیری با کلمه var درون یک فانکشن ایجاد کنید، scope اون متغیر محدود به فانکشنی هست که در اون محصور شده و اگر خارج از هرگونه فانکشنی تعریفشون کنید، scope اونها global خواهد بود. بیایید چند مثال را باهم ببینیم:

متغیرهای global

console.log(hoist); // خروجی: undefined

var hoist = 'variable hoist shod';

ما پیش خودمون فکر میکردیم خروجی کد بالا مساوی با ارور ReferenceError: hoist is not defined خواهد بود اما چنین نشد و بجاش با مقدار undefined روبرو شدیم! فکر میکنید دلیلش چیه ؟

دلیل اینکه در کد بالا با اروری مواجه نشدیم این هست که جاوااسکریپت محل تعریف شدن متغیر را ( variable declaration ) را قبل از اجرای هرگونه کدی به بالای scope منتقل کرد و اگر بخواهیم رفتار مفسر کد جاوااسکریپت در مواجه با کد بالا را شبیه سازی کنیم، میتونیم مثال زیر رو بزنیم:

var hoist;

console.log(hoist); // خروجی: undefined
hoist = 'variable hoist shod';

از اینرو ما میتونیم از متغیرها قبل از تعریف شدنشون هم استفاده کنیم، هرچند باید به خاطر داشت که مقدار متغیرهایی که قبل از تعریف شدنشون داریم ازشون استفاده میکنیم بصورت پیشفرض برابر با undefined خواهد بود و توصیه ما این هست که همیشه قبل از استفاده از یک متغیر اونرو تعریف و مقدار دهی کنید تا کد تمیزتر و قابل اطمینان تری داشته باشید.

Function scoped variables

همانطور که بالاتر دیدیم، متغیرهای global-scope به بالاترین سطح hoist میشن. حالا بیایید باهم ببینیم که متغیرهای function-scope به چه صورت عمل میکنند و چگونه hoist میشن.

function hoist() {
  console.log(message);
  var message='hello world'
}

hoist();

طبق چیزهایی که تا اینجای مقاله باهم یادگرفتیم حدس بزنید که خروجی کد بالا چی خواهد بود ؟

اگر حدس شما مقدار undefined هست که تبریک میگم! تا اینجای مقاله را خوب متوجه شدید و اگرهم حدستون اشتباه بوده که جای هیچ نگرانی نیست چراکه در ادامه مقاله به توضیح و برسی بیشتری میپردازیم.

کد زیر شکلی هست که مفسر جاوااسکریپت کد بالا رو میبینه:

function hoist() {
  var message;
  console.log(message);
  message='hello world'
}

hoist(); // خروجی: undefined

scope متغیر message فانکشن hoist هست، بنابر این متغیر message به بالای scope فانکشن hoist منتقل میشه و برای جلوگیری از مواجه شدن با مقدار undefined بهتر هست که ابتدا متغیر را تعریف و مقدار دهی کنیم و سپس از اون استفاده کنیم، یعنی به شکل زیر:

function hoist() {
  var message='hello world'
  return (message);
}

hoist(); // خروجی: hello world

strict mode و var hoisting

به لطف ابزاری از نسخه es5 جاوااسکریپت که به عنوان strict mode شناخته میشه، میتونیم با حالت سختگیرانه تری متغیرهامون را تعریف کنیم. با فعال سازی حالت strict mode ما وارد یک حالت محدود تری از جاوااسکریپت میشیم که اجازه استفاده از یک متغیر را قبل از تعریف شدنش نمیده!

بطور کل استفاده از حالت strict mode تغییرات زیر را به همراه داره:

  • در این حالت برخی از ارورهای اصطلاحا خاموش دیگه توسط جاوااسکریپت نادیده گرفته نمیشن.
  • اشتباهاتی که بهینه سازی را برای موتورهای جاوااسکریپتی سخت میکنه را اصلاح میکنه.
  • استتفاده از برخی syntax هایی که ممکنه در نسخه های آینده جاوااسکریپت اضافه بشه را منع میکنه.

حالت strict mode را میتونیم با نوشتن عبارت زیر در ابتدای فایل یا فانکشن مدنظرمون فعال کنیم:

"use strict";

حالا بیایید تستش کنیم:

'use strict';

console.log(hoist); // خروجی: ReferenceError: hoist is not defined
hoist = 'Hoisted';

در کدبالا اگر strict-mode فعال نبود متغیر ما به بالای scope خودش منتقل و hoist میشد، اما موقعی که strict mode فعال هست ما نمیتونیم از متغیری قبل از تعریف شدنش استفاده کنیم.

همچنین به خاطر داشته باشید که strict mode در مرورگرهای مختلف رفتار مختلفی داره و بهتره که همیشه از کدتون را تست و از درست رفتار کردنش اطمینان حاصل کنید.

let hoisting

قبل از اینکه hoisting متغیرهای let در جاوااسکریپت را برسی کنیم این نکته را به خاطر  داشته باشید که کلمه let یک متغیر block-scope ایجاد میکنه و scope این متغیرها برخلاف var مربوط به فانکشنی که در اون تعریف شدن نیست.

بیایید با برسی رفتار متغیرهای let شروع کنیم:

console.log(hoist); // Output: ReferenceError: hoist is not defined ...
let hoist = 'test hoist variable';

مثال بالا را قبلا با کلمه var نوشته بودیم و در اینجاهم انتظار داشتیم تا بجای ReferenceError با مقدار undefined مواجه بشیم. اما در ورژن es6 جاوااسکریپت، متغیرهای ایجاد شده با کلمه let برخلاف متغیرهای var این امکان را به ما نمیدن که از متغیری قبل از تعریف شدنش استفاده کنیم و اصطلاحا hoist نمیشن و درصورت اینکار ما با ReferenceError مواجه خواهیم شد.

از اینرو متغیرهای let اطمینان حاصل میکنند تا ابتدا متغیر را تعریف و سپس از اون استفاده کنیم.

هرچند که میتونیم به شکل زیر ابتدا متغیر را تعریف و سپس مقدار دهی کنیم و در این صورت دیگه ReferenceError نخواهیم گرفت.

let hoist;

console.log(hoist); // Output: undefined
hoist = 'test hoist'

لذا برای اینکه کد ما به ارور نخوره، نیازه تا ابتدا متغیر را تعریف و بعد مقداردهی کنیم و یا اینکه همزمان تعریف متغیر و مقدار دهی اونرو انجام بدیم.

const hoisting

متغیرهای const در نسخه es6 اضافه شد و کلمه const متغیری block-scope و اصطلاحا تغییر ناپذیر ایجاد میکنه. یعنی ما نمیتونیم بصورت مستقیم مقدار اینگونه متغیرهارو با استفاده از عملگر = تغییر بدیم و صرفا زمان هایی که مقدار یک متغیر const برابر با object یا array هست میتونیم مقادیر و محتوای اون object یا array را تغییر بدیم.

بیایید یک مثالی از تلاش برای تغییر مقدار یک متغیر const بوسیله عملگر = را ببینیم:

const PI = 3.142;

PI = 22/7;

console.log(PI); // خروجی: TypeError: Assignment to constant variable.

متغیرهای const برخلاف var و let تغییر ناپذیر و اصطلاحا immutable variables هستند و ما نمیتونیم بصورت مستقیم و با استفاده از عملگر = مقدار جدیدی را به اونها اضافه کنیم و درصورت اینکار با ارور TypeError: Assignment to constant variable مواجه میشیم.

حالا بیایید یک مثالی هم از استفاده از یک متغیر const قبل از تعریف شدنش ببینیم:

console.log(hoist); // خروجی: ReferenceError: hoist is not defined
const hoist = 'hoist test';

const و let در مثال بالا شبیه هم هستند و ما نمیتونیم قبل از تعریف شدن این متغیرها ازشون استفاده کنیم و درصورت اینکار با ارور ReferenceError: hoist is not defined روبرو میشیم. در نتیجه فقط متغیرهای var هستند که hoist میشن و به بالای scope منتقل میشن.

همچنین ارور بالا را در مثال زیر هم خواهیم دید و تفاوتی نداره که بخواهید از یک متغیر const در بدنه فانکشن یا هرجای دیگری قبل از تعریف شدنش استفاده کنید:

function getCircumference(radius) {
  console.log(circumference)
  circumference = PI*radius*2;
  const PI = 22/7;
}

getCircumference(2) // ReferenceError: circumference is not defined

تفاوت دیگر بین متغیرهای const با let و var این هست که ما مجبوریم تا موقع تعریف کردن، اونهارو مقدار دهی هم بکنیم :

const PI;
console.log(PI); // خروجی: SyntaxError: Missing initializer in const declaration

hoisting functions

فانکشن ها در جاوااسکریپت را میتونیم بطور نسبی به 2 گروه تقسیم کنیم:

  1. Function declarations
  2. Function expressions

حالا بریم تا برسی کنیم که hoisting روی هرکدوم از گروه های بالا به چه صورت رفتار میکنه.

Function declarations

این گروه از فانکشن ها به شکل مثال زیر نوشته میشن و تا بالاترین سطح scope خودشون منتقل و hoist میشن. از اینرو ما میتونیم قبل از تعریف شدن این گروه از فانکشن ها ازشون استفاده کنیم.

hoisted(); // خروجی: "hoist test"

function hoisted() {
  console.log('hoist test');
};

Function expressions

اما این گروه از فانکشن ها یعنی Function expressions برخلاف گروه قبلی hoist نمیشن.

expression(); //خروجی: "TypeError: expression is not a function

var expression = function() {
  console.log('test hoist');
};

همچنین ترکیب این دو گروه در مثال زیر هم بی اثره و فقط در گروه و مثال اول فانکشن ما hoist و به بالای scope منتقل میشه:

expression(); // Ouput: TypeError: expression is not a function

var expression = function hoisting() {
  console.log('hoist test');
};

قبلتر یادگرفتیم که متغیرهای var به بالای scope منتقل میشن و ما میتونیم از اونها استفاده کنیم و نکته ایی که خیلی به اون اشاره کردیم این بود که فقط تعریف متغیر به بالای scope منتقل میشه و نه مقدار اون! بنابر این در مثال بالا متغیر expression به بالا منتقل شده و مقدار اون undefined هست و زمانیکه ما میخواهیم متغیر expression که مقدارش undefined هست را مثل یک فانکشن صدا بزنیم با ارور TypeError: expression is not a function مواجه میشیم (‌ این ارور به ما میگه که این متغیر یک فانکشن نیست )

ترتیب ها و اولویت بندی ها

چند نکته حائز اهمیت درباره تعریف فانکشن ها و متغیرها در جاوااسکریپت وجود داره که باید به خاطر داشته باشید.

  1. انتساب مقدار به یک متغیر ( تعریف و مقداردهی همزمان یک متغیر ) بر تعریف یک فانکشن اولویت داره.
  2. تعریف یک فانکشن بر تعریف یک متغیر اولویت داره.

انتساب مقدار به یک متغیر بر تعریف یک فانکشن اولیت داره و تعریف یک فانکشن هم به تعریف یک متغیر اولویت داره (‌ این اولویت بندی ها از نظر انتقالشون به بالای scope هستش ).

بیایید با چند مثال این اولویت بندی هارو بیشتر برسی کنیم:

اولویت بیشتر انتساب مقدار به یک متغیر بر تعریف یک فانکشن

var double = 22;

function double(num) {
  return (num*2);
}

console.log(typeof double); // Output: number

اولویت بیشتر تعریف فانکشن نسبت به تعریف متغیر

var double;

function double(num) {
  return (num*2);
}

console.log(typeof double); // Output: function

در مثال بالا اگر جای تعریف متغیر و فانکشن را عوض کنیم هم خروجی ما فرقی نمیکنه و با مقدار function روبرو میشیم.

hoisting

#

variables

#

https://vaspar.io/blog/hoisting-in-javascript

اشتراک گذاری:

نظرات

500

/

0