دليل تعلم جافاسكربت التحويل من نوع كائن إلى نوع أولي في جافاسكربت


Emq Mohammed

ماذا يحدث عند جمع كائنين بالشكل obj1 + obj2، أو طرح كائنين obj1 - obj2 أو طباعة الكائنات باستخدام alert(obj)‎؟ تحوَّل الكائنات في مثل هذه الحالات إلى أنواع أولية (primitives) ثم تُنَفَّذ العملية.

رأينا في فصل تحويل الأنواع قواعد التحويل بين الأنواع الأساسية مثل الارقام، النصوص أو القيم المنطقية، لكن بقيت فجوة متعلقة بالكائنات. الآن بعد أن تعرفنا على الرموز والدوال، يمكن ملء هذه الفجوة.

1- في السياق المنطقي (boolean)، جميع الكائنات تساوي القيمة true، إذ يوجد تحوَّل الكائنات إلى أرقام ونصوص فقط.

2- يحدث التحويل العددي (numeric) عند طرح الكائنات أو تنفيذ العمليات الرياضية عليها. مثلًا، يمكن طرح كائنات Date (سيتم شرحها في فصل التاريخ والوقت) ويكون ناتج طرح date1 - date2 هو الفارق الزمني بين التاريخين.

3- بالنسبة للتحويل إلى نص (string)، يحدث ذلك عادةً عند إخراج كائن بالطريقة alert(obj)‎ وأي سياق مشابه.

التحويل إلى أنواع أساسية عبر ToPrimitive

يمكننا ضبط التحويل إلى نص أو عدد باستخدام دوال خاصة بالكائنات. يوجد ثلاثة أنواع للتحويل بين الأنواع (يطلق عليها «النوع المخمَّن» [hint] الذي سيجري تحويل الكائن إليه)، مشروحة في هذه المواصفات:

إلى سلسلة نصية "string"

يُجرَى التحويل من كائن لنص عند القيام بعمليات على كائن تتوقع أن يكون الكائن نصًا، مثل alert:

// المخرجات
alert(obj);

// استخدام كائن كمفتاح للخاصية
anotherObj[obj] = 123;

إلى عدد "number"

يجري تحويل كائن لعدد عند القيام بعمليات حسابية:

// تحويل صريح
let num = Number(obj);

// ( عمليات رياضية (عدى عملية الجمع الأحادي
let n = +obj; // عملية جمع أحادية
let delta = date1 - date2;

// عملية موازنة أصغر/أكبر
let greater = user1 > user2;

إلى نوع افتراضي "default"

يحدث في حالات نادرة عندما لا يكون المُشَغِّل متأكدًا من نوع البيانات المُتوَقَّع. فمثلًا، يمكن لِمُعامِل الجمع الثنائي + أن يتعامل مع كلًا من النصوص (دمج السلسلتين) ومع الأرقم (بجمعها)، لذا فإن كلًا من النصوص والأرقام تعمل بشكل صحيح. أيضًا، عند موازنة كائن بنص، أو رقم أو رمز، فلا يكون واضحًا أي نوع من التحويلات هو المطلوب.

// الجمع الثنائي
let total = car1 + car2;

// obj == string/number/symbol
if (user == 1) { ... };

يمكن لِمعاملات الموازنة أكبر/أصغر <> التعامل أيضًا مع كلً من النصوص والأعداد لكنها ترجح التحويل إلى عدد دون النظر إلى التحويل الافتراضي المرجح في هذه الحالة وذلك لأسباب تاريخية.

عمليًا، تُطبِّق جميع الكائنات المُدمجة في اللغة التحويل الافتراضي "default" بنفس طريقة التحويل إلى عدد "number". وربما يجب أن نقوم بذلك أيضًا. (عدا الكائن Date، والذي سَنتعلمه لاحقَا.)

لاحظ أن هناك ثلاثة أنواع للتحويل فقط، إذ الأمر بهذه البساطة. لا يوجد تحويل للنوع المنطقي (boolean، فجميع الكائنات تحمل القيمة true في السياق المنطقي) أو أي شيء آخر. وإن عاملنا كلًا من التحويل الافتراضي "default" والتحويل العددي "number" بالطريقة ذاتها كما تقوم أغلب الدوال المدمجة، فسيكون هنالك تحويلين فقط.

تحاول JavaScript العثور على ثلاث دوال للكائن واستدعائها عند القيام بالتحويل

  1. استدعاء obj[Symbol.toPrimitive](hint)‎ - الدالة ذات المفتاح الرمزي Symbol.toPrimitive (رمز نظام)، إن كانت هذه الدالة موجودة.
  2. أو إن كان النوع المخمَّن هو نص "string"
    • جرب obj.toString()‎ و obj.valueOf()‎، أيًا كانت متواجدة.
  3. إن كان hint هو عدد "number" أو "default"
    • جرب obj.valueOf()‎ و obj.toString()‎ أيًا كانت متواجدة.

تابع التحويل Symbol.toPrimitive

لنبدأ من التابع الأول؛ يوجد رمز مُضَمَّن في JavaScript مُسَمَّى Symbol.toPrimitive والذي يجب استخدامه لتسمية تابع التحويل، هكذا:

obj[Symbol.toPrimitive] = function(hint) {
  // يجب أن تُرجِع قيمة أولية
  // النوع المخمَّن هو إما نص أو عدد أو النوع الافتراضي
};

مثلا، الكائن user هنا يتضمنها:

let user = {
  name: "John",
  money: 1000,

  [Symbol.toPrimitive](hint) {
    alert(`hint: ${hint}`);
    return hint == "string" ? `{name: "${this.name}"}` : this.money;
  }
};

// تجربة للتحويل:
alert(user); // hint: string -> {name: "John"}
alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500

كما يمكننا أن نرى في الشيفرة، أصبح user نصًا يصف نفسه أو كمية من المال وفقًا للتحويل. تقوم الدالة user[Symbol.toPrimitive]‎ بجميع حالات التحويل.

التحويل إلى نص عبر toString أو valueOf

ظهر التابعان toString و valueOf منذ وقت طويل، ولا يتبعان إلى نوع الرمز (symbol، إذ لم تكن الرموز موجودة في ذلك الحين)، لكنهما تابعين مسميان بأسماء توحي بارتباطهما بالنوع النصي، إذ كانت توفر آلية لعملية التحويل أصبحت قديمة النمط الآن.

إن لم يكن هناك تنفيذًا للتابع Symbol.toPrimitive فتحاول JavaScript إيجاد هذه الدوال بالترتيب التالي:

  • toString ->‏ valueOf للنصوص.
  • valueOf ->‏ toString لباقي الأنواع.

مثلا، يقوم الكائن user بنفس الغرض السابق باستخدام toString و valueOf:

let user = {
  name: "John",
  money: 1000,

  // في حال النوع المخمَّن سلسلة نصية
  toString() {
    return `{name: "${this.name}"}`;
  },

  // في حال النوع المخمَّن عدد أو النوع الافتراضي
  valueOf() {
    return this.money;
  }

};

alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500

كما رأينا، يتنفذ السلوك ذاته كما في المثال السابق الذي استخدم Symbol.toPrimitive.

نحتاج غالبًا إلى مكان واحد يقوم بجميع التحويلات للأنواع الأولية (نص أو عدد). ففي هذه الحالة، يمكننا استخدام التابع toString فقط لينفِّذ جميع عمليات التحويل، كما يلي:

let user = {
  name: "John",

  toString() {
    return this.name;
  }
};

alert(user); // toString -> John
alert(user + 500); // toString -> John500

سينفِّذ التابع toString بجميع التحويلات الأولية في غياب كلًا من Symbol.toPrimitive و valueOf.

الأنواع المعادة

الأمر المهم الذي يجب معرفته عن جميع دوال التحويل بين الأنواع الأولية هو أنها لا تُرجِع بالضرورة النوع المخمَّن (hint) الأولي ذاته. لا يوجد تحكم في ما إن كانت toString()‎ ترجع نصًا، أو أن Symbol.toPrimitive تُرجِع عددًا عند تحويل عدد. الأمر الوحيد المعروف والثابت هو أنَّ هذه الدوال تُرجِع نوعًا اوليًا وليس كائنًا.

ملاحظة تاريخية

لا يوجد خطأ إن أعاد التابع toString أو valueOf كائنًا، وذلك لأسباب تاريخية، لكن يتم تجاهل مثل هذه القيم (وكأن الدالة ليست موجودة). وذلك لعدم وجود مبدأ الخطأ الجيد (good error) في JavaScript حينها.

بالمقابل، يجب أن يعيد التابع Symbol.toPrimitive قيمة أولية، وإلا فسيكون هناك خطأ.

عمليات تحويل إضافية

عرفنا مسبقًا أن جميع العوامل (operator) والدوال تجري عمليات تحويل على الأنواع التي تتعامل معها مثل معامل الضرب * يحول جميع المُعامَلات (operands) إلى أعداد. فإن مرَّرنا كائنًا عبر وسيط إلى إحدى الدوال أو العمليات، فستمر عملية التحويل على مرحلة أو مرحلتين هما:

  1. يحوَّل الكائن إلى نوع أولي (باستعمال القواعد التي تحدثنا عنها آنفًا)
  2. إن لم يكن النوع الأولي الناتج مطابقًا للنوع المطلوب، فيحوَّل إلى النوع المطلوب وفق مبدأ التحويل بين الأنواع الأولية

إليك المثال التالي:

let obj = {
  // جميع التحويلات في غياب باقي الدوال toString  يجري التابع 
  toString() {
    return "2";
  }
};

// حُوِّل الكائن إلى القيمةا لأولية "2", ثم جعلته عملية الضرب عددًا
alert(obj * 2); // 4 
  1. حوَّلت عملية الضرب obj * 2 الكائن إلى نوع أولي، وكان النوع المعاد من عملية التحويل سلسلةً نصية، هي "2".
  2. جرى بعدئذٍ تحويل تلك السلسلة في العملية ‎"2" * 2، إلى عدد ليصبح 2 * 2.

في المثال التالي، يقوم الجمع الثنائي بدمج النصوص في هذه الحالة والاكتفاء بعملية تحويل الكائن إلى سلسلة نصية:

let obj = {
  toString() {
    return "2";
  }
};

// (أعادت عملية التحويل إلى نوع أولي نصًا => دمج)
alert(obj + 2); // 22 

الخلاصة

تجرَى عملية التحويل من كائن لنوع أولي تلقائيًا بواسطة العديد من الدوال المدمجة في اللغة والمُعاملات التي تتوقع أن تتعامل مع قيم أولية.

يخمَّن النوع الأولي المراد تحويل الكائن إليه إلى:

  • سلسلة نصية "string" (للدالة alert والعمليات الأخرى التي تتعامل مع نصوص)
  • عدد "number" (للعمليات الرياضية) -النوع الافتراضي "default" (لبعض العوامل)

تُحدِّد المواصفات النوع المخمَّن (hint) الذي يستخدمه كل مُعامل بوضوح. يوجد القليل من العوامل التي لا تعلم ما النوع المتوقع وتستخدم النوع الافتراضي "default". يُعامل النوع الافتراضي "default" معامل العدد "number" في الكائنات أغلب الأحيان؛ لذا، يتم دمج النوعين عمليًا.

خوارزمية التحويل كالتالي:

  1. استدعاء obj[Symbol.toPrimitive](hint)‎ إن وُجِدَت،
  2. أو إن كان النوع المخمَّن هو نص "string"
    • جرب obj.toString()‎ و obj.valueOf()‎، أيًا كانت متواجدة.
  3. إن كان النوع المُخمَّن هو عدد "number" أو "default"
    • جرب obj.valueOf()‎ و obj.toString()‎ أيًا كانت متواجدة.

عمليًا، يكفي استخدام obj.toString()‎ فقط لإجراء جميع التحويلات، إذ تعيد "شيئًا مقروءًا" يمثِّل الكائن يستعمل هذا التمثيل لعملية التسجيل أو التنقيح.

ترجمة -وبتصرف- للفصل Object to primitive conversion من كتاب The JavaScript Language

اقرأ أيضًا





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن