مقایسه اشیا در جاوا اسکریپت – به زبان ساده


مقایسه اشیا در جاوا اسکریپت برای تعیین برابر بودن آسان است، اما این که آنها را مقایسه کنیم تا بدانیم شامل مقدار یکسانی هستند یا نه کار چندان سادهای محسوب نمیشود. در این مقاله تلاش میکنیم دلیل دشوار بودن این نوع مقایسه اخیر در جاوا اسکریپت را توضیح دهیم.
شاید با خود بگویید اصولاً چرا باید تلاش کنیم تا دلیل این دشواری را تبیین کنیم. دلیل آن این است که درک علت دشواری یا عدم کارکرد یک راهحل، میتواند فهم عمیقتری از آن راهحل به ما بدهد و بدین ترتیب میتوانیم راهحلهای بهتری به دست آوریم که کار میکنند.
انواع داده مقدماتی در برابر ارجاعی یا غیر مقدماتی
پاسخ ما به سؤال ابتدای مقاله به طور کلی به تفاوت بین انواع مختلف داده مقدماتی و غیر مقدماتی در جاوا اسکریپت مربوط میشود. چنان که با استفاده از کد و قیاس نشان خواهیم داد، متغیرهای شامل انواع «مقدماتی» (primitive) دارای مقداری در حافظه هستند، اما متغیرهای شامل نوع «غیر مقدماتی» (non-primitive) در حافظه دارای ارجاع یا رفرنس هستند.
نوع مقدماتی و تخصیص حافظه
در این بخش برای توضیح از یک مثال استفاده میکنیم.
مثال 1
انواع مقدماتی ساده هستند و به طور کلی شامل عدد، رشته، مقدار بولی، تعریفنشده و null هستند. فهرست کامل آنها را در اینجا (+) ببینید.
در زمان اعلان هر نوع متغیر مقدماتی، مقداری از حافظه تخصیص مییابد و مقدار مقدماتی در آن مکان حافظه ذخیره میشود. برای مقایسه فرض کنید عدد 1 را روی یک برگه یادداشت مینویسیم و آن را روی وایت بورد میچسبانیم. بدین ترتیب به خاطر خواهیم آورد که باید یک وظیفه را انجام دهیم. البته میتوانستیم خود وظیفه را نیز بنویسیم، اما میخواهیم همه چیز ساده بماند.
فرض کنید همکار شما نیز عدد 1 را روی برگه یادداشت مینویسد تا به خود یادآوری کند که امروز یک جلسه دارد و آن را نیز روی وایت بورد میچسباند. برگههای یادداشت متفاوت هستند و هر یک مقدار خاص خود را دارند.
این برگههای یادداشت را میتوان مانند حافظه اختصاصیافته و اعداد نوشتهشده را همچون مقادیر ذخیرهشده در آن مکان حافظه تصور کرد. در این مثال، این مکانهای حافظه شامل مقدار یکسانی به صورت 1 هستند.
زمانی که انواع داده مقدماتی را با هم مقایسه میکنیم، در واقع مقادیر ذخیرهشده در مکانهای حافظه را با هم مقایسه میکنیم. اگر یک وظیفه جدید بگیریم و عدد برگه یادداشت خدمات را به 2 تغییر دهیم، تأثیری روی برگه یادداشت همکارمان نخواهد داشت. هر کدام مقدار خاص خود را دارند.
مثال 2
متغیر a حافظه را تخصیص میدهد و مقدار 1 میگیرد. متغیر b فضای حافظه خود را دریافت کرده و مقدار متغیر a که 1 است را میگیرد. این متغیر به a اشاره نمیکند و مستقل از a است و مقدار خاص خود را دارد. بنابراین هنگامی که متغیر a تغییر یابد، متغیر b بدون تغییر میماند. در جاوا اسکریپت هر متغیر مقدماتی در زمان اعلان یافتن، مقدار حافظه خاص خود را دریافت میکند. زمانی که یک مقدار انتساب یابد، آن مقدار وارد فضای حافظه میشود.
نوع ارجاع و تخصیص حافظه
اگر بخواهیم به زبان ساده بیان کنیم، انواع ارجاعی در واقع همان اشیا، تابعها و آرایهها هستند که مجموعاً همگی «شیء» (Object) نامیده میشوند.
مثال 3
نکته مهمی که باید درک کنیم این است که متغیرهای obj1 و obj2 که ممکن است شیء، آرایه یا تابع باشند هر یک شامل یک ارجاع به مکانی از حافظه و نه مقدار آن شیء هستند. مکانها جدا از هم هستند و از این رو ارجاعها متفاوت هستند و مقایسه کار نمیکند. ما مقادیر اشیا را مقایسه نمیکنیم، بلکه ارجاعهای آنها به مکانهای حافظه را مقایسه میکنیم.
بنابراین در مورد انواع ارجاعی در جاوا اسکریپت، در زمان اعلان یافتن شیء مجموعههایی در حافظه داریم، اما متغیر تنها ارجاعی به مکان اشیا در حافظه و نه خود مقدار را میگیرد.
بررسی مجدد قیاس برگه یادداشت
اگر تصمیم بگیریم لیستی از وظایف (غیر مقدماتی) ایجاد کنیم و آن را روی میز کار قرار دهیم، آن برگه یادداشت روی وایتبورد شامل اطلاعاتی خواهد بود که محل یافتن لیست وظایف را نشان میدهد و در مورد خود وظایف اطلاعاتی ارائه نمیکند. برای مثال فرض کنید برگه یادداشت شامل پیامی مانند زیر است:
«به سمت چپ میز کار نگاه کن.»
اگر بخواهیم لیست وظایف دوم را ایجاد کرده و روی میز کار قرار دهیم، برگه یادداشت دیگری را روی وایتبورد قرار میدهیم که محل لیست دوم را نمایش دهد:
«به سمت راست میز کار نگاه کن.»
اگر دو پیام روی برگههای یادداشت متصل به وایتبورد را مقایسه کنیم، میبینیم که برابر نیستند. بنابراین حتی اگر لیست وظایف شماره 2 همانند لیست وظایف شماره 1 باشد، ارجاعها متفاوت هستند و با هم یکسان نخواهند بود.
انتساب انواع ارجاعی
اگر مثال 2 را به خاطر بیاورید، یک نوع مقدماتی را به متغیر a انتساب دادیم. سپس مقدار متغیر a را به متغیر b انتساب دادیم. از آنجا که هر کدام از متغیرها فضای حافظه خاص خود را دارند، تغییر مقدار متغیر a تأثیری بر مقدار متغیر b نخواهد داشت. اما این موضوع در مورد انواع ارجاعی فرق میکند.
مثال 4
در حالتی که دو متغیر برابر باشند، از آنجا که متغیرها اطلاعات یکسانی را شامل میشوند، ارجاع یکسانی به مکان شیء در حافظه دارند. اگر به قیاس برگه یادداشت بازگردیم، این حالت مانند آن است که دو برگه یادداشت روی وایتبورد بگویند که کجا میتوان لیست وظایف اصلی را پیدا کرد. مثلاً پیام هر دو آنها به صورت زیر باشد:
«روی میز کار سمت چپ را نگاه کن.»
بدین ترتیب هر دو متغیرها شامل اطلاعات یکسانی هستند و از این رو برابرند.
مثال 5
با تغییر دادن مقدار هر یک از متغیرهای obj1 و obj2، متغیر دیگر نیز تغییر میکند، زیرا به شیء یکسانی اشاره میکنند.
اشیا را چگونه مقایسه کنیم؟
مقایسه کردن اشیا کار آسانی است و میتوان از عملگر === تابع یا ()Object.is استفاده کرد. این تابع در صورتی که ارجاع یکسان باشد، مقدار true و در صورتی که یکسان نباشد مقدار false بازگشت میدهد. یک بار دیگر باید تأکید کنیم که این عملگر و تابع ارجاع به اشیا و نه خود مقدار اشیا را مقایسه میکنند. بنابراین در مثال 3 مقایسه ;Object.is(obj1,obj2) مقدار نادرست بازگشت میدهد. در مثال 4 ;Object.is(obj1,obj2) مقدار درست بازگشت میدهد.
بنابراین حتی اگر دو شیء شامل دادههای یکسانی باشند عملگر === و تابع ()Object.is مقدار نادرستی بازگشت خواهند داد، مگر اینکه متغیرها شامل ارجاع به شیء یکسانی باشند. چنان که میبینید مقایسه مقدار اشیا پیچیدهتر است.
سخن پایانی
در این مقاله دیدیم که مقایسه دو شیء چیزی بیش از بررسی مقدار دو متغیر است، چون متغیرها صرفاً شامل ارجاعی به مکانی در حافظه هستند. تعیین این که آیا متغیرهای اشیا شامل مقدار یکسانی هستند یا نه ممکن است به فرایند پیچیدهتری نیاز داشته باشد.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- آموزش JavaScript ES6 (جاوا اسکریپت)
- جاوا اسکریپت چیست؟ — به زبان ساده
- آموزش جاوا اسکریپت — مجموعه مقالات جامع وبلاگ فرادرس
==