علاوة على ذلك ، يتم تقديم مقال للقارئ ، والذي ، في حالة الاستجابة الإيجابية ، يمكن أن يتطور إلى حلقة. إذا قمت بكتابة هذه الدورة بنجاح ، وتمكن القارئ من إتقانها بنجاح ، فسيكون واضحًا بشأن الكود التالي ليس فقط ما يفعله ، ولكن أيضًا كيف يعمل تحت الغطاء:
while (true) {
const data = yield getNextChunk(); //
const processed = processData(data);
try {
yield sendProcessedData(processed);
showOkResult();
} catch (err) {
showError();
}
}
هذا هو الجزء الأول التجريبي: التكرارات والمولدات.
التكرارات
لذلك ، المكرر هو واجهة توفر وصولاً متسلسلاً إلى البيانات.
كما ترى ، لا يقول التعريف أي شيء عن البيانات أو هياكل الذاكرة. في الواقع ، يمكن تمثيل سلسلة من s غير المعرفة كمكرر دون شغل أي مساحة في الذاكرة.
أقترح على القارئ أن يجيب على السؤال: هل المصفوفة مكرر؟
إجابة
. shift pop .
لماذا ، إذن ، هناك حاجة إلى التكرارات إذا كانت المصفوفة ، أحد الهياكل الأساسية للغة ، تسمح لك بالعمل مع البيانات بشكل تسلسلي وتعسفي؟
لنتخيل أننا بحاجة إلى مكرر ينفذ سلسلة من الأعداد الطبيعية. أو أرقام فيبوناتشي. أو أي تسلسل لا نهاية له . من الصعب وضع تسلسل لا نهاية له في مصفوفة ؛ فأنت بحاجة إلى آلية لملء المصفوفة تدريجيًا بالبيانات ، وكذلك إزالة البيانات القديمة حتى لا تملأ ذاكرة العملية بأكملها. هذا تعقيد غير ضروري ، يحمل معه تعقيدًا إضافيًا في التنفيذ والدعم ، على الرغم من حقيقة أن الحل بدون مصفوفة يمكن أن يتناسب مع عدة أسطر:
const getNaturalRow = () => {
let current = 0;
return () => ++current;
};
أيضًا ، يمكن أن يمثل المكرر تلقي البيانات من قناة خارجية ، مثل مقبس الويب.
في جافا سكريبت ، المكرر هو أي كائن له طريقة () تالية تُرجع بنية ذات قيمة حقول - القيمة الحالية للمكرر وتم تنفيذه - علامة تشير إلى نهاية التسلسل (هذه الاتفاقية موصوفة في معيار لغة ECMAScript ). مثل هذا الكائن يطبق واجهة التكرار. دعنا نعيد كتابة المثال السابق بهذا التنسيق:
const getNaturalRow = () => ({
_current: 0,
next() { return {
value: ++this._current,
done: false,
}},
});
يوجد جافا سكريبت أيضا واجهة Iterable، وهو الكائن الذي لديهمكرر (هذا الثابت هو متاح في Symbol.iterator) أن عوائد مكرر. بالنسبة للكائنات التي تنفذ مثل هذه الواجهة ، يكون اجتياز المشغل متاحًا
for..of. دعنا نعيد كتابة مثالنا مرة أخرى ، هذه المرة فقط كتطبيق قابل للتكرار:
const naturalRowIterator = {
[Symbol.iterator]: () => ({
_current: 0,
next() { return {
value: ++this._current,
done: this._current > 3,
}},
}),
}
for (num of naturalRowIterator) {
console.log(num);
}
// : 1, 2, 3
كما ترى ، كان علينا أن نجعل علم المنجز في مرحلة ما يصبح موجبًا ، وإلا ستكون الحلقة غير محدودة.
مولدات كهرباء
أصبحت المولدات هي المرحلة التالية في تطور التكرارات. أنها توفر السكر النحوي لإرجاع قيم التكرار مثل قيمة الوظيفة. المولد هو وظيفة (معلنة بعلامة النجمة: function * ) تقوم بإرجاع مكرر. في هذه الحالة ، لا يتم إرجاع المكرر بشكل صريح ، حيث تقوم الوظائف بإرجاع قيم المكرر فقط باستخدام بيان العائد . عندما تنتهي الوظيفة من تنفيذها ، يُعتبر المكرر مكتملًا (نتائج الاستدعاءات اللاحقة للطريقة التالية سيكون لها علامة المنفذ تساوي true)
function* naturalRowGenerator() {
let current = 1;
while (current <= 3) {
yield current;
current++;
}
}
for (num of naturalRowGenerator()) {
console.log(num);
}
// : 1, 2, 3
بالفعل في هذا المثال البسيط ، يكون الفارق الدقيق الرئيسي للمولدات مرئيًا للعين المجردة: لا يتم تنفيذ الكود الموجود داخل وظيفة المولد بشكل متزامن . يتم تنفيذ كود المولد على مراحل ، كنتيجة لاستدعاءات التالي () على المكرر المقابل. دعونا نرى كيف يتم تنفيذ كود المولد في المثال السابق. سنستخدم مؤشرًا خاصًا لتحديد مكان توقف المولد.
عندما يتم استدعاء naturalRowGenerator ، يتم إنشاء مكرر.
function* naturalRowGenerator() {
▷let current = 1;
while (current <= 3) {
yield current;
current++;
}
}
علاوة على ذلك ، عندما نطلق على الطريقة التالية لأول ثلاث مرات ، أو في حالتنا ، نكرر الحلقة ، يتم وضع المؤشر بعد بيان العائد.
function* naturalRowGenerator() {
let current = 1;
while (current <= 3) {
yield current; ▷
current++;
}
}
وبالنسبة لجميع المكالمات اللاحقة إلى التالي وبعد الخروج من الحلقة ، ينهي المولد تنفيذه وستكون نتائج الاستدعاء التالي
{ value: undefined, done: true }
تمرير المعلمات إلى مكرر
لنتخيل أننا بحاجة إلى إضافة القدرة على إعادة ضبط العداد الحالي والبدء في العد من البداية إلى مكرر الأعداد الطبيعية.
naturalRowIterator.next() // 1
naturalRowIterator.next() // 2
naturalRowIterator.next(true) // 1
naturalRowIterator.next() // 2
من الواضح كيفية التعامل مع مثل هذه المعلمة في مكرر مكتوب ذاتيًا ، ولكن ماذا عن المولدات؟
اتضح أن المولدات تدعم تمرير المعلمة!
function* naturalRowGenerator() {
let current = 1;
while (true) {
const reset = yield current;
if (reset) {
current = 1;
} else {
current++;
}
}
}
يتم توفير المعلمة التي تم تمريرها كنتيجة لبيان العائد. دعنا نحاول إضافة الوضوح باستخدام نهج المؤشر. عندما تم إنشاء المكرر ، لم يتغير شيء. يتبع ذلك الاستدعاء الأول للطريقة () التالية:
function* naturalRowGenerator() {
let current = 1;
while (true) {
const reset = ▷yield current;
if (reset) {
current = 1;
} else {
current++;
}
}
}
تجمد المؤشر في اللحظة التي عاد فيها من بيان العائد. في المكالمة التالية إلى التالي ، ستحدد القيمة التي تم تمريرها إلى الوظيفة قيمة متغير إعادة التعيين. أين تنتهي القيمة التي تم تمريرها إلى النداء الأول إلى التالي ، حيث لم يكن هناك دعوة للخضوع بعد؟ لا مكان! سوف يذوب في اتساع جامع القمامة. إذا كنت بحاجة إلى تمرير بعض القيمة الأولية إلى المولد ، فيمكن القيام بذلك باستخدام وسيطات المولد نفسه. مثال:
function* naturalRowGenerator(start = 1) {
let current = start;
while (true) {
const reset = yield current;
if (reset) {
current = start;
} else {
current++;
}
}
}
const iterator = naturalRowGenerator(10);
iterator.next() // 10
iterator.next() // 11
iterator.next(true) // 10
خاتمة
لقد ناقشنا مفهوم التكرارات وتنفيذه بلغة جافا سكريبت. درسنا أيضًا المولدات - بناء نحوي لتنفيذ التكرارات بسهولة.
على الرغم من أنني قدمت أمثلة مع التسلسلات الرقمية في هذه المقالة ، إلا أن مكررات جافا سكريبت يمكنها فعل الكثير. يمكنهم تمثيل أي تسلسل للبيانات وحتى العديد من آلات الحالة المحدودة. في المقالة التالية ، أود أن أتحدث عن كيفية استخدام المولدات لبناء عمليات غير متزامنة (coroutines ، goroutines ، csp ، إلخ).