JavaScript هي لغة أحادية الخيط، وفي الوقت نفسه، هي أيضًا لغة غير متوقفة وغير متزامنة ومتزامنة. ستشرح لك هذه المقالة كيف يحدث ذلك.
وقت التشغيل
JavaScript هي لغة مفسرة وليست مترجمة. هذا يعني أنها تحتاج إلى مترجم فوري يقوم بتحويل JS الكود إلى رمز الآلة. هناك عدة أنواع من المترجمات (المعروفة باسم المحركات). محركات المتصفح الأكثر شيوعًا هي V8 (كروم) و Quantum (فايرفوكس) و WebKit (سفاري). وبالمناسبة، يستخدم V8 أيضًا في وقت تشغيل شائع غير المتصفح, Node.js.
يحتوي كل محرك على كومة ذاكرة، ومكدس استدعاءات، وحلقة أحداث، وقائمة انتظار لردود الاتصالات، وواجهة WebAPI مع طلبات HTTP، ومؤقتات، وأحداث، وما إلى ذلك، وكلها تنفذ بطريقتها الخاصة لتفسير أسرع وأكثر أمانًا لرمز JS.
بنية وقت تشغيل JS الأساسية. المؤلف: أليكس زلاتكوف
خيط واحد
اللغة أحادية الخيط هي لغة ذات مكدس استدعاء واحد وكومة ذاكرة واحدة. وهذا يعني أنها تقوم بتشغيل شيء واحد فقط في كل مرة.
A كومة منطقة متصلة من الذاكرة، وتخصيص سياق محلي لكل دالة منفذة.
A كومة منطقة أكبر بكثير، تخزن كل شيء مخصص ديناميكيًا.
A مكدس المكالمات عبارة عن بنية بيانات تسجل بشكل أساسي مكان وجودنا في البرنامج.
مكدس المكالمات
لنكتب شيفرة بسيطة ونتتبع ما يحدث على مكدس النداء.
كما ترى، تتم إضافة الدوال إلى المكدس وتنفيذها وحذفها لاحقًا. إنها الطريقة المسماة LIFO - آخر داخل، أول خارج. يُطلق على كل إدخال في مكدس الاستدعاء اسم إطار المكدس.
معرفة مكدس الاستدعاء مفيد لقراءة آثار مكدس الأخطاء. بشكل عام، يكون السبب الدقيق للخطأ في أعلى السطر الأول، على الرغم من أن ترتيب تنفيذ التعليمات البرمجية يكون من الأسفل إلى الأعلى.
في بعض الأحيان يمكنك التعامل مع خطأ شائع، يتم إعلامك به عن طريق تم تجاوز الحد الأقصى لحجم مكدس المكالمات. من السهل الحصول على ذلك باستخدام التكرار:
الدالة foo() {
foo()
}
فو()
ويتجمد متصفحنا أو جهازنا الطرفي. كل متصفح، حتى في إصداراته المختلفة، لديه حد مختلف لحجم مكدس المكالمات. في الغالبية العظمى من الحالات، تكون كافية ويجب البحث عن المشكلة في مكان آخر.
كومة المكالمات المحظورة
فيما يلي مثال على حظر مؤشر ترابط JS. لنحاول قراءة فو ملف و شريط باستخدام العقدةدالة متزامنة .js مزامنة الملفات.
هذه صورة GIF متكررة. كما ترى، ينتظر محرك JS حتى الاستدعاء الأول في مزامنة الملفات قد اكتمل. ولكن هذا لن يحدث لأنه لا يوجد فو ملف، لذا لن يتم استدعاء الدالة الثانية أبدًا.
السلوك غير المتزامن
ومع ذلك، يمكن أن تكون JS أيضًا غير متوقفة وتتصرف كما لو كانت متعددة الخيوط. وهذا يعني أنه لا ينتظر استجابة مكالمة واجهة برمجة التطبيقات، أو أحداث الإدخال/الإخراج، وما إلى ذلك، ويمكنه متابعة تنفيذ التعليمات البرمجية. هذا الأمر ممكن بفضل محركات JS التي تستخدم (تحت الغطاء) لغات حقيقية متعددة الخيوط، مثل C++ (كروم) أو Rust (فايرفوكس). فهي توفر لنا واجهة برمجة تطبيقات الويب تحت أغطية المتصفح أو على سبيل المثال. واجهة برمجة تطبيقات الإدخال/الإخراج تحت Node.js.
في صورة GIF أعلاه، يمكننا أن نرى أن الدالة الأولى يتم دفعها إلى مكدس الاستدعاء و مرحباً على الفور في وحدة التحكم.
بعد ذلك، نطلق على ضبط الوقت المستقطع التي يوفرها WebAPI الخاص بالمتصفح. تنتقل إلى مكدس الاستدعاء ورد النداء غير المتزامن الخاص بها فو تنتقل الدالة إلى قائمة انتظار WebApi، حيث تنتظر المكالمة، التي تم تعيينها لتحدث بعد 3 ثوانٍ.
في هذه الأثناء، يستمر البرنامج في التعليمات البرمجية ونرى مرحباً، أنا لست محجوباً في وحدة التحكم.
بعد أن يتم استدعاؤها، تنتقل كل دالة في قائمة انتظار WebAPI إلى قائمة انتظار الرد على المكالمات. وهو المكان الذي تنتظر فيه الدوال حتى تصبح كومة الاستدعاء فارغة. عندما يحدث ذلك، يتم نقلها إلى هناك واحدة تلو الأخرى.
لذا، عندما ضبط الوقت المستقطع ينتهي العد التنازلي للمؤقِّت فو تنتقل الدالة إلى قائمة انتظار رد الاستدعاء، وتنتظر حتى يصبح مكدس الاستدعاء متاحًا، ثم تذهب إلى هناك، ويتم تنفيذها، ونرى مرحبًا من معاودة الاتصال غير المتزامن في وحدة التحكم.
حلقة الحدث
السؤال هو، كيف يعرف وقت التشغيل أن مكدس الاستدعاء فارغ وكيف يتم استدعاء الحدث في قائمة انتظار رد الاتصال؟ تعرّف على حلقة الحدث. إنها جزء من محرك JS. تتحقق هذه العملية باستمرار مما إذا كانت كومة الاستدعاء فارغة، وإذا كانت فارغة، تراقب ما إذا كان هناك حدث في قائمة انتظار رد النداء ينتظر الاستدعاء.
هذا هو كل السحر وراء الكواليس!
اختتام النظرية
التزامن والتوازي
التزامن يعني تنفيذ مهام متعددة في نفس الوقت ولكن ليس في وقت واحد. على سبيل المثال مهمتان تعملان في فترات زمنية متداخلة.
التوازي يعني تنفيذ مهمتين أو أكثر في وقت واحد، مثل إجراء عمليات حسابية متعددة في الوقت نفسه.
الخيوط والعمليات
الخيوط هي سلسلة من تنفيذ التعليمات البرمجية التي يمكن تنفيذها بشكل مستقل عن بعضها البعض.
العملية هو مثيل لبرنامج قيد التشغيل. يمكن أن يحتوي البرنامج على عمليات متعددة.
متزامن وغير متزامن
في متزامن البرمجة، يتم تنفيذ المهام واحدة تلو الأخرى. تنتظر كل مهمة حتى تكتمل أي مهمة سابقة ويتم تنفيذها بعد ذلك فقط.
في غير متزامن البرمجة، عند تنفيذ مهمة واحدة، يمكنك التبديل إلى مهمة مختلفة دون انتظار اكتمال المهمة السابقة.
متزامن وغير متزامن في بيئة أحادية ومتعددة الخيوط
متزامن مع مؤشر ترابط واحد: يتم تنفيذ المهام واحدة تلو الأخرى. تنتظر كل مهمة تنفيذ المهمة السابقة لها.
متزامن مع خيوط متعددة: يتم تنفيذ المهام في خيوط مختلفة ولكن في انتظار تنفيذ أي مهام أخرى على أي خيط آخر.
غير متزامن مع مؤشر ترابط واحد: يبدأ تنفيذ المهام دون انتظار انتهاء مهمة أخرى. في وقت معين، يمكن تنفيذ مهمة واحدة فقط.
غير متزامن مع خيوط متعددة: يتم تنفيذ المهام في خيوط مختلفة دون انتظار اكتمال المهام الأخرى وإنهاء تنفيذها بشكل مستقل.
تصنيف JavaScript
إذا أخذنا بعين الاعتبار كيفية عمل محركات JS تحت الغطاء، يمكننا تصنيف JS كلغة مفسرة غير متزامنة وذات خيط واحد. كلمة "مفسرة" مهمة جدًا لأنها تعني أن اللغة ستكون دائمًا معتمدة على وقت التشغيل ولن تكون أبدًا بنفس سرعة اللغات المجمعة ذات الخيوط المتعددة المدمجة.
من الجدير بالذكر أن Node.js يمكن أن يحقق تعدد مؤشرات ترابط حقيقي، شريطة أن يبدأ كل مؤشر ترابط كعملية منفصلة. هناك مكتبات لهذا الغرض، لكن Node.js لديه ميزة مدمجة تسمى خيوط العامل.
تأتي جميع صور GIF حلقة الحدث من لوب تطبيق أنشأه فيليب روبرتس، حيث يمكنك اختبار سيناريوهاتك غير المتزامنة.