هذه المقالة مقدمة متعمقة عن العناصر التكرارية والمكررات في JavaScript. كان الدافع الرئيسي لكتابة هذا هو إعداد نفسي للتعرف على المولدات. في الواقع ، كنت أخطط للتجربة لاحقًا بدمج المولدات وخطافات React. إذا كنت مهتمًا ، فاتبع Twitter أو YouTube !
في الواقع ، خططت أن أبدأ بمقال عن المولدات ، ولكن سرعان ما أصبح واضحًا أنه من الصعب التحدث عنها دون فهم جيد للمواد التكرارية والمكررات. سوف نركز عليهم الآن. سأفترض أنك لا تعرف أي شيء عن هذا الموضوع ، لكن في نفس الوقت سوف نتعمق فيه بشكل كبير. لذلك إذا كنت شيئا تعرف على العناصر التكرارية والمكررات ، لكن لا تشعر بالراحة في استخدامها ، فهذه المقالة ستساعدك.
المقدمة
كما لاحظت، ونحن نناقش iterables و المكررات. هذه مفاهيم مترابطة ، لكنها مختلفة ، لذلك عند قراءة المقال ، انتبه إلى أي منها تتم مناقشته في حالة معينة.
لنبدأ بالكائنات القابلة للتكرار. ما هذا؟ هذا شيء يمكن تكراره ، على سبيل المثال:
for (let element of iterable) {
// do something with an element
}
يرجى ملاحظة أننا هنا ننظر فقط في الحلقات
for ... of
التي تم تقديمها في ES6. والحلقات
for ... in
هي بناء قديم لن نشير إليه على الإطلاق في هذه المقالة.
الآن قد تفكر ، "حسنًا ، هذا المتغير القابل للتكرار هو مجرد مصفوفة!" هذا صحيح ، المصفوفات قابلة للتكرار. ولكن توجد الآن هياكل أخرى في JavaScript الأصلي يمكن استخدامها في حلقة
for ... of
. هذا إلى جانب المصفوفات ، هناك كائنات أخرى قابلة للتكرار.
على سبيل المثال ، يمكننا التكرار
Map
، المقدم في ES6:
const ourMap = new Map();
ourMap.set(1, 'a');
ourMap.set(2, 'b');
ourMap.set(3, 'c');
for (let element of ourMap) {
console.log(element);
}
سيعرض هذا الرمز:
[1, 'a']
[2, 'b']
[3, 'c']
أي أن المتغير
element
في كل مرحلة من مراحل التكرار يخزن مصفوفة من عنصرين. الأول هو المفتاح ، والثاني هو القيمة.
أننا كنا قادرين على استخدام حلقة
for ... of
ل أعاد
Map
يثبت أن
Map
الصورة هي iterable. مرة أخرى
for ... of
، يمكن استخدام الكائنات القابلة للتكرار فقط في الحلقات . بمعنى ، إذا كان هناك شيء يعمل مع هذه الحلقة ، فهو كائن قابل للتكرار.
من المضحك أن المنشئ
Map
يقبل اختياريًا تكرارات أزواج المفتاح والقيمة. وهذا يعني أن هذه طريقة بديلة لبناء نفس الشيء
Map
:
const ourMap = new Map([
[1, 'a'],
[2, 'b'],
[3, 'c'],
]);
ونظرًا لأنه
Map
قابل للتكرار ، فيمكننا عمل نسخ منه بسهولة بالغة:
const copyOfOurMap = new Map(ourMap);
لدينا الآن
Map
مفتاحان مختلفان ، على الرغم من أنهما يخزنان نفس المفاتيح بنفس القيم.
لذلك رأينا مثالين على الكائنات القابلة للتكرار - المصفوفة و ES6
Map
. لكننا لا نعرف حتى الآن كيف حصلوا على القدرة على التكرار. الجواب بسيط: هناك مكررات مرتبطة بهم . كن حذرًا: التكرارات ليست قابلة للتكرار .
كيف يرتبط مكرر بكائن قابل للتكرار؟ يجب أن يحتوي الكائن القابل للتكرار ببساطة على وظيفة في خاصيته
Symbol.iterator
. عندما يتم استدعاؤها ، يجب أن تقوم الوظيفة بإرجاع مكرر لهذا الكائن.
على سبيل المثال ، يمكنك استرداد مصفوفة مكرر:
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
console.log(iterator);
يتم إخراج هذا الرمز إلى وحدة التحكم
Object [Array Iterator] {}
. نحن نعلم الآن أن المصفوفة لها مكرر مرتبط ، وهو نوع من الكائنات.
ما هو مكرر؟
انه سهل. المكرر هو كائن يحتوي على طريقة
next
. عندما يتم استدعاء هذه الطريقة ، يجب أن تعود:
- القيمة التالية في تسلسل القيم ؛
- معلومات حول ما إذا كان المكرر قد انتهى من إنشاء القيم.
دعونا نختبر هذا عن طريق استدعاء عملية
next
على مكرر المصفوفة لدينا:
const result = iterator.next();
console.log(result);
سنرى الكائن في وحدة التحكم
{ value: 1, done: false }
. العنصر الأول من المصفوفة الذي أنشأناه هو 1 ، وهنا ظهر كقيمة. لقد تلقينا أيضًا معلومات تفيد بأن المكرر لم ينته بعد ، أي أنه لا يزال بإمكاننا استدعاء الوظيفة
next
والحصول على بعض القيم. لنجرب! دعنا نسميها
next
مرتين أخريين:
console.log(iterator.next());
console.log(iterator.next());
تلقى واحدا تلو الآخر
{ value: 2, done: false }
و
{ value: 3, done: false }
.
لا يوجد سوى ثلاثة عناصر في مجموعتنا. ماذا يحدث إذا اتصلت به مرة أخرى
next
؟
console.log(iterator.next());
هذه المرة سنرى
{ value: undefined, done: true }
. يشير هذا إلى أن المكرر قد اكتمل. لا جدوى من الاتصال مرة أخرى
next
. إذا فعلنا ذلك ، فسنحصل مرارًا وتكرارًا على كائن
{ value: undefined, done: true }
.
done: true
يعني وقف التكرار.
الآن يمكنك فهم ما يفعله
for ... of
تحت الغطاء:
[Symbol.iterator]()
يتم استدعاء الطريقة الأولى للحصول على المكرر ؛next
يتم استدعاء الطريقة دوريًا على المكرر حتى نحصل عليهاdone: true
؛- بعد كل مكالمة
next
في جسم الحلقة ، يتم استخدام الخاصيةvalue
.
لنكتب كل هذا في الكود:
const iterator = ourArray[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
const element = result.value;
// do some something with element
result = iterator.next();
}
هذا الرمز يعادل هذا:
for (let element of ourArray) {
// do something with element
}
يمكنك التحقق من ذلك ، على سبيل المثال ، عن طريق الإدراج
console.log(element)
بدلاً من التعليق
// do something with element
.
قم بإنشاء مكرر خاص بك
نحن نعرف الآن ما هي العناصر التكرارية والمكررات. يطرح السؤال: "هل يمكنني كتابة مثيلاتي الخاصة؟"
من المؤكد!
لا يوجد شيء غامض حول التكرارات. هذه مجرد كائنات ذات طريقة
next
تتصرف بطريقة خاصة. لقد اكتشفنا بالفعل القيم الأصلية القابلة للتكرار في JS. لم تذكر أشياء بينهم. في الواقع ، لا يتم تكرارها بشكل أصلي. ضع في اعتبارك كائنًا مثل هذا:
const ourObject = {
1: 'a',
2: 'b',
3: 'c'
};
إذا كررنا ذلك
for (let element of ourObject)
، حصلنا على خطأ
object is not iterable
.
دعونا نكتب التكرارات الخاصة بنا بجعل مثل هذا الكائن قابلاً للتكرار!
للقيام بذلك ، يجب عليك تصحيح النموذج الأولي بطريقتك
Object
الخاصة
[Symbol.iterator]()
. نظرًا لأن تصحيح النموذج الأولي يعد ممارسة سيئة ، فلنقم بإنشاء فصل دراسي خاص بنا عن طريق توسيع
Object
:
class IterableObject extends Object {
constructor(object) {
super();
Object.assign(this, object);
}
}
يأخذ مُنشئ الفصل الخاص بنا كائنًا عاديًا وينسخ خصائصه إلى كائن قابل للتكرار (على الرغم من أنه غير قابل للتكرار بالفعل بعد!).
لنقم بإنشاء كائن قابل للتكرار:
const iterableObject = new IterableObject({
1: 'a',
2: 'b',
3: 'c'
})
لجعل الفصل الدراسي قابلاً للتكرار
IterableObject
حقًا ، نحتاج إلى طريقة
[Symbol.iterator]()
. دعونا نضيفها.
class IterableObject extends Object {
constructor(object) {
super();
Object.assign(this, object);
}
[Symbol.iterator]() {
}
}
الآن يمكنك كتابة مكرر حقيقي!
نحن نعلم بالفعل أنه يجب أن يكون كائنًا مع طريقة
next
. لنبدأ بهذا.
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
return {
next() {}
}
}
}
بعد كل مكالمة ،
next
تحتاج إلى إعادة كائن العرض
{ value, done }
. دعونا نجعلها بقيم خيالية.
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
return {
next() {
return {
value: undefined,
done: false
}
}
}
}
}
إعطاء كائن قابل للتكرار مثل هذا:
const iterableObject = new IterableObject({
1: 'a',
2: 'b',
3: 'c'
})
سنخرج أزواجًا ذات قيمة مفتاح ، على غرار طريقة تكرار ES6
Map
:
['1', 'a']
['2', 'b']
['3', 'c']
في مكررنا ،
property
سنخزن مصفوفة في القيمة
[key, valueForThatKey]
. يرجى ملاحظة أن هذا هو الحل الخاص بنا مقارنة بالخطوات السابقة. إذا أردنا كتابة مكرر يقوم بإرجاع مفاتيح فقط أو قيم خصائص فقط ، فيمكننا القيام بذلك دون أي مشاكل. لقد قررنا الآن إرجاع أزواج القيمة الرئيسية الآن.
نحن بحاجة إلى مصفوفة من النوع
[key, valueForThatKey]
. أسهل طريقة للحصول عليه هي الطريقة
Object.entries
. يمكننا استخدامه مباشرة قبل إنشاء كائن المكرر في الطريقة
[Symbol.iterator]()
:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
// we made an addition here
const entries = Object.entries(this);
return {
next() {
return {
value: undefined,
done: false
}
}
}
}
}
سيصل المكرر الذي تم إرجاعه في الطريقة إلى المتغير بفضل إغلاق JavaScript
entries
.
نحتاج أيضًا إلى متغير حالة. سيخبرنا أي زوج من قيم المفاتيح يجب إرجاعه في المكالمة التالية
next
. دعونا نضيفه:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
const entries = Object.entries(this);
// we made an addition here
let index = 0;
return {
next() {
return {
value: undefined,
done: false
}
}
}
}
}
لاحظ أننا أعلنا عن المتغير
index
c
let
لأننا نعلم أننا نخطط لتحديث قيمته بعد كل استدعاء
next
.
نحن الآن جاهزون لإرجاع القيمة الفعلية بالطريقة
next
:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = 0;
return {
next() {
return {
// we made a change here
value: entries[index],
done: false
}
}
}
}
}
لقد كان سهلا. نحن فقط استخدام المتغيرات
entries
و
index
الوصول إليها الزوج مفتاح القيمة الصحيح من مجموعة
entries
.
الآن نحن بحاجة إلى التعامل مع الممتلكات
done
، لأنها ستظل كذلك دائمًا
false
. يمكنك عمل متغير آخر بجانب
entries
و
index
، وتحديثه بعد كل مكالمة
next
. ولكن هناك طريقة أسهل. دعنا نتحقق مما إذا
index
كانت المصفوفة خارج الحدود
entries
:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = 0;
return {
next() {
return {
value: entries[index],
// we made a change here
done: index >= entries.length
}
}
}
}
}
ينتهي مكررنا عندما يكون المتغير
index
مساويًا أو أكبر من طوله
entries
. على سبيل المثال ، إذا كان y له
entries
طول 3 ، فإنه يحتوي على قيم في المؤشرات 0 و 1 و 2. وعندما يكون المتغير
index
مساويًا لـ 3 أو أكبر منه ، فهذا يعني أنه لم يعد هناك قيم متبقية. لقد انتهينا.
هذا الرمز يعمل تقريبًا . لم يتبق سوى شيء واحد لإضافته. يبدأ
المتغير من
index
0 ، لكن ... لا نقوم بتحديثه! انها ليست بهذه البساطة. نحتاج إلى تحديث المتغير بعد عودتنا
{ value, done }
. لكن عندما أعدناها ، الطريقة
next
يتوقف على الفور حتى إذا كان هناك بعض التعليمات البرمجية بعد التعبير
return
. لكن يمكننا إنشاء كائن
{ value, done }
، وتخزينه في متغير ، وتحديثه ،
index
وبعد ذلك فقط نعيد الكائن:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = 0;
return {
next() {
const result = {
value: entries[index],
done: index >= entries.length
};
index++;
return result;
}
}
}
}
بعد التغييرات ،
IterableObject
يبدو الفصل كما يلي:
class IterableObject extends Object {
constructor(object) {
super();
Object.assign(this, object);
}
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = 0;
return {
next() {
const result = {
value: entries[index],
done: index >= entries.length
};
index++;
return result;
}
}
}
}
تعمل الشفرة بشكل رائع ، لكنها أصبحت مربكة للغاية. هذا لأنه يظهر طريقة أكثر ذكاءً ولكن أقل وضوحًا للتحديث
index
بعد إنشاء الكائن
result
. يمكننا فقط التهيئة
index
إلى -1! وعلى الرغم من أنه يتم تحديثه قبل إرجاع الكائن من
next
، إلا أن كل شيء سيعمل بشكل جيد ، لأن التحديث الأول سيحل محل -1 بـ 0.
لذلك دعونا نفعل ذلك:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = -1;
return {
next() {
index++;
return {
value: entries[index],
done: index >= entries.length
}
}
}
}
}
كما ترى ، لا نحتاج الآن إلى التوفيق بين ترتيب إنشاء الكائن
result
وتحديثه
index
. أثناء الاستدعاء الثاني ،
index
سيتم تحديثه إلى 1 ، وسنعيد نتيجة مختلفة ، وهكذا. كل شيء يعمل كما أردنا ، وتبدو الشفرة أبسط بكثير.
لكن كيف نتحقق من صحة العمل؟ يمكنك تشغيل طريقة يدويًا لإنشاء
[Symbol.iterator]()
مثيل مكرر ثم التحقق مباشرة من نتائج المكالمات
next
. لكن يمكنك القيام به بشكل أسهل! قيل أعلاه أنه يمكن إدراج أي كائن قابل للتكرار في حلقة
for ... of
. لنفعل ذلك تمامًا ، بتسجيل القيم التي يعيدها الكائن القابل للتكرار على طول الطريق:
const iterableObject = new IterableObject({
1: 'a',
2: 'b',
3: 'c'
});
for (let element of iterableObject) {
console.log(element);
}
يعمل! هذا ما يتم عرضه في وحدة التحكم:
[ '1', 'a' ]
[ '2', 'b' ]
[ '3', 'c' ]
رائع! لقد بدأنا بكائن لا يمكن استخدامه في الحلقات
for ... of
، لأنها لا تحتوي أصلاً على مكررات مضمنة. لكننا أنشأنا منطقتنا
IterableObject
، والتي لها مكرر مكتوب ذاتيًا.
آمل أن تتمكن الآن من رؤية إمكانات العناصر التكرارية والمكررات. إنها آلية تسمح لك بإنشاء هياكل البيانات الخاصة بك للعمل مع وظائف JS مثل الحلقات
for ... of
، وهي تعمل تمامًا مثل الهياكل الأصلية! هذه ميزة مفيدة للغاية يمكنها تبسيط التعليمات البرمجية بشكل كبير في مواقف معينة ، خاصة إذا كنت تخطط لتكرار هياكل البيانات الخاصة بك بشكل متكرر.
بالإضافة إلى ذلك ، يمكننا تخصيص ما يجب أن تعيده هذه التكرارات بالضبط. يقوم المكرر الآن بإرجاع أزواج المفتاح والقيمة. ماذا لو كنا نريد القيم فقط؟ سهل ، فقط أعد كتابة المكرر:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
// changed `entries` to `values`
const values = Object.values(this);
let index = -1;
return {
next() {
index++;
return {
// changed `entries` to `values`
value: values[index],
// changed `entries` to `values`
done: index >= values.length
}
}
}
}
}
وهذا كل شيء! إذا بدأنا الآن الحلقة
for ... of
، فسنرى في وحدة التحكم:
a b c
قمنا بإرجاع قيم الكائنات فقط. كل هذا يثبت مرونة المكرر المكتوب ذاتيًا. يمكنك جعلهم يعيدون ما تريد.
التكرارات ككائنات متكررة
من الشائع جدًا أن يخلط الناس بين التكرارات والمتكررة. هذا خطأ وقد حاولت أن أفصل بين الاثنين بدقة. أظن أنني أعرف سبب إرباك الناس لهم كثيرًا.
اتضح أن التكرارات ... تكون أحيانًا كائنات قابلة للتكرار!
ماذا يعني هذا؟ تذكر أن العنصر القابل للتكرار هو الكائن الذي يرتبط به المكرر. كل مكرر JavaScript أصلي لديه طريقة
[Symbol.iterator]()
تقوم بإرجاع مكرر آخر! هذا يجعل المكرر الأول كائنًا متكررًا.
يمكنك التحقق من ذلك إذا أخذت مكررًا عاد من مصفوفة واستدعته
[Symbol.iterator]()
:
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
const secondIterator = iterator[Symbol.iterator]();
console.log(secondIterator);
بعد تشغيل هذا الرمز ، سترى
Object [Array Iterator] {}
. أي أن المكرر لا يحتوي فقط على مكرر آخر مرتبط به ، بل هو أيضًا مصفوفة.
إذا قارنت كلا التكرارات مع ،
===,
فقد تبين أنهما متماثلان تمامًا:
const iterator = ourArray[Symbol.iterator]();
const secondIterator = iterator[Symbol.iterator]();
// logs `true`
console.log(iterator === secondIterator);
في البداية ، قد تجد أنه من الغريب سلوك مكرر يكون مكررًا خاصًا به. لكن هذه ميزة مفيدة للغاية. لا يمكنك لصق مكرر عاري في حلقة
for ... of
، فهو يقبل فقط كائنًا متكررًا - كائنًا به طريقة
[Symbol.iterator]()
.
ومع ذلك ، فإن الموقف الذي يكون فيه المكرر هو مكرره الخاص (وبالتالي كائنًا متكررًا) يخفي المشكلة. نظرًا لأن مكررات JS الأصلية تحتوي على طرق
[Symbol.iterator]()
، يمكنك تمريرها مباشرة إلى حلقات بدون تفكير ثانٍ
for ... of
.
ونتيجة لذلك ، فإن هذا المقتطف:
const ourArray = [1, 2, 3];
for (let element of ourArray) {
console.log(element);
}
و هذه:
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
for (let element of iterator) {
console.log(element);
}
تعمل بسلاسة وتفعل الشيء نفسه. ولكن لماذا قد يستخدم أي شخص متكررات مثل هذه في حلقات مباشرة
for ... of
؟ في بعض الأحيان لا مفر منه.
أولاً ، قد تحتاج إلى إنشاء مكرر دون الانتماء إلى أي تكرار. سنلقي نظرة على هذا المثال أدناه ، وهو أمر شائع. في بعض الأحيان لا نحتاج إلى العنصر التكراري نفسه.
وسيكون أمرًا محرجًا جدًا إذا كان وجود مكرر خالي يعني أنه لا يمكنك استخدامه
for ... of
. بالطبع ، يمكنك القيام بذلك يدويًا باستخدام طريقة
next
، على سبيل المثال ، حلقة
while
، لكننا رأينا أنه يجب عليك كتابة الكثير من التعليمات البرمجية ، علاوة على ذلك ، بشكل متكرر.
الحل بسيط: إذا كنت تريد تجنب الشفرة المعيارية واستخدام مكرر في حلقة
for ... of
، عليك أن تجعل المكرر كائنًا متكررًا.
من ناحية أخرى ، نحصل أيضًا على مكررات في كثير من الأحيان من طرق أخرى غير
[Symbol.iterator]()
. على سبيل المثال، ES6
Map
يحتوي على الأساليب
entries
،
values
و
keys
. كلهم يعودون مكررات.
إذا لم تكن مكررات JS الأصلية كائنات قابلة للتكرار أيضًا ، فلا يمكنك استخدام هذه الطرق مباشرة في الحلقات
for ... of
، مثل هذا:
for (let element of map.entries()) {
console.log(element);
}
for (let element of map.values()) {
console.log(element);
}
for (let element of map.keys()) {
console.log(element);
}
يعمل هذا الرمز لأن التكرارات التي تُرجعها الطرق هي أيضًا كائنات قابلة للتكرار. وإلا ، فسيتعين عليك ، على سبيل المثال ، التفاف النتيجة
map.entries()
في كائن غبي قابل للتكرار. لحسن الحظ ، لسنا بحاجة إلى القيام بذلك.
يعتبر إنشاء كائناتك الخاصة القابلة للتكرار ممارسة جيدة. خاصة إذا تم إرجاعها من طرق أخرى غير
[Symbol.iterator]()
. من السهل جدًا جعل المكرر كائنًا متكررًا. دعني أعرض مع مثال على مكرر
IterableObject
:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
// same as before
return {
next() {
// same as before
},
[Symbol.iterator]() {
return this;
}
}
}
}
لقد أنشأنا طريقة
[Symbol.iterator]()
أسفل الطريقة
next
. جعل هذا المكرر مكررًا خاصًا به بمجرد العودة
this
، مما يعني أنه يعيد نفسه. أعلاه ، لقد رأينا بالفعل كيف يتصرف مكرر الصفيف. هذا يكفي لكي يعمل مكررنا في حلقات
for ... of
حتى بشكل مباشر.
دولة التكرار
يجب أن يكون من الواضح الآن أن كل مكرر له حالة مرتبطة به. على سبيل المثال ، في مكرر ،
IterableObject
قمنا بتخزين حالة - متغير
index
- كإغلاق. وقمنا بتحديثه بعد كل خطوة من خطوات التكرار.
ماذا يحدث بعد اكتمال عملية التكرار؟ يصبح المكرر عديم الفائدة ويمكنك (يجب) حذفه. يمكنك أن ترى أن هذا يحدث حتى من خلال مثال كائنات JS الأصلية. لنأخذ مكرر مصفوفة ونحاول تشغيله مرتين في حلقة
for ... of
.
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
for (let element of iterator) {
console.log(element);
}
for (let element of iterator) {
console.log(element);
}
قد تتوقع أن تعرض وحدة التحكم الأرقام مرتين
1
،
2
و
3
. لكن النتيجة ستكون هكذا:
1
2
3
لماذا ا؟
دعنا نتصل يدويًا
next
بعد انتهاء الحلقة:
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
for (let element of iterator) {
console.log(element);
}
console.log(iterator.next());
يتم إخراج السجل الأخير إلى وحدة التحكم
{ value: undefined, done: true }
.
هذا هو. بعد انتهاء الحلقة ، ينتقل المكرر إلى حالة "تم". الآن سيعود دائمًا كائنًا
{ value: undefined, done: true }
.
هل هناك طريقة "لإعادة ضبط" حالة المكرر بحيث يمكن استخدامه مرة ثانية
for ... of
؟ في بعض الحالات ، يكون ذلك ممكنًا ، لكنه لا معنى له. لذلك ، فهي
[Symbol.iterator]
طريقة وليست مجرد خاصية. يمكنك استدعاء الطريقة مرة أخرى والحصول على مكرر آخر :
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
for (let element of iterator) {
console.log(element);
}
const secondIterator = ourArray[Symbol.iterator]();
for (let element of secondIterator) {
console.log(element);
}
كل شيء يعمل الآن كما هو متوقع. دعونا نرى لماذا تعمل الحلقات المتعددة للأمام عبر المصفوفة:
const ourArray = [1, 2, 3];
for (let element of ourArray) {
console.log(element);
}
for (let element of ourArray) {
console.log(element);
}
كل الحلقات
for ... of
تستخدم مكررات مختلفة ! بمجرد انتهاء المكرر والحلقة ، لن يتم استخدام هذا المكرر.
التكرارات والمصفوفات
نظرًا لأننا نستخدم التكرارات (وإن كان ذلك بشكل غير مباشر) في الحلقات
for ... of
، فقد تبدو مخادعة مثل المصفوفات. لكن هناك اختلافان مهمان. يستخدم التكرار والمصفوفة مفاهيم الجشع والقيم الكسولة. عندما تقوم بإنشاء مصفوفة ، في أي لحظة زمنية لها طول معين ، وقيمها مهيأة بالفعل. بالطبع ، يمكنك إنشاء مصفوفة بدون قيم على الإطلاق ، لكن هذا ليس هو الحال. وجهة نظري هي أنه لا يمكن إنشاء مصفوفة تقوم بتهيئة قيمها فقط بعد الوصول إليها عن طريق الكتابة
array[someIndex]
. قد يكون من الممكن الالتفاف على هذا باستخدام وكيل أو خدعة أخرى ، ولكن بشكل افتراضي لا تتصرف مصفوفات JavaScript بهذه الطريقة.
وعندما يقولون أن المصفوفة لها طول ، فإنهم يعنون أن هذا الطول محدود. لا توجد مصفوفات لانهائية في JavaScript.
هاتان الصفتان تدلان على جشع المصفوفات.
والمكررات كسالى .
لإظهار ذلك ، سننشئ اثنين من مكرراتنا: الأول سيكون لانهائي ، على عكس المصفوفات المحدودة ، والثاني سيهيئ قيمه فقط عندما يطلبها مستخدم التكرار.
لنبدأ مع مكرر لانهائي. يبدو الأمر مخيفًا ، لكن من السهل جدًا إنشاؤه: يبدأ المكرر من الصفر ويعيد الرقم التالي في التسلسل في كل خطوة. إلى الأبد.
const counterIterator = {
integer: -1,
next() {
this.integer++;
return { value: this.integer, done: false };
},
[Symbol.iterator]() {
return this;
}
}
وهذا كل شيء! بدأنا بخاصية
integer
-1. في كل مرة نسميها ،
next
نزيدها بمقدار 1 ونعيدها ككائن
value
. لاحظ أننا استخدمنا الحيلة المذكورة أعلاه مرة أخرى: بدأنا بـ -1 لإرجاع 0 في المرة الأولى.
ألق نظرة أيضًا على الخاصية
done
. أنه سوف دائما تكون كاذبة. هذا المكرر لا ينتهي!
بالإضافة إلى ذلك ، جعلنا المكرر قابلاً للتكرار من خلال إعطائه تنفيذًا بسيطًا
[Symbol.iterator]()
.
شيء أخير: هذه هي الحالة التي ذكرتها أعلاه - أنشأنا مكررًا ، لكنها لا تحتاج إلى والد متكرر للعمل.
الآن لنجرب هذا المكرر في حلقة
for ... of
. ما عليك سوى أن تتذكر إيقاف الحلقة في وقت ما ، وإلا فسيتم تنفيذ الكود إلى الأبد.
for (let element of counterIterator) {
if (element > 5) {
break;
}
console.log(element);
}
بعد الإطلاق ، سنرى في وحدة التحكم:
0
1
2
3
4
5
لقد أنشأنا في الواقع مكررًا لا نهائيًا يعرض أكبر عدد تريده من الأرقام. وكان من السهل جدًا القيام بذلك!
لنكتب الآن مكررًا لا يُنشئ قيمًا حتى يتم طلبها.
حسنًا ... لقد فعلنا ذلك بالفعل!
هل لاحظت أنه
counterIterator
يتم تخزين رقم خاصية واحد فقط في أي وقت
integer
؟ هذا هو آخر رقم تم إرجاعه في المكالمة
next
. وهذا هو نفس الكسل. مكرر يمكن أن يحتمل عودة أي عدد (على نحو أدق، عدد صحيح موجب). لكنها تخلقها فقط عند الحاجة إليها: عندما يتم استدعاء الطريقة
next
.
قد تبدو لافتة للانتباه. بعد كل شيء ، يتم إنشاء الأرقام بسرعة ولا تشغل مساحة كبيرة من الذاكرة. ولكن إذا كنت تعمل مع كائنات كبيرة جدًا تستهلك قدرًا كبيرًا من الذاكرة ، فقد يكون استبدال المصفوفات بمكررات أحيانًا مفيدًا للغاية ، مما يؤدي إلى تسريع البرنامج وتوفير الذاكرة.
كلما زاد حجم الكائن (أو كلما استغرق إنشاؤه وقتًا أطول) ، زادت الفائدة.
طرق أخرى لاستخدام التكرارات
حتى الآن ، استهلكنا فقط التكرارات في حلقة
for ... of
أو يدويًا باستخدام
next
. لكن هذه ليست الطرق الوحيدة.
لقد رأينا بالفعل أن المنشئ
Map
يأخذ العناصر التكرارية كوسيطة. يمكنك أيضًا
Array.from
بسهولة تحويل المصفوفة القابلة للتكرار إلى مصفوفة باستخدام الطريقة . لكن كن حريص! كما قلت ، قد يكون كسل المكرر في بعض الأحيان ميزة كبيرة. التحويل إلى مصفوفة يزيل الكسل. تتم تهيئة جميع القيم التي يتم إرجاعها بواسطة المكرر على الفور ثم وضعها في مصفوفة. هذا يعني أنه إذا حاولنا تحويل اللانهائي
counterIterator
إلى مصفوفة ، فسيؤدي ذلك إلى كارثة.
Array.from
ستنفذ إلى الأبد دون إرجاع نتيجة. لذلك قبل تحويل المكرر / المكرر إلى مصفوفة ، عليك التأكد من أن العملية آمنة.
ومن المثير للاهتمام أن العناصر المتكررة تعمل أيضًا بشكل جيد مع عامل الانتشار
(...
.) تذكر أن هذا يعمل بنفس
Array.from
الطريقة عندما يتم إنشاء جميع قيم المكرر مرة واحدة. على سبيل المثال ، يمكنك إنشاء نسختك الخاصة باستخدام عامل تشغيل السبريد
Array.from
. ما عليك سوى تطبيق عامل التشغيل على المتكرر ، ثم وضع القيم في مصفوفة:
const arrayFromIterator = [...iterable];
يمكنك أيضًا الحصول على جميع القيم من الكائن القابل للتكرار وتطبيقها على الوظيفة:
someFunction(...iterable);
استنتاج
أرجو أن تفهم الآن عنوان المقالة كائنات Iterable و المكررات. لقد تعلمنا ماهيتها ، وكيف تختلف ، وكيفية استخدامها ، وكيفية إنشاء مثيلاتنا الخاصة. نحن الآن جاهزون تمامًا للعمل مع المولدات. إذا كنت معتادًا على التكرارات ، فلن يكون الانتقال إلى الموضوع التالي صعبًا للغاية.