قد نقرر تنفيذ وظيفة ما ليس الآن، ولكن في وقت معين لاحقًا. وهذا ما يسمى "جدولة المكالمة".
هناك طريقتان لذلك:
يتيح لنا setTimeout
تشغيل دالة مرة واحدة بعد فترة زمنية.
يتيح لنا setInterval
تشغيل دالة بشكل متكرر، بدءًا من فترة زمنية، ثم التكرار بشكل مستمر في تلك الفترة.
هذه الأساليب ليست جزءًا من مواصفات JavaScript. لكن معظم البيئات تحتوي على برنامج جدولة داخلي وتوفر هذه الأساليب. وعلى وجه الخصوص، فهي مدعومة في جميع المتصفحات وNode.js.
بناء الجملة:
دع timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
حدود:
func|code
وظيفة أو سلسلة من التعليمات البرمجية للتنفيذ. عادة، هذه وظيفة. لأسباب تاريخية، يمكن تمرير سلسلة من التعليمات البرمجية، ولكن هذا غير مستحسن.
delay
التأخير قبل التشغيل، بالمللي ثانية (1000 مللي ثانية = 1 ثانية)، بشكل افتراضي 0.
arg1
، arg2
...
الحجج للوظيفة
على سبيل المثال، يستدعي هذا الكود sayHi()
بعد ثانية واحدة:
الدالة sayHi() { تنبيه('مرحبا'); } setTimeout(sayHi, 1000);
مع الحجج:
وظيفة sayHi(عبارة، من) { تنبيه (عبارة + '،' + who ); } setTimeout(sayHi, 1000, "Hello", "John"); // مرحبا جون
إذا كانت الوسيطة الأولى عبارة عن سلسلة، فستقوم JavaScript بإنشاء دالة منها.
لذا، سيعمل هذا أيضًا:
setTimeout("alert('Hello')", 1000);
ولكن لا يُنصح باستخدام السلاسل النصية، استخدم وظائف الأسهم بدلاً منها، كما يلي:
setTimeout(() => تنبيه('مرحبا'), 1000);
قم بتمرير دالة، لكن لا تقم بتشغيلها
يخطئ المطورون المبتدئون أحيانًا بإضافة قوسين ()
بعد الوظيفة:
// خطأ! setTimeout(sayHi(), 1000);
هذا لا يعمل، لأن setTimeout
يتوقع إشارة إلى دالة. وهنا يقوم sayHi()
بتشغيل الدالة، ويتم تمرير نتيجة تنفيذها إلى setTimeout
. في حالتنا، نتيجة sayHi()
undefined
(الدالة لا تُرجع شيئًا)، لذلك لا تتم جدولة أي شيء.
يؤدي استدعاء setTimeout
إلى إرجاع معرف timerId
"معرف المؤقت" الذي يمكننا استخدامه لإلغاء التنفيذ.
بناء الجملة للإلغاء:
دع timerId = setTimeout(...); ClearTimeout(timerId);
في الكود أدناه، نقوم بجدولة الوظيفة ثم نقوم بإلغائها (غيرنا رأينا). ونتيجة لذلك، لا يحدث شيء:
Let timerId = setTimeout(() => تنبيه("لا يحدث أبدًا"), 1000); تنبيه (معرف الوقت)؛ // معرف الموقت ClearTimeout(timerId); تنبيه (معرف الوقت)؛ // نفس المعرف (لا يصبح فارغًا بعد الإلغاء)
كما يمكننا أن نرى من مخرجات alert
، فإن معرف المؤقت في المتصفح هو رقم. في بيئات أخرى، يمكن أن يكون هذا شيئا آخر. على سبيل المثال، تقوم Node.js بإرجاع كائن مؤقت مع طرق إضافية.
مرة أخرى، لا توجد مواصفات عالمية لهذه الأساليب، لذلك فلا بأس.
بالنسبة للمتصفحات، تم وصف المؤقتات في قسم المؤقتات في HTML Living Standard.
الأسلوب setInterval
له نفس بناء الجملة مثل setTimeout
:
دع timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
جميع الحجج لها نفس المعنى. ولكن على عكس setTimeout
فإنه يقوم بتشغيل الوظيفة ليس مرة واحدة فقط، ولكن بانتظام بعد فترة زمنية معينة.
لإيقاف المزيد من المكالمات، يجب علينا استدعاء clearInterval(timerId)
.
سيظهر المثال التالي الرسالة كل ثانيتين. بعد 5 ثواني، يتوقف الإخراج:
// كرر بفاصل زمني ثانيتين Let timerId = setInterval(() => تنبيه('tick'), 2000); // بعد 5 ثواني توقف setTimeout(() => { ClearInterval(timerId); تنبيه('stop'); }, 5000);
يستمر الوقت بينما يظهر alert
في معظم المتصفحات، بما في ذلك Chrome وFirefox، يستمر المؤقت الداخلي في "الإشارة" أثناء عرض alert/confirm/prompt
.
لذا، إذا قمت بتشغيل الكود أعلاه ولم تتجاهل نافذة alert
لبعض الوقت، فسيتم عرض alert
التالي على الفور أثناء قيامك بذلك. سيكون الفاصل الزمني الفعلي بين التنبيهات أقل من ثانيتين.
هناك طريقتان لتشغيل شيء ما بانتظام.
واحد هو setInterval
. والآخر هو setTimeout
متداخل، مثل هذا:
/** بدلاً من: Let timerId = setInterval(() => تنبيه('tick'), 2000); */ دع timerId = setTimeout(وظيفة القراد() { تنبيه('القراد'); timerId = setTimeout(tick, 2000); // (*) }، 2000)؛
يقوم setTimeout
أعلاه بجدولة المكالمة التالية مباشرةً في نهاية المكالمة الحالية (*)
.
يعد setTimeout
المتداخل طريقة أكثر مرونة من setInterval
. بهذه الطريقة قد تتم جدولة المكالمة التالية بشكل مختلف، اعتمادًا على نتائج المكالمة الحالية.
على سبيل المثال، نحتاج إلى كتابة خدمة ترسل طلبًا إلى الخادم كل 5 ثوانٍ يطلب البيانات، ولكن في حالة التحميل الزائد على الخادم، يجب زيادة الفاصل الزمني إلى 10، 20، 40 ثانية...
إليك الكود الزائف:
دع التأخير = 5000؛ دع timerId = setTimeout(طلب الوظيفة() { ...أرسل الطلب... إذا (فشل الطلب بسبب التحميل الزائد على الخادم) { // زيادة الفاصل الزمني إلى الجولة التالية تأخير *= 2; } timerId = setTimeout(request, Delay); }، تأخير)؛
وإذا كانت الوظائف التي نقوم بجدولتها متعطشة لوحدة المعالجة المركزية، فيمكننا قياس الوقت الذي يستغرقه التنفيذ والتخطيط للمكالمة التالية عاجلاً أم آجلاً.
يسمح setTimeout
المتداخل بضبط التأخير بين عمليات التنفيذ بشكل أكثر دقة من setInterval
.
دعونا نقارن بين شظيتين من التعليمات البرمجية. الأول يستخدم setInterval
:
دعني = 1؛ سيت إنترفال (وظيفة () { وظيفة(i++); }، 100)؛
يستخدم الثاني setTimeout
المتداخل :
دعني = 1؛ setTimeout(تشغيل الوظيفة() { وظيفة(i++); setTimeout(run, 100); }، 100)؛
بالنسبة إلى setInterval
سيتم تشغيل المجدول الداخلي func(i++)
كل 100 مللي ثانية:
هل لاحظت؟
التأخير الحقيقي بين استدعاءات func
لـ setInterval
أقل مما هو موجود في الكود!
وهذا طبيعي، لأن الوقت الذي يستغرقه تنفيذ func
"يستهلك" جزءًا من الفاصل الزمني.
من الممكن أن يكون تنفيذ func
أطول مما توقعنا ويستغرق أكثر من 100 مللي ثانية.
في هذه الحالة، ينتظر المحرك حتى تكتمل func
، ثم يتحقق من المجدول وإذا انتهى الوقت، يقوم بتشغيله مرة أخرى على الفور .
في حالة الحافة، إذا كانت الوظيفة يتم تنفيذها دائمًا لفترة أطول من delay
مللي ثانية، فسيتم إجراء المكالمات دون توقف مؤقت على الإطلاق.
وهذه هي صورة setTimeout
المتداخلة:
يضمن setTimeout
المتداخل تأخيرًا ثابتًا (هنا 100 مللي ثانية).
وذلك لأنه يتم التخطيط لإجراء مكالمة جديدة في نهاية المكالمة السابقة.
جمع البيانات المهملة ورد الاتصال setInterval/setTimeout
عندما يتم تمرير دالة في setInterval/setTimeout
، يتم إنشاء مرجع داخلي لها وحفظه في المجدول. يمنع الدالة من تجميع البيانات المهملة، حتى لو لم يكن هناك أي مراجع أخرى لها.
// تظل الوظيفة في الذاكرة حتى يستدعيها المجدول setTimeout(function() {...}, 100);
بالنسبة إلى setInterval
تبقى الدالة في الذاكرة حتى يتم استدعاء clearInterval
.
هناك تأثير جانبي. تشير الدالة إلى البيئة المعجمية الخارجية، لذلك، أثناء وجودها، تعيش المتغيرات الخارجية أيضًا. وقد تشغل ذاكرة أكبر بكثير من الوظيفة نفسها. لذلك عندما لا نحتاج إلى الوظيفة المجدولة بعد الآن، فمن الأفضل إلغاؤها، حتى لو كانت صغيرة جدًا.
هناك حالة استخدام خاصة: setTimeout(func, 0)
أو فقط setTimeout(func)
.
يؤدي هذا إلى جدولة تنفيذ func
في أقرب وقت ممكن. لكن المجدول لن يستدعيه إلا بعد اكتمال البرنامج النصي الذي يتم تنفيذه حاليًا.
لذلك تمت جدولة الوظيفة للتشغيل "مباشرة بعد" البرنامج النصي الحالي.
على سبيل المثال، يُخرج هذا "Hello"، ثم على الفور "World":
setTimeout(() => تنبيه("World")); تنبيه("مرحبا");
السطر الأول "يضع المكالمة في التقويم بعد 0 مللي ثانية". لكن المجدول سوف "يتحقق من التقويم" فقط بعد اكتمال البرنامج النصي الحالي، لذا فإن "Hello"
تأتي أولاً، و "World"
- بعدها.
هناك أيضًا حالات استخدام متقدمة متعلقة بالمتصفح لمهلة التأخير الصفري، والتي سنناقشها في فصل حلقة الأحداث: المهام الدقيقة والمهام الكبيرة.
التأخير الصفري ليس في الواقع صفرًا (في المتصفح)
يوجد في المتصفح قيود على عدد المرات التي يمكن فيها تشغيل المؤقتات المتداخلة. يقول معيار HTML Living: "بعد خمسة مؤقتات متداخلة، يجب أن يكون الفاصل الزمني 4 مللي ثانية على الأقل."
دعونا نوضح ما يعنيه ذلك من خلال المثال أدناه. يقوم استدعاء setTimeout
بإعادة جدولة نفسه دون أي تأخير. تتذكر كل مكالمة الوقت الفعلي من المكالمة السابقة في مصفوفة times
. كيف تبدو التأخيرات الحقيقية؟ دعونا نرى:
لنبدأ = Date.now(); دع الأوقات = []؛ setTimeout(تشغيل الوظيفة() { times.push(Date.now() - start); // تذكر التأخير من المكالمة السابقة if (start + 100 < Date.now()) تنبيه (مرات) ؛ // إظهار التأخير بعد 100 مللي ثانية else setTimeout(run); // آخر إعادة الجدولة }); // مثال على الإخراج: // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
تعمل أجهزة ضبط الوقت الأولى على الفور (تمامًا كما هو مكتوب في المواصفات)، ثم نرى 9, 15, 20, 24...
. يتم تفعيل التأخير الإلزامي بمقدار 4+ مللي ثانية بين الاستدعاءات.
يحدث الشيء المشابه إذا استخدمنا setInterval
بدلاً من setTimeout
: يتم تشغيل setInterval(f)
عدة f
بدون تأخير، وبعد ذلك بتأخير قدره 4+ مللي ثانية.
يأتي هذا القيد من العصور القديمة وتعتمد عليه العديد من النصوص، لذا فهو موجود لأسباب تاريخية.
بالنسبة لجافا سكريبت من جانب الخادم، هذا القيد غير موجود، وتوجد طرق أخرى لجدولة مهمة فورية غير متزامنة، مثل setImmediate لـ Node.js. لذا فإن هذه الملاحظة خاصة بالمتصفح.
تسمح لنا الطرق setTimeout(func, delay, ...args)
و setInterval(func, delay, ...args)
بتشغيل func
مرة واحدة/بانتظام بعد delay
بالمللي ثانية.
لإلغاء التنفيذ، يجب علينا استدعاء clearTimeout/clearInterval
مع القيمة التي يتم إرجاعها بواسطة setTimeout/setInterval
.
تعد استدعاءات setTimeout
المتداخلة بديلاً أكثر مرونة لـ setInterval
، مما يسمح لنا بضبط الوقت بين عمليات التنفيذ بشكل أكثر دقة.
يتم استخدام جدولة التأخير الصفري باستخدام setTimeout(func, 0)
(مثل setTimeout(func)
) ) لجدولة المكالمة "في أقرب وقت ممكن، ولكن بعد اكتمال البرنامج النصي الحالي".
يحدد المتصفح الحد الأدنى من التأخير لخمسة أو أكثر من الاستدعاءات المتداخلة لـ setTimeout
أو لـ setInterval
(بعد المكالمة الخامسة) بـ 4 مللي ثانية. وذلك لأسباب تاريخية.
يرجى ملاحظة أن جميع طرق الجدولة لا تضمن التأخير الدقيق.
على سبيل المثال، قد يتباطأ مؤقت المتصفح لعدة أسباب:
وحدة المعالجة المركزية مثقلة.
علامة تبويب المتصفح في وضع الخلفية.
الكمبيوتر المحمول في وضع توفير البطارية.
كل ذلك قد يزيد من الحد الأدنى لدقة المؤقت (الحد الأدنى من التأخير) إلى 300 مللي ثانية أو حتى 1000 مللي ثانية اعتمادًا على المتصفح وإعدادات الأداء على مستوى نظام التشغيل.
الأهمية: 5
اكتب دالة printNumbers(from, to)
تُخرج رقمًا كل ثانية، بدءًا من from
وانتهاءً بـ to
.
قم بعمل نوعين مختلفين من الحل.
باستخدام setInterval
.
باستخدام setTimeout
المتداخلة.
باستخدام setInterval
:
أرقام الطباعة الدالة (من، إلى) { دع التيار = من؛ دع timerId = setInterval(function() { تنبيه (الحالي)؛ إذا (الحالي == إلى) { ClearInterval(timerId); } الحالي++; }, 1000); } // الاستخدام: printNumbers(5, 10);
باستخدام setTimeout
المتداخلة :
أرقام الطباعة الدالة (من، إلى) { دع التيار = من؛ setTimeout(وظيفة الذهاب() { تنبيه (الحالي)؛ إذا (الحالي < إلى) { setTimeout(go, 1000); } الحالي++; }, 1000); } // الاستخدام: printNumbers(5, 10);
لاحظ أنه في كلا الحلين، هناك تأخير أولي قبل الإخراج الأول. يتم استدعاء الدالة بعد 1000ms
في المرة الأولى.
إذا أردنا أيضًا تشغيل الوظيفة على الفور، فيمكننا إضافة استدعاء إضافي على سطر منفصل، مثل هذا:
أرقام الطباعة الدالة (من، إلى) { دع التيار = من؛ وظيفة الذهاب () { تنبيه (الحالي)؛ إذا (الحالي == إلى) { ClearInterval(timerId); } الحالي++; } يذهب()؛ Let timerId = setInterval(go, 1000); } printNumbers(5, 10);
الأهمية: 5
يوجد في الكود أدناه مكالمة setTimeout
مجدولة، ثم يتم تشغيل عملية حسابية ثقيلة، وتستغرق أكثر من 100 مللي ثانية للانتهاء.
متى سيتم تشغيل الوظيفة المجدولة؟
بعد الحلقة.
قبل الحلقة.
في بداية الحلقة.
ما هو alert
سوف تظهر؟
دعني = 0؛ setTimeout(() => تنبيه(i), 100); // ؟ // افترض أن الوقت اللازم لتنفيذ هذه الوظيفة هو >100 مللي ثانية من أجل (دع j = 0؛ j <100000000؛ j++) { أنا++; }
لن يتم تشغيل أي setTimeout
إلا بعد انتهاء الكود الحالي.
i
الأخير: 100000000
.
دعني = 0؛ setTimeout(() => تنبيه(i), 100); // 100000000 // افترض أن الوقت اللازم لتنفيذ هذه الوظيفة هو >100 مللي ثانية من أجل (دع j = 0؛ j <100000000؛ j++) { أنا++; }