۲۱ سپتامبر ۲۰۲۳

Reference Type

ویژگی یک زبان عمیق

این مقاله در دسته بندی مباحث پیشرفته است, برای فهم آن باید به مباحث پایه مسلط باشید.

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

با فراخوانی یک متد به صورت پویا و داینامیک ممکن است مقدار this از دست برود.

به عنوان مثال:

let user = {
  name: "John",
  hi() { alert(this.name); },
  bye() { alert("Bye"); }
};

user.hi(); // works

// اجرا شود user.by و درغیراینصورت user.hi بود متد john حال بررسی میکنیم اگر نام برابر
(user.name == "John" ? user.hi : user.bye)(); // Error!

در خط آخر ما شرط سه تایی (تک خطی) را داریم که براساس true یا false بودن مقدار user.name مشخص میشود که آیا متد user.hi اجرا میشود یا user.bye. نتیجه true است و user.hi برگردانده میشود.

ولی چون متد پرانتز ندارد درهنگام اجرا با مشکل روبرو خواهیم بود.

همانطور که میبینید هنگام اجرا به ارور برخورد کردیم. چون مقدار "this" برابر با undefined است.

کد زیر کار میکند (آبجکت با عملگر نقطه):

user.hi();

اما این کد اصلا کار نمیکند! (فانکشن بدون پرانتز)

(user.name == "John" ? user.hi : user.bye)(); // Error!

چرا؟ اگر متوجه شدید که تا اینجا چه اتفاقی افتاده, بیایید به عملکرد ()obj.method بپردازیم.

توضیحات درباره Reference type

نگاه کنید. عبارت ()obj.method دو عملگر داخل خود دارد:

  1. ابتدا, عملگر نقطه '.' ویژگی obj.method را پیدا میکند.
  2. سپس پرانتز () آن را اجرا میکند.

خب پس چگونه اطلاعات مربوط به this از قسمت اول به دوم منتقل میشود؟

اگر ما این دستور را در خط های جداگانه ای قرار بدهیم, مطمئنا مقدار this از دست خواهد رفت:

let user = {
  name: "John",
  hi() { alert(this.name); }
};

// اگر به این صورت در دو خط جدا متد را صدا بزنیم:
let hi = user.hi;
hi(); // است undefiend برابر با this خطا! چون مقدار

در اینجا hi = user.hi تابع را در متغیر میریزد و چون خط آخر مستقل و از نظر جاوااسکریپت دستور جداگانه ای است به همین دلیل this وجود ندارد.

** برای اینکه دستور ()user.hi درست کار کند, جاوااسکریپت برای آن یک راه حلی دارد. عملگر نقطه '.' نه فقط فانکشن بلکه مقدار خاصی از ریفرنس تایپ ها Reference Type را برمیگرداند **.

ریفرنس تایپ ها یا نوع ارجاعی “نوع خاصی از داده ها یا specification type” هستند. ما نمیتوانیم به صورت مستقیم از آن استفاده کنیم ولی به صورت داخلی داخل زبان تعبیه شده و استفاده میشود.

این نوع داده خاص شامل سه مقدار (base, name, strict) هست که:

  • base یک آبجکت است
  • name نام ویژگی آبجکت هست
  • اگر use strict تعبیه شده باشد مقدار strict نیز true است.

نه تنها مقدار user.hi فانکشن نیست بلکه نوع آن از نوع Reference type هست. user.hi در حالت strict mode به این صورت است:

// مقدار Reference Type
(user, "hi", true)

وقتی که پرانتزی در Reference type ها صدا زده میشود, همه اطلاعات مربوط به اشیاء و متد فعلی را دریافت میکند و به درستی کلمه کلیدی this را مقداردهی میکند.

نوع Reference type یک نوع داده داخلی “واسط” با هدف انتقال اطلاعات مربوطه از عملگر نقطه . به پرانتز است.

عملگری مانند عملگر انتساب (Assign) در hi = user.hi خاصیت Reference type را در متغیر جدید کنار میذارد و صرفا مقدار تابع user.hi برگردانده شده را میریزد. و سپس this مقدار خود را از دست میدهد.

درنتیجه, تنها درصورتی مقدار this به درستی پاس میشود که مستقیما فانکشن را با استفاده از نقطه obj.method() یا براکت باز و بسته obj['method']() صدا بزنیم. راه های مختلفی برای رفع این مشکل هست مثل func.bind().

خلاصه

تایپ مرجعی یا Reference Type ها نوع داده داخلی در زبان جاوااسکریپت هستند.

وقتی که یک ویژگی یا Peraperty مخصوصا توسط عملگر نقطه . در ()obj.method خوانده میشود, نه تنها مقدار ویژگی بلکه مقادیر خاص مانند ویژگی ها یا توابع خاص را هم برمیگرداند.

بعد از اینکه متدی فراخوانی شد, عملگر پرانتز آبجکت را دریافت و this را مقداردهی میکند.

برای مابقی عملگر ها Reference Type تبدیل به مقدار ویژگی میشود (تابع هم همینطور).

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

تمارین

اهمیت: 2

خروجی کد زیر چیست؟

let user = {
  name: "John",
  go: function() { alert(this.name) }
}

(user.go)()

نکته: سوال دام دار است (یعنی نکته دارد) :)

خطا!

کد زیر را مدنظر داشته باشید:

let user = {
  name: "John",
  go: function() { alert(this.name) }
}

(user.go)() // خطا دارد!

پیام خطا در بیشتر مرورگر ها اطلاعاتی بابت اینکه چه اشتباهی رخ داده است نمیدهد.

** این خطا نشان داده میشود چون سمیکالن بعد از این دستور فراموش شده است {...} = user.**

جاوااسکریپت به صورت پیش فرض سمیکالن را در براکت قبل از دستور ()(user.go) اضافه نمیکند. سپس کد به این صورت خوانده میشود:

let user = { go:... }(user.go)()

بنابراین جاوااسکریپت حدس میزند که ما درواقع آبجکت { ... :go } را به عنوان یک تابع با آرگومان های (user.go) صدا زده ایم. و سپس چون به let user میرسیم آبجکت user به صورت تعریف نشده معرفی میشود و ما ارور را مشاهده میکنیم.

در کد زیر همه چیز خوب بنظر میرسد اگر سمیکالن را انتهای آبجکت اضافه کنیم:

let user = {
  name: "John",
  go: function() { alert(this.name) }
};

(user.go)() // John

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

اهمیت: 3

در کد زیر قصد داریم متد ()obj.go را در 4 موقعیت مختلف اجرا کنیم.

ولی پاسخ موقعیت (1) و (2) با موقعیت (3) و (4) متفاوت است. چرا؟

let obj, method;

obj = {
  go: function() { alert(this); }
};

obj.go();               // (1) [object Object]

(obj.go)();             // (2) [object Object]

(method = obj.go)();    // (3) undefined

(obj.go || obj.stop)(); // (4) undefined

با توجه به موارد زیر.

  1. متدی که داخل یک آبجکت معمولی بوده, اجرا شده.

  2. به همین ترتیب, پرانتز در اینجا اولیت را عوض نمیکند و مهم نقطه . است.

  3. ما نمونه پیچیده تری نسبت به ()(expression). در زیر داریم که به دو خط کد تقسیم شده است:

    f = obj.go; // calculate the expression
    f();        // call what we have

    اینجا ()f به عنوان یک تابع, بدون this اجرا میشود.

  4. در موارد مشابه (3), در سمت چپ پرانتز عبارت داریم.

برای اینکه اتفاقاتی که در مورد (3) و (4) رج میدهد را بدانیم, باید به خاطر داشته باشیم که دسترسی به مقدار ویژگی ها (چه با نقطه یا چه با استفاده از براکت) یک مقداری از Reference Type

هر عملیاتی که برروی آن غیر از توابع (مثل عملگر تخصیص = یا ||) اعمال میشود, از آن یک مقدار معمولی میسازد, که اطلاعاتی برای تنظیم مقدار this ندارد.

نقشه آموزش