المتكررات
هي تعميم للمصفوفات
(arrays).
وهذا مفهوم يسمح باستخدام أى كائن فى التكرار من نوع
for..of
.
إن المصفوفات عباره عن متكررات (iterables) بالطبع. ولكن هناك كائنات أخرى موجوده بالفعل والتى هي متكرره أيضا. فعلى سبيل المثال ستجد أن النصوص (strings) متكررة أيضا.
إذا كان الكائن ليس فعليًا عباره عن مصفوفه ولكنها تعرض مجموعة من العناصر
(قائمة أو مجموعه)
إذًا فإن التكرار
for..of
هو خيار جيّد للتكرار على هذا الكائن. فهيّا بنا نرى كيف نحقق هذا.
Symbol.iterator
يمكننا أن نفهم بسهولة مفهوم المتكررات عن طريق إنشاء واحد بنفسنا.
على سبيل المثال، إذا كان لدينا كائن ولكنه ليس مصفوقة، ولكنه مناسب للتكرار
for..of
مثل الكائن
range
الذى يعرض مجموعه من الأرقام:
let range = {
from: 1,
to: 5,
};
// نريد أن يعمل التكرار كالآتى:
// for(let num of range) ... num=1,2,3,4,5
لنحوّل الكائن
range
متكرر (وبالتالى يقوم التكرار for..of
بعمله)
سنقوم بإضافة دالة إلى الكائن تسمي
Symbol.iterator
(رمز موجود بالفعل من أجل هذا).
- عندما يبدأ
for..of
, فإنه يقوم باستدعاء هذه الدالة مرة واحده (أو تعرض خطأًا إن وُجد). وهذه الدالة يجب أن تقوم بإرجاع متكرر – أى كائن بالدالة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, then 2, 3, 4, 5
}
لاحظ الميزة الأساسيه للمتكررات: الفصل بين المفاهيم (separation of concerns).
- الكائن
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, then 2, 3, 4, 5
}
والآن تقوم
range[Symbol.iterator]()
تقوم بإرجاع الكائن
range
:
فهي تحتوى على الدالة next()
وتقوم باستذكار الدورة الحالية فى this.current
. أليس هذا أقصر ؟ وفى بعض الأحيان يكون جيدًا أيضًا.
إن العيب الوحيد الآن هو أنه من المستحيل أن يكون هناك تكرارين من النوع for..of
يعملان على نفس الكائن فى نفس الوقت: حيث أن كل التكرارات يتشاركون نفس حالة التكرار – وهى الكائن نفسه. ولكن وجود تكرارين يعملان فى نفس الوقت على نفس الكائن هي حالة نادرة حتى فى الحالات المتزامنة.
التكرارات الغير نتهية ممكنة أيضًا. فعلى سبيل المثال، سيصبح الكائن range
غير منتهي عندما range.to = Infinity
. أو يمكننا أن نصنع كائنا متكررًا والذى يقوم بإنشاء تتابعًا غير منتهي من الأرقام العشوائية. ويمكن لهذا أن يكون مفيدأ أيضًا.
لا يوجد أى حدود للدالة next
حيث يمكنها أن تقوم بإرجاع المزيد والمزيد من القيم وهذا طبيعي.
إن التكرار من النوع for..of
على متكرر كهذا سيكون بالطبع غير منتهي. ولكن يمكننا دائما إيقافه باستخدام break
.
النصوص كمتكررات
إن النصوص (strings) والمصفوفات (arrays) هي متكررة بالطبيعة.
بالنسبة إلى النص، فإن التكرارات من نوع for..of
ستقوم بالتكرار على حروف النص:
for (let char of "test") {
// يتم استدعؤها 4 مرات، مرة لكل حرف
alert(char); // t, then e, then s, then t
}
وتعمل أيضا بشكل صحيح مع الأشكال!
let str = "𝒳😂";
for (let char of str) {
alert(char); // 𝒳, and then 😂
}
استدعاء المتكرر بوضوح
لفهم أعمق، هيا بنا نرى كيف يمكننا استخدام المتكرر صراحةً.
سنقوم بالتكرار على نص مثلما تعمل 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
. على سبيل المثال، يمكننا فصل عملية التكرار: نكرر قليلًا ثم نقف ثم نفعل شيئًا آخر ثم نستأنف التكرار.
المتكررات وأشباه المصفوفات (array-likes)
يوجد مصطلحان يبدوان شبيهين ولكنهما مختلفين تمامًا. من فضلك تأكد من فهمك لهم حتى لا تتحيّر.
- المتكررات هي كائنات تستدعى الدالة
Symbol.iterator
، كما أوضحنا سابقًا. - أشباه المصفوفات هي كائنات أيضًا ولكنها تحتوى على
index
وlength
، وبالتالى فهي تشبه المصفوفة.
عندما نستخدم الجافاسكريبت لمهام فى المتصفح (browser) أو أى بيئة أخرى، يمكننا أن نجد كائنات متكررة أو شبيهة بالمصفوفه أو كلاهما.
على سبيل المثال، ستجد أن النصوص (strings) عبارة عن متكرر (حيث يمكن استخدام for..of
معها) وكذلك هي شبيهة بالمصفوفه (لأنها تحتوي على length
و indexes
).
ولكن المتكرر يمكن أن لا يكون شبيهًا بالمصفوفه. والعكس صحيح.
على سبيل المثال، فإن الكائن range
الذى استخدمناه سابقًا هو متكرر ، ولكنه ليس شبيهًا بالمصفوفه لأنها لا تحتوي على indexes
أو length
.
ويوجد أيضًا الكائن الذى هو شبيه بالمصفوفة ولكنه ليس متكررًا:
let arrayLike = { // تحتوى على indexes & length إذًا فهي شبيهة بالمصفوفة ولكنها ليست متكررًا
0: "Hello",
1: "World",
length: 2
};
// خطأ (no 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])
المتغير الثاني الإفتراضي الذي تستقبله الدالة Array.from
mapFn
يمكن أن يكون دالة تعمل على كل عنصر قبل إضافته للمصفوفة الجديدة، والمتغير thisArg
يتيح لنا بأن نحدد قيمة this
للعنصر.
على سبيل المثال:
// على فرض أن الكائن range مأخوذ من المثال السابق
// تربيع كل رقم
let arr = Array.from(range, (num) => num * num);
alert(arr); // 1,4,9,16,25
فى هذا المثال استخدمنا Array.from
لتقوم بتحويل النص إلى مصفوفة من الحروف:
let str = "𝒳😂";
// splits str into array of characters
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 internally does the same loop
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)); // 😂𩷶
// الدالة الأساسية لا تدعم الأشكال
alert(str.slice(1, 3)); // قطعة من كل شكل!
الملخص
إن الكائنات التى يمكن استخدامها فى التكرار for..of
تدعى متكررات.
- فعليًا، يجب أن تحتوى المتكررات على دالة تسمى
Symbol.iterator
.- إن نتيجة استدعاء
obj[Symbol.iterator]
يسمى متكررًا. وهى تقوم بالتعامل مع عملية التكرار. - إن المتكرر يجب أن يحتزى على دالة تسمي
next()
والتى تقوم بإرجاع كائن على الشكل{done: Boolean, value: any}
, وإذا كانتdone:true
فهذا يعني توقف التكرار، غير ذلك فإن الخاصيةvalue
تحتوى على القيمة التالية.
- إن نتيجة استدعاء
- إن الدالة
Symbol.iterator
يتم استدعاؤها تلقائيًا عن طريقfor..of
، ولكن يمكننا أيضًا أن نفعله مباشرةً. - إن المتكررات الموجودة بالفعل مثل النصوص والمصفوفات تقوم أيضًا باستدعاء الدالة
Symbol.iterator
. - النص المتكرر يدعم الأشكال.
إن الكائنات التى تحتوى على indexes
& length
تسمي أشباه المصفوفات. هذه الكائنات يمكنها ان تحتوى أيضًا على خصائص ودوالٍ أخرى ولكنها لا تحتوى على دوال المصفوفات مثل push
& pop
.
إذا نظرنا إلى المصدر – سنجد أن أغلب الدوال الموجوده بالفعل تفترض أن أنها تعمل مع متكررات أو أشباه مصفوفات بدلًا من مصفوفات، لأن هذا أكثر اختصارًا.
إن الدالة Array.from(obj[, mapFn, thisArg])
تصنع مصفوفة حقيقية من متكرر أو شبيهٍ بالمصفوفة، وبالتالى يمكننا استخدام دوال المصفوفات عليهم. حيث أن المتغيرات mapFn
& thisArg
تتيح لنا أن ننفذ دالة على كل عنصر قبل إضافته للمصفوفة.
Array.from(obj[, mapFn, thisArg])
makes a real Array
of an iterable or array-like obj
, and we can then use array methods on it. The optional arguments mapFn
and thisArg
allow us to apply a function to each item.
التعليقات
<code>
، وللكثير من السطور استخدم<pre>
، ولأكثر من 10 سطور استخدم (plnkr, JSBin, codepen…)