ماذا يحدث فى حالة جمع كائنين obj1 + obj2
، أو طرحهما obj1 - obj2
أو طباعتهما باستخدام دالة التنبيه alert(obj)
؟
فى هذه الحالة، تتحول الكائنات إلى قيم فردية تلقائيًا، ثم يتم تنفيذ هذه العملية الحسابية.
فى قسم (تحويل الأنواع) رأينا كيف يمكن تحويل النصوص (strings) والأرقام والقيَم المنطقيه (booleans) إلى قيم فردية. ولكننا تركنا مساحة فارغة من أجل الكائنات. والآن بعد أن عرفنا الكثير عن الدوال (methods) والرموز (symbols)، أصبح الآن ممكنًا أن نملأ هذه المساحه.
-
كل الكائنات عند تحويلها إلى قيمه منطقيه (boolean) فإن قيمتها تساوى
true
. وبالتالى فإن التحويلات المتاحة هي التحويل إلى نص أو رقم. -
يحدث التحويل إلى رقم عند طرح كائنين أو استخدام دالة حسابية. على سبيل المثال، الكائنات من نوع
Date
(سيتم شرحها فى قسم التاريخ) يمكن طرحها، ونتيجة طرحdate1 - date2
هي الفرق بين التاريخين. -
وبالنسبه إلى التحويل إلى نص – فإنه يحدث عادة عند طباعة الكائن باستخدام دالة التنبيه
alert(obj)
والدوال المشابهة.
ToPrimitive
يمكننا التحكم فى التحويل إلى نص أو رقم، باستخدام بعض دوال الكائنات.
هناك ثلاث ملاحظات مختلفه على تحويل الأنواع ويطلق عليها “hints” وتم ذكرها فى المصدر:
"النص"
-
يحدث التحويل إلى نص عندما نقوم بعملية معينه على كائن تتوقع نصًا لا كائنًا مثل دالة التنبيه
alert
:// output alert(obj); // using object as a property key anotherObj[obj] = 123;
"الرقم"
-
يحدث التحويل إلى رقم عندما نقوم يعملية حسابيه على سبيل المثال:
// explicit conversion let num = Number(obj); // maths (except binary plus) let n = +obj; // unary plus let delta = date1 - date2; // less/greater comparison let greater = user1 > user2;
"التصرف الإفتراضي"
For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it.
على سبيل المثال، العلامه +
يمكن أن تعمل مع النصوص (حيث تقوم بالإضافه) أو الأرقام (حيث تقوم بالجمع)، ولذلك فإنه يمكن التحويل إلى نصوص أو أرقام. ولذلك إذا استقبلت علامة ال +
كائنا فإنها تستخدم "التصرف الإفتراضي"
.
وأيضًا فى حالة مقارنة كائن مع نص أو رقم أو رمز باستخدام ==
فإنه ليس واضح لأى نوع يمكن التحويل، ولذلك يتم استخدام "التصرف الإفتراضي"
.
// binary plus uses the "default" hint
let total = obj1 + obj2;
// obj == number uses the "default" hint
if (user == 1) { ... };
المقارنه باستخدام علامات الأكبر من أو الأصغر من مثل <
>
، يمكنها التعامل مع الأرقام والنصوص أيضا ولكنها مع ذلك تستخدم التحويل إلى رقم وليس الطريقه الافتراضيه، وهذا لأسباب متأصله historical reasons.
لا نحتاج إلى تذكر كل هذه التفاصيل الغريبه لأن كل الكائنات الموجوده عدا (Date
والذي سيتم شرحه قريبا) يتم تحويلها باستخدام "الطريقه الإفتراضيه"
مثل طريقة التحويل إلى رقم.
"القيم المنطقيه"
لاحظ أن هناك ثلاث طرق (أو ملاحظات) فقط بكل بساطه.
لا توجد طريقة التحويل إلى “قيمه منطقيه” (لأن كل الكائنات قيمتها true
عن تحويلها إلى قيمه منطقيه). وإذا تعاملنا مع "الطريقه الإفتراضيه"
و "الرقم"
بطريقة مشابهة مثل كل الطرق الموجوده فسيكون هناك طريقتين فقط.
عند القيام بالتحويل، تقوم جافا سكريبت باستدعاء ثلاث دوال:
- استدعاء
obj[Symbol.toPrimitive](hint)
– وهو رمز موجود بالفعل (built-in)، وهذا فى حالة وجود هذه الدالة. - فى حالة عدم وجودها وطانت الطريقه هى التحويل إلى نص
- استخدام
obj.toString()
وobj.valueOf()
، أيهم موجود.
- استخدام
- غير ذلك، إذا كانت الطريقه هي
"الطريقه الإفتراضيه"
أو"الرقم"
- استخدام
obj.valueOf()
وobj.toString()
، أيهم موجود.
- استخدام
Symbol.toPrimitive
لنبدأ بأول طريقه. يوجد رمز (symbol) موجود بالفعل يسمى Symbol.toPrimitive
والذي يجب استخدامه لتسمية طريقة التحويل كالآتى:
obj[Symbol.toPrimitive] = function(hint) {
// must return a primitive value
// hint = one of "string", "number", "default"
};
على سبيل المثال, يطبق هذه الطريقه الكائن user
:
let user = {
name: 'John',
money: 1000,
[Symbol.toPrimitive](hint) {
alert(`hint: ${hint}`);
return hint == 'string' ? `{name: "${this.name}"}` : this.money;
},
};
// conversions demo:
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.toPrimitive
فإن جافا سكريبت تقوم بالبحث عنهمو استخدامهم بالترتيب الآتى:
toString -> valueOf
فى الطريقه النصيه.valueOf -> toString
غير ذلك.
هذه الدوال لابد أن تقوم بإرجاع قيمه فردية. فإذا قامت هاتان الدالتان بإرجاع كائن فسيتم تجاهله.
أى كائن يمتلك افتراضيا الدالتين toString
و valueOf
:
- الداله
toString
تقوم بإرجاع النص"[object Object]"
. - الداله
valueOf
تقوم بإرجاع الكائن نفسه.
كما فى المثال:
let user = { name: 'John' };
alert(user); // [object Object]
alert(user.valueOf() === user); // true
لذلك إذا حاولنا أن نستخدم الكائن كنص، كما فى حالة استخدام الداله النصيه alert
سنرى بشكل افتراضي [object object]
.
الداله valueOf
تم ذكرها هنا فقط لإكمال المعلومات ولتجنب أى التباس. فكما ترى فإن هذه الداله تقوم بإرجاع الكائن نفسه وبالتالى يتم تجاهله. لا تسأل لماذا فهذا لأسباب متأصله historical reasons. ولذلك يمكننا اعتبار أنها غير موجوده.
هيا نقوم باستخدام هذه الدوال.
على سبيل المثال، فإن الكائن user
هنا يقوم بنفس التصرف أعلاه عند استخدام خليط من toString
و valueOf
بدلًا من Symbol.toPrimitive
:
let user = {
name: 'John',
money: 1000,
// for hint="string"
toString() {
return `{name: "${this.name}"}`;
},
// for hint="number" or "default"
valueOf() {
return this.money;
},
};
alert(user); // toString -> {name: "John"}
alert(+user); // valueOf -> 1000
alert(user + 500); // valueOf -> 1500
كما نرى هنا فإن التصرف هو نفسه الموجود فى المثال السابق عند استخدام Symbol.toPrimitive
.
ونحن عالبا مانحتاج إلى طريقه للتعامل مع كل حالات التحويل إلى قيم فرديه (primitive values). ففى هذه الحاله يمكننا استخدام toString
فقط كالآتى:
let user = {
name: 'John',
toString() {
return this.name;
},
};
alert(user); // toString -> John
alert(user + 500); // toString -> John500
فى حالة غياب Symbol.toPrimitive
و valueOf
فإن toString
ستقوم بالتعامل مع كل حالات التحويل إلى قيم فرديه.
أنواع القيم المسترجعه
هناك شئ مهم يجب أن تعرفه وهو أن كل طرق التحويل إلى قيم مفرده لا يجب بالضروره أن تقوم بإرجاع نفس نوع القيمه المفرده المحوَّله إليه.
فلا يوجد ضمانه إذا كانت toString
ستقوم بإرجاع نص بالتحديد أو حتى Symbol.toPrimitive
ستقوم بإرجاع رقم فى طريقة "الرقم"
.
الأمر الوحيد الذى يمكن ضمانه والإلزامى هو أن هذه الدوال يجب أن تقوم بإرجاع يمة مفردة لا كائنًا.
لأسباب قديمه historical reasons فإنه فى حالة أن الدوال
toString
or valueOf
قامت بإرجاع كائن، فلا يوجد خطأ يظهر، بل يتم تجاه النتيجه فقط كأن شيئًا لم يكن. وذلك لأنه فى الماضي لم يكن هناك مفهوم جيد للخطأ فى جافا سكريبت.
على النقيض، فإن Symbol.toPrimitive
يجب أن تقوم بإرجاع قيمة مفرده، وإلا سيكون هناك خطأ.
التحويلات الإضافيه
كما نعرف بالفعل أن الكثير من العلامات والدوال تقوم بتحويل الأنواع, مثال على ذلك علامة *
تقوم بتحويل العاملين إلى أرقام.
إذا استخدمنا كائنين كعملين رياضيين فسيكون هناك مرحلتين:
- تحويل إلى الكائن إلى قيمه مفردة.
- إذا كانت نتيجة التحويل ليست من النوع الصحيح فسيتم تحويلها.
على سبيل المثال:
let obj = {
// toString handles all conversions in the absence of other methods
toString() {
return '2';
},
};
alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number
- عملية الضرب
obj * 2
تقوم أولا بتحويل الكائن إلى قيمة مفرده (والذي هو النص"2"
). - ثم بعد ذلك فإن الجمله
"2" * 2
تتحول إلى2 * 2
(يتحول النص إلى رقم).
علامة الجمع +
ستقوم بإضافة النصوص فى نفس هذا الموقف لأنها تعمل مع النصوص:
let obj = {
toString() {
return '2';
},
};
alert(obj + 2); // 22 ("2" + 2), conversion to primitive returned a string => concatenation
الملخص
التحويل من كائن إلى قيمة مفردة يحدث تلقائيا عن طريق الكثير من الدوال الموجوده بالفعل والعمليات التى تُجرى والتى تعمل فقط على قيم مفردة وليس كائنات.
هناك 3 أنواع من طرق التحويل:
"النص"
(ويحدث ذلك عند استخدام دالة التنبيهalert
والتى تتوقع نصًا)."الرقم"
(فى العمليات الحسابيه)."الطريقة الإفتراضيه"
(فى بعض العمليات).
يوضح المصدر أى عملية تستخدم أى طريقه. وهناك القليل من العمليات التي
“لا تعلم ما نوع العامل الذي ستستقبله”
وتستخدم "الطريقه الإفتراضيه"
. وعادةً ما يتم استخدام "الطريقة الإفتراضيه"
مع الكائنات الموجوده بالفعل كما يتم التعامل مع "الأرقام"
, ولذلك عمليا فإن الطريقتين الأخيرتين يمكن ضمهما معًا.
تتم طريقة التحويل كالآتى:
- استدعاء الداله
obj[Symbol.toPrimitive](hint)
فى حالة وجودها, - غير ذلك إذا كانت الظريقه
"نصًا"
- استخدام
obj.toString()
وobj.valueOf()
فى حالة وجود أي منهم.
- استخدام
- غير ذلك إذا كانت الطريقة
"رقمًا"
أو"الطريقة الإفتراضيه"
- استخدام
obj.valueOf()
أوobj.toString()
فى حالة وجود أى منهم.
- استخدام
ويكفى عمليًا استخدام obj.toString()
لكل التحويلات والتى تقوم بإرجاع قيمة يمكن قرائتها من أجل الطباعة أو البحث عن الأخطاء.
التعليقات
<code>
، وللكثير من السطور استخدم<pre>
، ولأكثر من 10 سطور استخدم (plnkr, JSBin, codepen…)