تتوقّع العديد من دوال جافا سكريبت المضمّنة في اللغة عددًا من الوُسطاء لا ينتهي.
مثال:
Math.max(arg1, arg2, ..., argN)
– يُعيد أكبر وسيط من الوُسطاء.Object.assign(dest, src1, ..., srcN)
– ينسخ الخصائص منsrc1..N
إلىdest
.- …وهكذا.
سنتعلّم في هذا الفصل كيف نفعل ذلك أيضًا. كما وكيف نمرّر المصفوفات إلى هذه الدوال على أنّها مُعاملات.
المُعاملات «البقية» ...
يمكن أن ننادي الدالة بأيّ عدد من الوُسطاء كيفما كانت معرّفة الدالة.
هكذا:
function sum(a, b) {
return a + b;
}
alert( sum(1, 2, 3, 4, 5) );
لن ترى أيّ خطأ بسبب تلك الوُسطاء «الزائدة». ولكن طبعًا فالنتيجة لن تأخذ بالحسبان إلا أوّل اثنين.
يمكن تضمين بقية المُعاملات في تعريف الدالة باستعمال الثلاث نقاط ...
ثمّ اسم المصفوفة التي ستحتويهم. تعني تلك النقط حرفيًا «اجمع المُعاملات الباقية في مصفوفة».
فمثلًا لجمع كلّ الوُسطاء في المصفوفة args
:
function sumAll(...args) { // اسم المصفوفة هو args
let sum = 0;
for (let arg of args) sum += arg;
return sum;
}
alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6
يمكن لو أردنا أن نأخذ المُعاملات الأولى في متغيّرات ونجمع البقية فقط.
هنا نأخذ الوسيطين الأوليين في متغيرات والباقي نرميه في المصفوفة titles
:
function showName(firstName, lastName, ...titles) {
alert( firstName + ' ' + lastName ); // Julius Caesar
// الباقي نضعه في مصفوفة الأسماء titles
// مثلًا titles = ["Consul", "Imperator"]
alert( titles[0] ); // Consul
alert( titles[1] ); // Imperator
alert( titles.length ); // 2
}
showName("Julius", "Caesar", "Consul", "Imperator");
تجمع المُعاملات البقية كلّ الوُسطاء التي بقيت. وبهذا فالآتي ليس منطقيًا وسيتسبّب بخطأ:
function f(arg1, ...rest, arg2) { // الوسيط arg2 بعد ...البقية؟!
// خطأ
}
يجب أن يكون ...rest
الأخير دومًا.
متغيّر الوُسطاء arguments
هناك كائن آخر شبيه بالمصفوفات يُدعى arguments
ويحتوي على كلّ الوُسطاء حسب ترتيب فهارسها.
مثال:
function showName() {
alert( arguments.length );
alert( arguments[0] );
alert( arguments[1] );
// المصفوفة مُتعدَّدة
// for(let arg of arguments) alert(arg);
}
// تعرض: 2, Julius, Caesar
showName("Julius", "Caesar");
// تعرض: 1, Ilya, undefined (ما من مُعطى ثانٍ)
showName("Ilya");
قديمًا لم تكن المُعاملات البقية موجودة في اللغة ولم يكن لدينا سوى استعمال arguments
لنجلب كلّ مُعاملات الدالة. وما زالت تعمل الطريقة إلى يومنا هذا ويمكن أن تراها في الشيفرات القديمة.
ولكن السلبية هنا هي أنّ arguments
ليست مصفوفة (على الرغم من أنّها شبيهة بالمصفوفات ومُتعدّدة). بهذا لا تدعم توابِع المصفوفات فلا ينفع أن نستدعي عليها arguments.map(...)
مثلًا.
كما وأنّها تحتوي على كل الوُسطاء دومًا. لا يمكن أن نأخذ منها ما نريد كما نفعل مع المُعاملات البقية.
لهذا متى ما احتجنا إلى ميزة كهذه، فالأفضل استعمال المُعاملات البقية بدلًا من arguments
.
ليس للدوال السهمية "arguments"
لو حاولت الوصول إلى كائن الوُسطاء arguments
من داخل الدالة السهمية، فستستلم الناتج من الدالة «الطبيعية» الخارجية. إليك مثالًا:
function f() {
let showArg = () => alert(arguments[0]);
showArg();
}
f(1); // 1
كما نذكر فليس للدوال السهمية قيمة this
تخصّها، أمّا الآن صرنا نعلم بأنّ ليس لها كائن arguments
أيضًا.
مُعامل التوزيع
رأينا كيف نأخذ مصفوفة من قائمة من المُعطيات.
ولكن ماذا لو أردنا العكس من ذلك؟
فمثلًا لنقل أردنا استعمال الدالة المبنية في اللغة Math.max والتي تُعيد أكبر عدد من القائمة:
alert( Math.max(3, 5, 1) ); // 5
لنقل أنّ لدينا المصفوفة [3, 5, 1]
. كيف نستدعي Math.max
عليها؟
لا ينفع تمريرها «كما هي» لأنّ Math.max
يتوقّع قائمةً بالوُسطاء العددية لا مصفوفة واحدة:
let arr = [3, 5, 1];
alert( Math.max(arr) ); // NaN
وطبعًا لا يمكن أن نفكّ عناصر القائمة يدويًا في الشيفرة Math.max(arr[0], arr[1], arr[2])
لأنّنا في حالات لا نعرف كم من عنصر هناك أصلًا. وما إن يتنفّذ السكربت يمكن أن يكون فيه أكبر مما كتبناه أو حتّى لا شيء أصلًا، وسنحصد لاحقًا ما جنته هذه الشيفرة.
عاش مُنقذنا مُعامل التوزيع! عاش عاش عاش! من بعيد نراه مشابهًا تمامًا للمُعاملات البقية، كما ويستعمل ...
، إلّا أنّ وظيفته هي العكس تمامًا.
فحين نستعمل ...arr
في استدعاء الدالة، «يتوسّع» الكائن المُتعدَّد ...arr
إلى قائمة من الوُسطاء.
فمثلًا نعود إلى Math.max
:
let arr = [3, 5, 1];
// 5 (يحوّل التوزيع المصفوفة إلى قائمة من الوُسطاء)
alert( Math.max(...arr) );
يمكن أيضًا أن نمرّر أكثر من مُتعدَّد واحد بهذه الطريقة:
let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];
alert( Math.max(...arr1, ...arr2) ); // 8
أو حتّى ندمج مُعامل التوزيع مع القيم العادية:
let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];
alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25
كما يمكن أن نستعمل مُعامل التوزيعة لدمج المصفوفات:
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
let merged = [0, ...arr, 2, ...arr2];
alert(merged); // 0,3,5,1,2,8,9,15 (0 ثمّ arr ثمّ 2 ثمّ arr2)
استعملنا في الأمثلة أعلاه مصفوفة لنشرح مُعامل التوزيع، إلّا أنّ المُتعدَّدات أيًا كانت تنفع أيضًا.
فمثلًا نستعمل هنا مُعامل التوزيع لنحوّل السلسلة النصية إلى مصفوفة محارف:
let str = "Hello";
alert( [...str] ); // H,e,l,l,o
يستعمل مُعامل التوزيع هذا داخليًا المُعدِّدات لجمع العناصر، كما تفعل حلقة for..of
.
لذا لو استلمت for..of
سلسلةً نصيّة فتُعيد لنا المحارف وتصير ...str
بالقيمة "H","e","l","l","o"
. وهكذا تُمرّر قائمة المحارف إلى مُهيّئ المصفوفة [...str]
.
يمكننا أيضًا لهذه المهمة استعمال Array.from
إذ أنّه يحوّل المُتعدَّد (مثل السلاسل النصية) إلى مصفوفة:
let str = "Hello";
// يُحوّل Array.from المُتعدَّد إلى مصفوفة
alert( Array.from(str) ); // H,e,l,l,o
ناتجه هو ذات ناتج [...str]
.
ولكن… هناك فرق ضئيل بين Array.from(obj)
و[...obj]
:
- يعمل
Array.from
على الشبيهات بالمصفوفات والمُتعدَّدات. - ويعمل مُعامل التوزيع على المُتعدَّدات فقط لا غير.
لذا لو أردت تحويل شيء إلى مصفوفة فالتابِع Array.from
أكثر استعمالًا وشيوعًا.
الحصول علي نسخة من المصفوفة/الكائن
Copy an array/object
يمكن أن تقوم بالمثل عن طريق ...
.
Remember when we talked about Object.assign()
in the past?
let arr = [1, 2, 3];
let arrCopy = [...arr]; // spread the array into a list of parameters
// then put the result into a new array
// هل متساويين؟
alert(arr === arrCopy); // خطأ (ليس نفس المرجع)
// تعديل مصفوفتنا الأولى لا يتم تعديله في النسخة:
arr.push(4);
alert(arr); // 1, 2, 3, 4
alert(arrCopy); // 1, 2, 3
لاحظ أنه من الممكن أن نقوم بنفس الشئ لصنع نسخة من الكائن أيضاً:
let obj = { a: 1, b: 2, c: 3 };
let objCopy = { ...obj }; // spread the object into a list of parameters
// then return the result in a new object
// هل الكائنات تمتلك نفس القيمة؟
alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // صحيح
// هل متساويين؟
alert(obj === objCopy); // خطأ (ليس نفس المرجع)
// تعديل الكائن الأول لا يعدل في النسخة
obj.d = 4;
alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4}
alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3}
This way of copying an object is much shorter than let objCopy = Object.assign({}, obj)
or for an array let arrCopy = Object.assign([], arr)
so we prefer to use it whenever we can.
متى رأينا "..."
في الشيفرة نعرف أنّه إمّا المُعاملات البقية وأمّا مُعامل التوزيع.
إليك طريقة بسيطة للتفريق بينهما:
- حين ترى
...
موجودة في نهاية مُعاملات الدالة فهي «المُعاملات البقية» وستجمع بقية قائمة الوُسطاء في مصفوفة. - وحين ترى
...
في استدعاء دالة أو ما شابهه فهو «مُعامل توزيع» يوسّع المصفوفة إلى قائمة.
طُرق الاستعمال:
- تُستعمل المُعاملات البقية لإنشاء دوال تقبل أيّ عدد كان من الوُسطاء.
- يُستعمل مُعامل التوزيع لتمرير مصفوفة إلى دوال تطلب (عادةً) قائمة طويلة من الوُسطاء.
كلا الميزتين تساعدك في التنقل بين القائمة ومصفوفة المُعاملات بسهولة ويُسر.
يمكنك أيضًا أن ترى كل وُسطاء استدعاء الدالة «بالطريقة القديمة» arguments
وهو كائن مُتعدَّد شبيه بالمصفوفات.