لنبدأ بسؤال بسيط:
<script type="text/javascript">
تنبيه (أنا)؛ //؟
فار ط = 1؛
</script>
نتيجة الإخراج غير محددة. تسمى هذه الظاهرة "التحليل المسبق": سيقوم محرك JavaScript بتحليل متغيرات var وتعريفات الوظائف أولاً. لا يتم تنفيذ التعليمات البرمجية حتى اكتمال التحليل المسبق. إذا كان تدفق المستند يحتوي على مقاطع تعليمات برمجية متعددة للبرنامج النصي (رمز js مفصول بعلامات البرنامج النصي أو ملفات js مستوردة)، فإن ترتيب التشغيل هو:
الخطوة 1. قراءة مقطع التعليمات البرمجية الأول
الخطوة 2. قم بتحليل بناء الجملة إذا كان هناك خطأ، فسيتم الإبلاغ عن خطأ في بناء الجملة (مثل الأقواس غير المتطابقة، وما إلى ذلك) والانتقال إلى الخطوة 5.
الخطوة 3. قم بإجراء "تحليل مسبق" لتعريفات المتغير والوظيفة (لن يتم الإبلاغ عن أي أخطاء على الإطلاق، لأنه يتم تحليل الإعلانات الصحيحة فقط)
الخطوة 4. قم بتنفيذ مقطع التعليمات البرمجية والإبلاغ عن خطأ إذا كان هناك خطأ (على سبيل المثال، المتغير غير محدد)
step5. إذا كان هناك مقطع كود آخر، فاقرأ مقطع الكود التالي وكرر الخطوة 2.
الخطوة 6. في نهاية التحليل أعلاه، تمكنت من شرح العديد من المشكلات، لكنني أشعر دائمًا أن هناك شيئًا مفقودًا. على سبيل المثال، في الخطوة 3، ما هو بالضبط "التحليل المسبق"؟ وفي الخطوة 4، انظر إلى المثال التالي:
<script type="text/javascript">
تنبيه (أنا)؛ // خطأ: لم يتم تعريفه.
أنا = 1؛
</script>
لماذا الجملة الأولى تسبب خطأ؟ في جافا سكريبت، ألا يجب أن تكون المتغيرات غير محددة؟
مر وقت عملية التجميع مثل حصان أبيض، وفتحت "مبادئ التجميع" بجوار خزانة الكتب كما لو كانت في عالم بعيد، وكانت هناك هذه الملاحظة في المساحة الفارغة المألوفة ولكن غير المألوفة:
للغات المجمعة التقليدية تنقسم خطوات التجميع إلى: التحليل المعجمي والتحليل النحوي والتحقق الدلالي وتحسين التعليمات البرمجية وتوليد البايت.
ولكن بالنسبة للغات المفسرة، بعد الحصول على شجرة بناء الجملة من خلال التحليل المعجمي وتحليل بناء الجملة، يمكن أن يبدأ التفسير والتنفيذ.
ببساطة، التحليل المعجمي هو تحويل دفق الأحرف (دفق الأحرف) إلى دفق رمزي (دفق رمزي)، مثل تحويل c = a - b إلى:
NAME "c"
يساوي
الاسم "أ"
ناقص
الاسم "ب"
فاصلة منقوطة
ما ورد أعلاه مجرد أمثلة لمزيد من المعلومات، يرجى الاطلاع على التحليل المعجمي
في الفصل الثاني من "الدليل النهائي لجافا سكريبت" الذي يتحدث عن البنية المعجمية، والتي تم وصفها أيضًا في ECMA-262. البنية المعجمية هي أساس اللغة ومن السهل إتقانها. أما بالنسبة لتنفيذ التحليل المعجمي، فهذا مجال بحث آخر ولن يتم استكشافه هنا.
يمكننا استخدام تشبيه اللغة الطبيعية بالترجمة الصعبة من واحد إلى واحد، على سبيل المثال، إذا تمت ترجمة فقرة من اللغة الإنجليزية إلى اللغة الصينية كلمة بكلمة، فإن ما نحصل عليه هو مجموعة من التدفقات الرمزية، وهو أمر صعب. لفهم. مزيد من الترجمة تتطلب التحليل النحوي الشكل التالي هو شجرة بناء الجملة الشرطية:
عند إنشاء شجرة بناء الجملة، إذا وجد أنه لا يمكن بناؤها، مثل if(a { i = 2; }، فسيتم الإبلاغ عن خطأ في بناء الجملة وسينتهي تحليل كتلة التعليمات البرمجية بأكملها. هذه هي الخطوة 2 في بداية هذه المقالة،
من خلال تحليل بناء الجملة، بعد شجرة بناء الجملة، قد تظل الجملة المترجمة غامضة، ويلزم إجراء مزيد من التدقيق الدلالي للغات التقليدية المكتوبة بقوة، والجزء الرئيسي من التحقق الدلالي هو التحقق من الكتابة، مثل المعلمات الفعلية للوظائف وما إذا كانت أنواع المعلمات الرسمية متطابقة مع اللغات المكتوبة بشكل ضعيف، قد لا تكون هذه الخطوة متاحة (لدي طاقة محدودة وليس لدي الوقت للنظر في تنفيذ محرك JS، لذلك لست متأكدًا مما إذا كان هناك (خطوة التحقق الدلالي في محرك JS)
اتضح أنه بالنسبة لمحركات JavaScript، يجب أن يكون هناك تحليل معجمي وتحليل بناء جملة، ومن ثم قد تكون هناك خطوات مثل التحقق الدلالي وتحسين التعليمات البرمجية بعد اكتمال خطوات التجميع هذه (أي لغة لديها عملية تجميع، ولكن لم يتم تجميع اللغات المترجمة في كود ثنائي)، سيبدأ تنفيذ التعليمات البرمجية.
لا تزال عملية التجميع المذكورة أعلاه غير قادرة على شرح "التحليل المسبق" في بداية المقالة، وعلينا استكشاف التنفيذ بعناية عملية كود JavaScript
قال Zhou Aimin في "جوهر لغة JavaScript". يحتوي الجزء الثاني من "ممارسة البرمجة" على تحليل دقيق للغاية لهذا الأمر:
من خلال التجميع، تم تحليل كود JavaScript يتم ترجمتها إلى شجرة بناء الجملة، ثم سيتم تنفيذها على الفور وفقًا لشجرة بناء الجملة،
الأمر الذي يتطلب مزيدًا من التنفيذ. فهم آلية نطاق JavaScript. في مصطلحات الشخص العادي، يتم تحديد نطاق متغيرات JavaScript عند تعريفها وليس عند تنفيذها، وهذا يعني أن النطاق المعجمي يعتمد على الكود المصدري، ويمكن للمترجم تحديده من خلال التحليل الثابت، لذلك يُطلق على النطاق المعجمي أيضًا اسم النطاق الثابت ولا يمكن تحقيق التقييم إلا من خلال التكنولوجيا الثابتة. في الواقع، لا يمكننا التحدث إلا عن آلية نطاق JS القريبة جدًا من النطاق
المعجمي كائن الاستدعاء هو بنية scriptObject يتم استخدامها لحفظ هياكل تحليل بناء الجملة الداخلية مثل varDecls، و funDecls لجدول الوظائف، وقيمة القائمة المرجعية الأصلية (ملاحظة: يتم الحصول على معلومات مثل varDecls و funDecls أثناء عملية الاستدعاء. مرحلة تحليل بناء الجملة ويتم حفظها في شجرة بناء الجملة عند تنفيذ مثيل الوظيفة، سيتم نسخ هذه المعلومات من شجرة بناء الجملة إلى scriptObject). scriptObject هو نظام ثابت متعلق بالوظيفة، يتوافق مع دورة حياة مثيل الوظيفة .
النطاق المعجمي هو آلية نطاق JS، وتحتاج أيضًا إلى فهم طريقة التنفيذ، وهذه هي سلسلة النطاق. سلسلة النطاق هي آلية بحث عن الاسم، وهي تبحث أولاً عن كائن البرنامج النصي في بيئة التنفيذ الحالية، وإذا لم يتم العثور عليه، فإنها تتبع القيمة الأعلى لكائن البرنامج النصي الأصلي وتبحث عن الكائن العام.
عند تنفيذ مثيل دالة، يتم إنشاء إغلاق أو ربطه به. يتم استخدام scriptObject لحفظ الجداول المتغيرة المتعلقة بالوظائف بشكل ثابت، بينما يحفظ الإغلاق ديناميكيًا هذه الجداول المتغيرة وقيمها قيد التشغيل أثناء التنفيذ. قد تكون دورة حياة الإغلاق أطول من دورة حياة مثيل الوظيفة. سيتم تدمير مثيل الوظيفة تلقائيًا بعد أن يصبح المرجع النشط فارغًا، وسيتم إعادة تدوير الإغلاق بواسطة محرك JS بعد أن يصبح مرجع البيانات فارغًا (في بعض الحالات، لن تتم إعادة تدويره تلقائيًا، مما يؤدي إلى تسرب الذاكرة).
لا تخف من مجموعة الأسماء المذكورة أعلاه بمجرد فهمك لمفاهيم بيئة التنفيذ، وكائن الاتصال، والإغلاق، والنطاق المعجمي، وسلسلة النطاق، يمكن حل العديد من الظواهر في لغة JS بسهولة.
ملخص في هذه المرحلة، يمكن شرح الأسئلة الواردة في بداية المقالة بشكل واضح للغاية:
ما يسمى بـ "التحليل المسبق" في الخطوة 3 يكتمل بالفعل في مرحلة تحليل بناء الجملة من الخطوة 2 ويتم تخزينه في شجرة بناء الجملة. عند تنفيذ مثيل دالة، سيتم نسخ varDelcs وfuncDecls من شجرة بناء الجملة إلى كائن البرنامج النصي الخاص ببيئة التنفيذ.
في الخطوة 4، تعني المتغيرات غير المحددة أنه لا يمكن العثور عليها في جدول متغيرات scriptObject. سيبحث محرك JS لأعلى على طول قيمة scriptObject. إذا لم يتم العثور على أي منهما، فستكون عملية الكتابة i = 1; i = 1; يضيف سمة جديدة إلى كائن النافذة. بالنسبة لعمليات القراءة، إذا تعذر العثور على كائن البرنامج النصي الذي تم إرجاعه إلى بيئة التنفيذ العامة، فسيحدث خطأ في وقت التشغيل.
وبعد الفهم انقشع الضباب وتفتحت الزهور وصارت السماء صافية.
أخيرًا، أتركك مع سؤال:
<script type="text/javascript">
فار أرج = 1؛
وظيفة فو (أرج) {
تنبيه (أرج)؛
فار أرج = 2؛
}
فو(3);
</script>
ما هو مخرج التنبيه؟