يعد التعامل مع كل عنصر في المجموعة عملية شائعة جدًا. يوفر JavaScript العديد من الطرق للتكرار على مجموعة ، من البساطة إلى وكل حلقة إلى MAP () و FILTER () وشاملات الصفيف (اشتقاق الصفيف). في JavaScript 1.7 ، يجلب المتكررون والمولدات آليات تكرارية جديدة في بناء جملة JavaScript الأساسي ، وكذلك توفير آليات لتخصيص سلوك ... في كل حلقات.
المتكرر
التكرار هو كائن يصل إلى عنصر في تسلسل المجموعة في كل مرة ويتتبع الموضع الحالي للتكرار في هذا التسلسل. في JavaScript Iterator هو كائن يوفر طريقة التالية () ، والتي تُرجع العنصر التالي في التسلسل. هذه الطريقة ترمي استثناء التوقف عندما يتم اجتياز جميع العناصر في التسلسل.
بمجرد إنشاء كائن Iterator ، يمكن تسميته ضمنيًا من خلال التكرار الصريح التالي () ، أو باستخدام JavaScript لـ ... في كل حلقة.
يمكن إنشاء تكرار بسيط يتكرر على الكائنات والمصفوفات باستخدام ITerator ():
نسخة الكود كما يلي:
var lang = {name: 'JavaScript' ، عيد ميلاد: 1995} ؛
var it = iterator (lang) ؛
بمجرد اكتمال التهيئة ، يمكن استدعاء الطريقة التالية () للوصول إلى أزواج القيمة الرئيسية للكائن بدورها:
نسخة الكود كما يلي:
var pair = it.next () ؛ // أزواج القيمة الرئيسية هي ["الاسم" ، "JavaScript"]
زوج = it.next () ؛ // زوج القيمة الرئيسية هو ["عيد ميلاد" ، 1995]
زوج = it.next () ؛ // تم إلقاء استثناء "توقف"
يمكن استخدام FOR ... في الحلقة لاستبدال المكالمة الصريحة إلى الطريقة التالية (). عندما يتم طرح استثناء التوقف ، ستنتهي الحلقة تلقائيًا.
نسخة الكود كما يلي:
var it = iterator (lang) ؛
لـ (var pair in)
طباعة (زوج) ؛ // واحد [مفتاح ، قيمة] زوج المفاتيح في الإخراج في كل مرة
إذا كنت تريد فقط التكرار على القيمة الرئيسية للكائن ، فيمكنك تمرير المعلمة الثانية إلى وظيفة ITerator () ، مع القيمة الصحيح:
نسخة الكود كما يلي:
var it = iterator (lang ، true) ؛
ل (مفتاح var فيه)
طباعة (مفتاح) ؛ // فقط قيمة مفتاح الإخراج
تتمثل إحدى ميزة استخدام ITerator () للوصول إلى الكائنات في أن الخصائص المخصصة التي تمت إضافتها إلى Object.prototype لم يتم تضمينها في كائن التسلسل.
يمكن أيضًا استخدام iterator () على صفيف:
نسخة الكود كما يلي:
var langs = ['JavaScript' ، 'Python' ، 'Haskell'] ؛
var it = iterator (langs) ؛
لـ (var pair in)
طباعة (زوج) ؛ // فقط إخراج التكرار [الفهرس ، اللغة] زوج قيمة المفاتيح
تمامًا مثل اجتياز كائن ما ، ستكون نتيجة تمرير True إلى truperal كمعلمة ثانية فهرس المصفوفة:
نسخة الكود كما يلي:
var langs = ['JavaScript' ، 'Python' ، 'Haskell'] ؛
var it = iterator (langs ، true) ؛
ل (var i في ذلك)
طباعة (ط) ؛ // الإخراج 0 ، ثم 1 ، ثم 2
استخدم الكلمة الرئيسية LET لتعيين الفهارس والقيم لمنع المتغيرات بشكل منفصل داخل الحلقة ، ويمكنك أيضًا تدمير المهمة (مهمة التدمير):
نسخة الكود كما يلي:
var langs = ['JavaScript' ، 'Python' ، 'Haskell'] ؛
var it = التكرار (langs) ؛
ل (دع [أنا ، لانغ] فيه)
print (i + ':' + lang) ؛ // الإخراج "0: JavaScript" إلخ.
أعلن عن موعد مخصص
يجب تكرار بعض الكائنات التي تمثل مجموعة من العناصر بطريقة محددة.
1. يجب أن تكرر على كائن يمثل نطاقًا (نطاق) الرقم الوارد في هذا النطاق واحد تلو الآخر.
2. يمكن الوصول إلى عقدة أوراق الشجرة باستخدام العمق الأول أو العرض الأول
3. يجب أن تُرجع التكرار على كائن يمثل نتائج استعلام قاعدة البيانات الصف حسب الصف ، حتى لو لم يتم تحميل مجموعة النتائج بأكملها في صفيف واحد.
4. يجب على التكرار الذين يعملون على تسلسل رياضي لا حصر له (مثل تسلسل فيبوناتشي) إرجاع النتائج واحدة تلو الأخرى دون إنشاء بنية بيانات الطول غير المحدود.
يتيح لك JavaScript كتابة التعليمات البرمجية التي تقوم بتخصيص المنطق التكراري وتطبيقه على كائن
نقوم بإنشاء كائن نطاق بسيط مع قيمتين:
نسخة الكود كما يلي:
نطاق الوظائف (منخفض ، مرتفع) {
this.low = low ؛
this.high = عالية ؛
}
الآن نقوم بإنشاء جهاز تكرار مخصص يعيد سلسلة من جميع الأعداد الصحيحة في النطاق. تتطلب واجهة التكرار منا أن نقدم طريقة التالية () لإرجاع العنصر التالي في التسلسل أو رمي استثناء التوقف.
نسخة الكود كما يلي:
وظيفة RangeIrator (المدى) {
this.range = المدى ؛
this.current = this.range.low ؛
}
RangeIterator.Prototype.next = function () {
إذا (this.current> this.range.high)
رمي التوقف.
آخر
إرجاع this.current ++ ؛
} ؛
يتم إنشاء مثيل له من خلال مثيل النطاق مع الحفاظ على خاصية حالية لتتبع موقع التسلسل الحالي.
أخيرًا ، لكي يتم دمج RangeIterator مع النطاق ، نحتاج إلى إضافة طريقة خاصة __iterator__ إلى النطاق. عندما نحاول التكرار على نطاق ما ، سيتم استدعاؤه ويجب أن يعيد مثيلًا لـ RangeIterator الذي ينفذ المنطق التكراري.
نسخة الكود كما يلي:
Range.Prototype .__ iterator__ = function () {
إرجاع New RangeIterator (هذا) ؛
} ؛
بعد الانتهاء من التكرار المخصص لدينا ، يمكننا التكرار على مثيل النطاق:
نسخة الكود كما يلي:
VAR Range = نطاق جديد (3 ، 5) ؛
ل (var i في المدى)
طباعة (ط) ؛ // الإخراج 3 ، ثم 4 ، ثم 5
المولد: طريقة أفضل لبناء التكرار
على الرغم من أن المتكررين المخصصين أداة مفيدة ، إلا أنك تحتاج إلى تخطيطها بعناية عند إنشائها لأنهم بحاجة إلى الحفاظ عليها بشكل صريح.
يوفر المولد وظائف قوية: فهو يتيح لك تحديد وظيفة تحتوي على خوارزمية التكرارية الخاصة بك ، ويمكنه الحفاظ تلقائيًا على حالتها.
المولد هو وظيفة خاصة يمكن استخدامها كمصنع للتكرار. إذا كانت الوظيفة تحتوي على تعبيرات أو أكثر من العائد ، فسيتم تسميتها للمولد (ملاحظة المترجم: يجب أن يتم تمثيل Node.js أيضًا بواسطة * قبل اسم الوظيفة).
ملاحظة: لا يمكن استخدام الكلمة الرئيسية إلا لكتل التعليمات البرمجية في HTML والتي يتم تضمينها في <script type = "application/javaScript ؛ version = 1.7"> (أو لاحقًا). لا تتطلب علامات البرنامج النصي XUL (لغة واجهة مستخدم XML) تحديد كتلة الرمز الخاصة هذه للوصول إلى هذه الميزات.
عندما يتم استدعاء وظيفة المولد ، لن يتم تنفيذ هيئة الوظيفة على الفور ، فسيقوم بإرجاع كائن المولد. في كل مرة يتم استدعاء طريقة () المولد المولد ، سيتم تنفيذ جسم الوظيفة إلى تعبير العائد التالي ثم إعادة النتيجة. عندما تنتهي الوظيفة أو تواجه عبارة إرجاع ، سيتم طرح استثناء توقف.
استخدم مثالًا لتوضيح:
نسخة الكود كما يلي:
وظيفة SimpleGenerator () {
العائد "الأول" ؛
العائد "الثاني" ؛
العائد "الثالث" ؛
لـ (var i = 0 ؛ i <3 ؛ i ++)
استسلم
}
var g = simplegenerator () ؛
print (g.next ()) ؛ // الإخراج "الأول"
print (g.next ()) ؛ // الإخراج "الثاني"
print (g.next ()) ؛ // الإخراج "الثالث"
print (g.next ()) ؛ // الإخراج 0
print (g.next ()) ؛ // الإخراج 1
print (g.next ()) ؛ // الإخراج 2
print (g.next ()) ؛ // رمي استثناء التوقف
يمكن استخدام وظائف المولد مباشرة بواسطة فئة كطريقة __iterator__ ، ويمكن أن تقلل بشكل فعال من كمية التعليمات البرمجية حيث تكون هناك حاجة إلى التكرار المخصص. دعنا نعيد كتابة النطاق باستخدام المولد:
نسخة الكود كما يلي:
نطاق الوظائف (منخفض ، مرتفع) {
this.low = low ؛
this.high = عالية ؛
}
Range.Prototype .__ iterator__ = function () {
لـ (var i = this.low ؛ i <= this.high ؛ i ++)
استسلم
} ؛
VAR Range = نطاق جديد (3 ، 5) ؛
ل (var i في المدى)
طباعة (ط) ؛ // الإخراج 3 ، ثم 4 ، ثم 5
لن تنتهي جميع المولدات ، يمكنك إنشاء مولد يمثل تسلسلًا لا حصر له. ينفذ المولد التالي تسلسل فيبوناتشي ، حيث يكون كل عنصر هو مجموع الاثنين الأول:
نسخة الكود كما يلي:
وظيفة Fibonacci () {
var fn1 = 1 ؛
var fn2 = 1 ؛
بينما (1) {
var current = fn2 ؛
fn2 = fn1 ؛
fn1 = fn1 + الحالي ؛
العائد الحالي ؛
}
}
تسلسل var = fibonacci () ؛
print (sequence.next ()) ؛ // 1
print (sequence.next ()) ؛ // 1
print (sequence.next ()) ؛ // 2
print (sequence.next ()) ؛ // 3
print (sequence.next ()) ؛ // 5
print (sequence.next ()) ؛ // 8
print (sequence.next ()) ؛ // 13
يمكن أن تأخذ وظائف المولد معلمات وستستخدم هذه المعلمات عندما يتم استدعاء الوظيفة لأول مرة. يمكن إنهاء المولد (مما تسبب في إلقاء استثناء توقف) باستخدام عبارة الإرجاع. يأخذ متغير Fibonacci () التالي معلمة حد اختيارية تنهي الوظيفة عند تشغيل الشرط.
نسخة الكود كما يلي:
وظيفة فيبوناتشي (الحد) {
var fn1 = 1 ؛
var fn2 = 1 ؛
بينما (1) {
var current = fn2 ؛
fn2 = fn1 ؛
fn1 = fn1 + الحالي ؛
if (limit && current> limit)
يعود؛
العائد الحالي ؛
}
}
مولد الميزات المتقدمة
يمكن للمولد حساب قيمة إرجاع العائد بناءً على المتطلبات ، مما يجعلها تمثل متطلبات حساب التسلسل باهظة الثمن سابقًا ، حتى التسلسل اللانهائي الموضح أعلاه.
بالإضافة إلى الطريقة التالية () ، يحتوي كائن المولد المتراكم أيضًا على طريقة SEND () ، والتي يمكنها تعديل الحالة الداخلية للمولد. سيتم التعامل مع القيمة التي تم تمريرها إلى SEND () كنتيجة لتعبير العائد الأخير وسيتم إيقاف المولد. قبل تمرير قيمة محددة باستخدام طريقة Send () ، يجب عليك الاتصال بـ Next () مرة واحدة على الأقل لبدء المولد.
يستخدم مولد Fibonacci التالي طريقة Send () لإعادة تشغيل التسلسل:
نسخة الكود كما يلي:
وظيفة Fibonacci () {
var fn1 = 1 ؛
var fn2 = 1 ؛
بينما (1) {
var current = fn2 ؛
fn2 = fn1 ؛
fn1 = fn1 + الحالي ؛
var reset = التيار العائد ؛
إذا (إعادة تعيين) {
fn1 = 1 ؛
fn2 = 1 ؛
}
}
}
تسلسل var = fibonacci () ؛
print (sequence.next ()) ؛ // 1
print (sequence.next ()) ؛ // 1
print (sequence.next ()) ؛ // 2
print (sequence.next ()) ؛ // 3
print (sequence.next ()) ؛ // 5
print (sequence.next ()) ؛ // 8
print (sequence.next ()) ؛ // 13
print (sequence.send (true)) ؛ // 1
print (sequence.next ()) ؛ // 1
print (sequence.next ()) ؛ // 2
print (sequence.next ()) ؛ // 3
ملاحظة: من المثير للاهتمام ، أن الاتصال (غير المحدد) هو بالضبط نفس استدعاء NEXT (). ومع ذلك ، عندما يتم استدعاء طريقة send () لبدء مولد جديد ، سيتم إلقاء استثناء من النوع باستثناء غير محدد.
يمكنك استدعاء طريقة الرمي وتمرير أكثر من غيرها يجب أن ترمي لإجبار المولد على رمي استثناء. سيتم طرح هذا الاستثناء من السياق الحالي وتوقف مؤقتًا عن المولد ، على غرار تنفيذ العائد الحالي ، ولكن تم استبداله ببيان قيمة الرمي.
إذا لم تتم مواجهة العائد أثناء عملية رمي الاستثناء ، فسيتم تمرير الاستثناء حتى يتم استدعاء طريقة الرمي () ، وبعد ذلك سيؤدي الاتصال التالي () إلى إلقاء استثناء التوقف.
يحتوي المولد على طريقة قريبة () لإجبار المولد على النهاية. سيكون لإنهاء المولد الآثار التالية:
1. سيتم تنفيذ جميع الجمل الصالحة في المولد أخيرًا
2. إذا كانت الكلمة الأخيرة تلقي أي استثناء باستثناء التوقف ، فسيتم تمرير الاستثناء إلى المتصل بالطريقة Close ().
3. سينتهي المولد
تعبيرات المولد
أحد العيوب الواضحة لاشتقاق الصفيف هو أنها تتسبب في بناء الصفيف بأكمله في الذاكرة. تكون النفقات العامة للإدخال إلى الاشتقاق ضئيلة عندما تكون النفقات العامة عبارة عن صفيف صغير نفسه - ومع ذلك ، يمكن أن تنشأ المشكلات عندما تكون صفيف الإدخال كبيرًا أو عند إنشاء مولد صفيف جديد باهظ الثمن (أو لا نهائي).
يسمح المولد الحوسبة الكسول بحساب العناصر حسب الحاجة عند الحاجة. تعبيرات المولدات هي من الناحية البريدية تقريبًا مثل اشتقاق الصفيف - فهي تستخدم الأقواس بدلاً من قوسين مربعة (وتستخدم ... بدلاً من كل ... في) - ولكنها تنشئ مولدًا بدلاً من صفيف بحيث يمكن تأخير الحساب. يمكنك التفكير في الأمر كناء على بناء جملة مختصرة لإنشاء مولد.
لنفترض أن لدينا مؤلفًا للتكرار على سلسلة ضخمة من الأعداد الصحيحة. نحن بحاجة إلى إنشاء مكرر جديد للتكرار على الأرقام الزوجية. سيقوم اشتقاق الصفيف بإنشاء صفيف كامل يحتوي على جميع الأرقام الزوجية في الذاكرة:
نسخة الكود كما يلي:
var الزوجي = [i * 2 لـ (i في ذلك)] ؛
سيقوم تعبير المولد بإنشاء مكرر جديد ويحسب القيمة الزوجية حسب الحاجة:
نسخة الكود كما يلي:
var it2 = (i * 2 for (i in it)) ؛
print (it2.next ()) ؛ // أول رقم متساوٍ فيه
print (it2.next ()) ؛ // الرقم الثاني الزوجي فيه
عند استخدام المولد كمعلمة للدالة ، يتم استخدام الأقواس كمكالمة دالة ، مما يعني أنه يمكن حذف الأقواس الخارجية:
نسخة الكود كما يلي:
var result = dosomething (i * 2 for (i in It)) ؛
نهاية.