الكائنات المُكرَّرة (Iterables) هي مفهوم أعمّ من المصفوفات. تتيح لنا هذه الكائنات تحويل أيّ كائن إلى ”كائن يمكن تكراره“ فيمكننا استعماله في حلقة for..of
.
بالطبع فالمصفوفات يمكن تكرارها، ولكن هناك كائنات أخرى (مضمّنة في أصل اللغة) يمكن تكرارها أيضًا، مثل السلاسل النصية.
لو لم يكن الكائن مصفوفة تقنيًا، ولكن يمكننا تمثيله على أنّه تجميعة من العناصر (النوع list
، والنوع set
)، فصياغة for..of
ممتازة لنمرّ على عناصره. لذا دعنا نرى كيف يمكن توظيف هذه ”المُكرَّرات“.
Symbol.iterator
يمكن لنا أن نُدرك هذا المفهوم -مفهوم المُكرَّرات أعني- بأن نصنع واحدًا بنفسنا. لنقل أنّ لدينا كائن وهو ليس بمصفوفة بأيّ شكل، ولكن يمكن توظيفه لحلقة for..of
. مثلًا كائن المدى هذا range
يُمثّل مجموعة متتالية من الأعداد.
let range = { from: 1, to: 5 }; // نُريد أن تعمل for..of هكذا: // for(let num of range) ... num=1,2,3,4,5
لنُضيف خاصية التكرار إلى range
(فتعمل بهذا for..of
)، علينا إضافة تابِع إلى الكائن بالاسم Symbol.iterator
(وهو رمز خاصّ في اللغة يتيح لنا هذه الميزة).
-
حين تبدأ
for..of
، تنادي ذلك التابِع مرة واحدة (أو تعرض الأخطاء المعروفة لو لم تجدها). على هذا التابِع إعادة مُكرِّر/iterator، أي كائنًا له التابِعnext
. -
بعدها، تعمل
for..of
مع ذلك الكائن المُعاد فقط لا غير. -
حين تحتاج
for..of
القيمة التالية، تستدعيnext()
لذاك الكائن. -
يجب أن يكون ناتج
next()
بالشكل هذا{done: Boolean, value: any}
، حيث لو كانتdone=true
فيعني أن التكرار اكتمل، وإلّا فقيمةvalue
هي التالية.
إليك النص الكامل لتنفيذ كائن range
(مع الملاحظات):
let range = { from: 1, to: 5 }; // 1. حين ننادي for..of فهي تنادي هذه range[Symbol.iterator] = function() { // ...وتُعيد الكائن المُكرِّر: // 2. بعد ذلك تعمل for..of مع هذا المُكرِّر، طالبةً منه القيم التالية return { current: this.from, last: this.to, // 3. يُستدعى next() في كلّ كرّة في حلقة for..of next() { // 4. يجب أن يُعيد القيمة كائنًا كهذا {done:.., value :...} if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; }; // والآن تعمل! for (let num of range) { alert(num); // 1، ثمّ 2 فَ 3 فَ 4 فَ 5 }
لاحظ الميزة الأساس للمُكرَّرات: فصل الاهتمامات.
-
عنصر
range
ذاته ليس له التابِعnext()
. -
بدل ذلك، يُنشأ كائن آخر (أي "المُكرَّر") عند استدعاء
range[Symbol.iterator]()
، وتابِعهnext()
يُولّد قيم التكرار.
الخلاصة هي أنّ كائن المُكرَّر منفصل عن الكائن الذي يُكرِّره هذا المُكرَّر.
نظريًا، يمكننا دمجهما معًا واستعمال كائن range
نفسه مُتعدَّدًا لتبسيط الكود أكثر. هكذا تمامًا:
let range = { from: 1, to: 5, [Symbol.iterator]() { this.current = this.from; return this; }, next() { if (this.current <= this.to) { return { done: false, value: this.current++ }; } else { return { done: true }; } } }; for (let num of range) { alert(num); // 1، ثمّ 2 فَ 3 فَ 4 فَ 5
الآن صار يُعيد range[Symbol.iterator]()
كائن range
نفسه: وهذا الكائن فيه تابِع next()
اللازم كما ويتذكّر حالة التعداد الحالية في this.current
. الشيفرة أبسط، أجل. وأحيانًا لا بأس به هكذا.
المشكلة هنا هي استحالة وجود حلقتي for..of
تعملان على الكائن في آن واحد، إذ سيتشاركان حالة التعداد؛ فليس هناك إلا مُتعدَّد واحد: الكائن نفسه. لكن أصلًا وجود حلقتي for..of نادر، حتى في العمليات غير المتزامَنة.
مُكرَّرات لا تنتهي يمكن أيضًا ألا تنتهي المُكرَّرات أبدًا. فمثلًا لا ينتهي المدى range
لو صار range.to = Infinity
. يمكن أيضًا أن نصنع كائن مُتعدَّد يولّد أعدادًا شبه عشوائية (pseudorandom) لانهائية، ستفيد في حالات حرجة.
ما من حدود مفروضة على ناتج next
. يمكنه إعادة القيم مهما أراد وبالكم الذي أراد، لا مشكلة. طبعًا، المرور على هذا المُكرَّر بحلقة for..of
لن ينتهي أبد الدهر، ولكن يمكننا إيقافها باستعمال break
.
السلاسل النصية مُكرَّرة
تُعدّ المصفوفات والسلاسل النصية أكثر المُكرَّرات المقدَّمة من اللغة استعمالًا. بالنسبة إلى السلاسل النصية، فحلقة for..of
تمرّ على محارفها:
for (let char of "test") { // تتنفّذ أربع مرات: مرة لكلّ محرف alert( char ); // t فَ e فَ s فَ t }
كما وتعمل -كما يجب!- مع الأزواج النائبة أو البديلة (Surrogate Pairs)!
let str = '??'; for (let char of str) { alert( char ); // ? وثمّ ? }
نداء المُكرَّر جهارة
لنعرف المُكرَّرات معرفةً أعمق، لنرى كيف يمكن استعمالها جهارةً.
سنمرّ على سلسلة نصية بنفس الطريقة التي يمرّ بها for..of
، ولكن هذه المرة ستكون النداءات مباشرة. تُنشِئ هذه الشيفرة مُكرَّرًا لسلسلة نصية وتأخذ القيم منه "يدويًا":
let str = "Hello"; // تنفّذ ما تنفّذه // for (let char of str) alert(char); *!* let iterator = str[Symbol.iterator](); */!* while (true) { let result = iterator.next(); if (result.done) break; alert(result.value); // تطبع المحارف واحدًا تلو الآخر }
في الحياة الواقعية، نادرًا ما ستحتاج هذا. لكن المفيد أنّنا نتحكّم أكثر على عملية التكرار موازنةً بِـ for..of
. فمثلًا يمكننا تقسيم عملية التكرار: نكرّر قليلًا، نتوقّف ونفعل شيئًا آخر، ثمّ نواصل التكرار.
المُكرَّرات والشبيهات بالمصفوفات
هذان المصطلحان الرسميان يبدوان متشابهين إلى حدّ ما، ولكنّهما مختلفين تمام الاختلاف. حاوِل إدراكهما إدراكًا صحيحًا لتتجنّب هذا الاختلاط لاحقًا.
-
المُكرَّرات كائنات تُنفّذ التابِع
Symbol.iterator
، كما شرحنا أعلاه. -
الشبيهات بالمصفوفات كائنات لها فهارس وصفة طول
length
، وبهذا ”تشبه المصفوفات“… المصطلح يشرح نفسه.
حين نستعمل جافاسكربت للمهام الحقيقية في المتصفحات وغيرها من بيئات، نقابل مختلف الكائنات أكانت مُكرَّرات أو شبيهات بالمصفوفات، أو كليهما معًا.
السلاسل النصية مثلًا مُكرَّرة (يمكن استعمال for..of
عليها)، وشبيهة بالمصفوفات أيضًا (لها فهارس عددية وصفة length
). ولكن ليس من الضروري أن يكون المُكرَّر شبيه بالمصفوفة، والعكس صحيح (لا يكون الشبيه بالمصفوفة مُكرَّر). فالمدى range
في المثال أعلاه مُكرَّر، ولكنه ليس شبيه بالمصفوفة إذ ليس فيه صفات فهارس وlength
.
إليك كائنًا شبيهًا بالمصفوفات وليس مُكرَّرًا:
let arrayLike = { // فيه فهارس وطول => شبيه بالمصفوفات 0: "Hello", 1: "World", length: 2 }; // خطأ (ما من Symbol.iterator) for (let item of arrayLike) {}
عادةً، لا تكون لا المُكرَّرات ولا الشبيهات بالمصفوفات مصفوفات حقًا، فليس لها push
أو pop
وغيرها. لكن هذا غير منطقي. ماذا لو كان لدينا كائن من هذا النوع وأردنا التعامل معه بأنه مصفوفة؟ لنقل أنّا سنعمل على range
باستعمال توابِع المصفوفات، كيف السبيل؟
Array.from
التابِع العام Array.from يأخذ مُكرَّرًا أو شبيهًا بالمصفوفات ويحوّله إلى مصفوفة "فعلية". بعدها ننادي توابِع المصفوفات التي نعرفها عليها.
هكذا مثلًا:
let arrayLike = { 0: "Hello", 1: "World", length: 2 }; let arr = Array.from(arrayLike); // (*) alert(arr.pop()); // تكتب World (أيّ أنّ التابِع عمل)
يأخذ التابِع Array.from
في سطر (*)
الكائن، ويفحصه أكان مُكرَّرًا أو شبيهًا بالمصفوفات، ويصنع مصفوفة جديدة ينسخ قيم ذلك الكائن فيها.
ذات الأمر للمُكرَّرات:
// نأخذ range من المثال أعلاه let arr = Array.from(range); alert(arr); // تكتب 1,2,3,4,5 (تحويل toString للمصفوفة يعمل)
والصياغة الكاملة للتابِع Array.from
تتيح لنا تقديم دالة ”خريطة“ اختيارية:
Array.from(obj[, mapFn, thisArg])
يمكن أن يكون الوسيط الاختياري الثاني mapFn
دالةً تُطبّق على كلّ عنصر قبل إضافته للمصفوفة، ويتيح thisArg
ضبط ماهيّة this
للتابِع.
مثال:
// نأخذ range من المثال أعلاه // نُربّع كلّ عدد let arr = Array.from(range, num => num * num); alert(arr); // 1,4,9,16,25
هنا نستعمل Array.from
لتحويل سلسلة نصية إلى مصفوفة من المحارف:
let str = '??'; // يقسم str إلى مصفوفة من المحارف let chars = Array.from(str); alert(chars[0]); // ? alert(chars[1]); // ? alert(chars.length); // 2
على العكس من str.split
، فهي هنا تعتمد على طبيعة تكراريّة السلسلة النصية، ولهذا تعمل كما ينبغي (كما تعمل for..of
) مع الأزواج النائبة.
هنا أيضًا تقوم بذات الفعل، نظريًا:
let str = '??'; let chars = []; // داخليًا، تُنفّذ Array.from ذات الحلقة for (let char of str) { chars.push(char); } alert(chars);
…ولكن تلك أقصر.
يمكننا أيضًا صناعة تابِع slice
مبني عليها يحترم الأزواج النائبة.
function slice(str, start, end) { return Array.from(str).slice(start, end).join(''); } let str = '???'; alert( slice(str, 1, 3) ); // ?? // التابِع الأصيل/native في اللغة لا يدعم الأزواج النائبة alert( str.slice(1, 3) ); // يُولّد نصّ ”قمامة“ (قطعتين من أزواج نائبة مختلفة)
خلاصة
تُدعى الكائنات التي يمكن استعمالها في for..of
بالمُكرَّرات (Iterables).
-
على المُكرَّرات (تقنيًا) تنفيذ التابِع بالاسم
Symbol.iterator
.-
يُدعى ناتج
obj[System.iterator]
بالمُكرَّر. يتعامل المُكرَّر بعملية التكرار. -
يجب أن يحتوي المُكرَّر التابِع بالاسم
next()
حيث يُعيد كائن{done: Boolean, value: any}
… تُشيرdone:true
هنا بأنّ التكرار اكتمل، وإلّا فَـvalue
هي القيمة التالية.
-
يُدعى ناتج
-
تُنادي الحلقة
for..of
التابِعSymbol.iterator
تلقائيًا عند تنفيذها، ولكن يمكننا أيضًا فعل ذلك يدويًا. -
تُنفّذ المُكرَّرات المضمّنة في اللغة
Symbol.iterator
(مثل السلاسل النصية والمصفوفات). - مُكرَّر السلاسل النصية يفهم الأزواج البديلة.
تُدعى الكائنات التي فيها صفات فهارس وصفة طول length
بالشبيهات بالمصفوفات. يمكن أيضًا أن تكون لها صفات وتوابِع أخرى، إلّا أنّ ليس فيها توابِع المصفوفات المضمّنة في بنية اللغة.
لو نظرنا ورأينا مواصفات اللغة، فسنرى بأنّ أغلب التوابِع المضمّنة فيها تتعامل مع المصفوفات على أنّها مُكرَّرات أو شبيهات بالمصفوفات بدل أن تكون مصفوفات ”حقيقية“؛ هكذا تصير أكثر تجرّديّة (abstract).
تصنع Array.from(obj[, mapFn, thisArg])
مصفوفةً Array
حقيقية من المُكرَّر أو الشبيه بالمصفوفات obj
، بهذا يمكن استعمال توابِع المصفوفات عليها. يتيح لنا الوسيطين mapFn
وthisArg
تقديم دالة لكلّ عنصر من عناصرها.
ترجمة -وبتصرف- للفصل Iterables من كتاب The JavaScript language
اقرأ أيضًا
- المقال التالي: النوع Map (الخرائط) والنوع Set (الأطقم)
- المقال السابق: توابع المصفوفات (Array methods)
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.