چگونه objectها را در جاوااسکریپت مقایسه کنیم

چگونه objectها را در جاوااسکریپت مقایسه کنیم

بدون شک تابحال بارها مجبور به مقایسه مقادیر مختلف شدید. مقایسه مقادیر اصلی (اعداد، رشته‌ها و...) در جاوااسکریپت ساده هست. برای این کار میشه به راحتی از عملگرهای مقایسه استفاده کرد. اما مقایسه اشیاء (object) کار دشوارتریه، چون اشیاء دیتاهای ساختار‌یافته هستند. در این مقاله روش‌هایی برای مقایسه اشیاء رو با هم یاد میگ‌یریم.

1. Referential equality (برابری مرجع یا برابری ارجاعی)

جاوااسکریپت سه راه برای مقایسه مقادیر اشیاء فراهم کرده:

  • اپراتورِ strict equality===
  • اپراتورِ loose equality ==
  • تابع ()Object.is

هنگام مقایسه اشیاء با هر یک از روش‌های بالا، مقایسه فقط در صورتی درست ارزیابی میشه که مقادیر مقایسه شده ارجاعی به یک نمونه (instance) یکسان باشن. این برابری ارجاعی هست.

شاید گفته بالا کمی پیچیده به نظر برسه. اجازه بدید با مثالی اون رو بررسی کنیم.

دو شیئ به نام های hero1 و hero2 تعریف میکنیم و برابری ارجاعی رو در عمل می‌بینیم:

const hero1 = {
    name: 'Batman'
  };
  const hero2 = {
    name: 'Batman'
  };

  hero1 === hero1; // => true
  hero1 === hero2; // => false

  hero1 == hero1; // => true
  hero1 == hero2; // => false
  
  Object.is(hero1, hero1); // => true
  Object.is(hero1, hero2); // => false

در هر سه روش نتیجه یکسانه، عبارت hero1 === hero1 مقداری true برمی‌گردونه زیرا هر دو عملوند به نمونه شیء (object instance) یکسانِ hero1 اشاره میکنند.

در سمت دیگه، عبارت hero1 === hero2 برابر با false هست زیرا عملوندهای hero1 و hero2 ، نمونه‌های شیء متفاوتی هستند.

جالبه که اشیاء hero1 و hero2 دارای محتوای یکسانی هستند؛ هر دو دارای یک property به نام name با مقدار batman هستند. با این حال حتی مقایسه این اشیاء با ساختار یکسان مقدار false رو برمی‌گردونه. برابری ارجاعی زمانی مناسبه که قصد داشته باشید مرجع (refrence) اشیاء رو به جای محتوای اون ها مقایسه کنید.

با این حال در بیشتر مواقع نیاز دارید که محتوای اشیاء رو با هم مقایسه کنید، یعنی propertyها و valueهای اون ها.

2. مقایسه دستی

یک راه مشخص  و ساده برای مقایسه objectها بر اساس محتوای اون ها، اینه که propertyهای اون ها رو تک تک بخونیم و با هم مقایسه کنیم. برای مثال، بیاین با هم یک تابع به نام ()isHeroEqual بنویسیم تا به وسیله اون، دو شیء رو با هم مقایسه کنیم:

const hero1 = {
  name: 'Batman'
};
const hero2 = {
  name: 'Batman'
};
const hero3 = {
  name: 'Joker'
};

function isHeroEqual(object1, object2) {
    return object1.name === object2.name;
};

  isHeroEqual(hero1, hero2); // => true
  isHeroEqual(hero1, hero3); // => false

تابع ()isHeroEqual دو شیء به عنوان ورودی دریافت کرد و ویژگی name هر دو شیء رو با هم مقایسه کرد. این روش در موقعیت های خاصی کاربرد داره، مثلا زمانی که اشیاء مورد مقایسه propertyهای کم تعدادی دارن و همچنین ما از ساختار اون ها اطلاع داریم. در این مواقع میشه از روش مقایسه دستی استفاده کرد که پرفورمنس خوبی داره و اطلاع از نحوه‌ی کارش هم راحته. مقایسه دستی نیاز به استخراج دستیِ valueهای هر یک از propertyها و مقایسه اون ها با هم داره؛ برای اشیاء ساده این یک مشکل نیست ولی برای اشیاء بزرگ‌تر و پیچیده‌تر استفاده از اون مناسب نیست.

3. برابری سطحی یا shallow equality

در این روش شما لیستی از propertyهای هر دو شیء رو تهیه میکنید (به وسیله ()Object.keys) و  سپس مقادیر propertyها رو با هم مقایسه می‌کنید. اجازه بدین در قالب یک مثال کارکردش رو ببینیم.

  function shallowEqual(object1, object2) {
    const keys1 = Object.keys(object1);
    const keys2 = Object.keys(object2);

    if (keys1.length !== keys2.length) {
      return false;
    }
    for (let key of keys1) {
      if (object1[key] !== object2[key]) {
        return false;
      }
    }
    
    return true;
  }

داخل بدنه تابع، keys1 و keys2 آرایه هایی هستن که به صورت متقابل اسامی propertyهای object1 و object2 رو نگهداری می‌کنند.

اول بررسی میکنیم که اگر تعداد کلیدهای دو شیء باهم برابر نبودن، پس دو شیء با هم مساوی نیستن و مقدار false رو برمی‌گردونیم.

حلقه  for بر روی کلیدها پیمایش میکنه و هر property متعلق به object1 رو با property متعلق به object2 مقایسه میکنه.

حالا از روش مقایسه سطحی برای مقایسه اشیائی با چندین property استفاده می‌کنیم:

  const hero1 = {
    name: 'Batman',
    realName: 'Bruce Wayne'
  };
  const hero2 = {
    name: 'Batman',
    realName: 'Bruce Wayne'
  };
  const hero3 = {
    name: 'Joker'
  };
  
  shallowEqual(hero1, hero2); // => true
  shallowEqual(hero1, hero3); // => false

مقایسه دو شیء hero1 و hero2 مقداری true رو برمی‌گردونه چون هر دو شیء propertyهای یکسان با مقادیر مشابه دارند (name و realName).

در سمت دیگه مقایسه اشیاء hero1 و hero3 مقدار false برمی‌گردونه چون دو شیء propertyهای متفاوتی دارند.

اگر propertyهای اشیاء مورد مقایسه دارای مقادیر اولیه باشند (مثل رشته، عدد، boolean، undefined و...)، روش برابری سطحی میتونه مناسب باشه؛ اما اشیاء در جاوااسکریپت ممکنه تودرتو باشن. در چنین شرایطی این روش به خوبی کار نمیکنه. یک مثال ببینیم: 

  const hero1 = {
    name: 'Batman',
    address: {
      city: 'Gotham'
    }
  };
  
  const hero2 = {
    name: 'Batman',
    address: {
      city: 'Gotham'
    }
  };

  shallowEqual(hero1, hero2); // => false

این بار حتی با اینکه hero1 و hero2 محتوای مشابه دارن ولی مقدار false برگردونده میشه. چرا؟

شیء‌های تودرتوی hero1.address و hero2.address نمونه های (instance) متفاوتی هستن به این ترتیب تابع shallowEqual تشخیص میده که hero1.address و hero2.address مقادیر متفاوتی هستند.

4. برابری عمیق یا Deep Equality

این روش شبیه به روش قبلی یعنی shallow equality هست اما با یک تفاوت! در این روش، اگر propertyهای مقایسه شده، از نوع شیء باشن، یک تابع shallow equality check بازگشتی بر روی هر یک از شیء های تودرتو اجرا می‌شه. بیایید تا نحوه اجرای بررسی برابری عمیق رو ببینیم:

function deepEqual(object1, object2) {
    const keys1 = Object.keys(object1);
    const keys2 = Object.keys(object2);

    if (keys1.length !== keys2.length) {
      return false;
    }
    
    for (const key of keys1) {
      const val1 = object1[key];
      const val2 = object2[key];
      const areObjects = isObject(val1) && isObject(val2);
      if (
        areObjects && !deepEqual(val1, val2) ||
        !areObjects && val1 !== val2
      ) {
        return false;
      }
    }
    return true;
  };

  function isObject(object) {
    return object != null && typeof object === 'object';
  }

خط 14 نشون میده به محض اینکه propertyهای مورد مقایسه شیء باشند، یک تابع بازگشتی شروع میشه تا بررسی کنه آیا این اشیاء توردتو هم برابر هستند یا نه:

areObjects && !deepEqual(val1, val2) ||

حالا یک مثال از ()deepEquality ببینیم:

const hero1 = {
    name: 'Batman',
    address: {
      city: 'Gotham'
    }
  };

  const hero2 = {
    name: 'Batman',
    address: {
      city: 'Gotham'
    }
  };
  
  deepEqual(hero1, hero2); // => true

تابع deep equality به خوبی تشخیص داد که hero1 و hero2 دارای propertyها و valueهای یکسانی هستند که این موضوع اشیاء تودرتوی hero1.address و hero2.address رو هم شامل می‌شه.

 

امیدوارم این مقاله براتون مفید بوده باشه. شما روش دیگه ای برای مقایسه objectها می‌شناسید؟ برامون بنویسید.

پیشنهادات بیشتر سکان بلاگ برای شما