Iterable objects are a generalization of arrays. That’s a concept that allows us to make any object useable in a for..of
loop.
Of course, Arrays are iterable. But there are many other built-in objects, that are iterable as well. For instance, strings are also iterable.
If an object isn’t technically an array, but represents a collection (list, set) of something, then for..of
is a great syntax to loop over it, so let’s see how to make it work.
Symbol.iterator
يمكننا أن نفهم بسهولة مفهوم المتكررات عن طريق إنشاء واحد بنفسنا.
على سبيل المثال، إذا كان لدينا كائن ولكنه ليس مصفوقة، ولكنه مناسب للتكرار
for..of
مثل الكائن
range
الذى يعرض مجموعه من الأرقام:
let range = {
from: 1,
to: 5,
};
// نريد أن يعمل التكرار كالآتى:
// for(let num of range) ... num=1,2,3,4,5
To make the range
object iterable (and thus let for..of
work) we need to add a method to the object named Symbol.iterator
(a special built-in symbol just for that).
- When
for..of
starts, it calls that method once (or errors if not found). The method must return an iterator – an object with the methodnext
. - Onward,
for..of
works only with that returned object. - When
for..of
wants the next value, it callsnext()
on that object. - The result of
next()
must have the form{done: Boolean, value: any}
, wheredone=true
means that the iteration is finished, otherwisevalue
is the next value.
Here’s the full implementation for range
with remarks:
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 deeper understanding, let’s see how to use an iterator explicitly.
سنقوم بالتكرار على نص مثلما تعمل 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)
Two official terms look similar, but are very different. Please make sure you understand them well to avoid the confusion.
- المتكررات هي كائنات تستدعى الدالة
Symbol.iterator
، كما أوضحنا سابقًا. - أشباه المصفوفات هي كائنات أيضًا ولكنها تحتوى على
index
وlength
، وبالتالى فهي تشبه المصفوفة.
When we use JavaScript for practical tasks in a browser or any other environment, we may meet objects that are iterables or array-likes, or both.
على سبيل المثال، ستجد أن النصوص (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
تدعى متكررات.
- Technically, iterables must implement the method named
Symbol.iterator
.- The result of
obj[Symbol.iterator]()
is called an iterator. It handles further iteration process. - An iterator must have the method named
next()
that returns an object{done: Boolean, value: any}
, heredone:true
denotes the end of the iteration process, otherwise thevalue
is the next value.
- The result of
- The
Symbol.iterator
method is called automatically byfor..of
, but we also can do it directly. - Built-in iterables like strings or arrays, also implement
Symbol.iterator
. - String iterator knows about surrogate pairs.
إن الكائنات التى تحتوى على indexes
& length
تسمي أشباه المصفوفات. هذه الكائنات يمكنها ان تحتوى أيضًا على خصائص ودوالٍ أخرى ولكنها لا تحتوى على دوال المصفوفات مثل push
& pop
.
إذا نظرنا إلى المصدر – سنجد أن أغلب الدوال الموجوده بالفعل تفترض أن أنها تعمل مع متكررات أو أشباه مصفوفات بدلًا من مصفوفات، لأن هذا أكثر اختصارًا.
إن الدالة Array.from(obj[, mapFn, thisArg])
تصنع مصفوفة حقيقية من متكرر أو شبيهٍ بالمصفوفة، وبالتالى يمكننا استخدام دوال المصفوفات عليهم. حيث أن المتغيرات mapFn
& thisArg
تتيح لنا أن ننفذ دالة على كل عنصر قبل إضافته للمصفوفة.
Array.from(obj[, mapFn, thisArg])
makes a real Array
from 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.