لقد استكشفنا سابقًا مبدأ عمل JavaScript من خلال آلية التحليل لمحرك JavaScript. والآن نستخدم مثالًا أكثر وضوحًا لتوضيح ترتيب تنفيذ تعليمات JavaScript البرمجية على الصفحة. إذا كانت آلية عمل محرك JavaScript عميقة نسبيًا لأنها تنتمي إلى السلوك الأساسي، فإن ترتيب تنفيذ كود JavaScript أكثر وضوحًا، لأنه يمكننا أن نشعر بشكل حدسي بأمر التنفيذ هذا بالطبع، ترتيب تنفيذ كود JavaScript إنها معقدة نسبيًا، لذا من الضروري أيضًا تحديد ملف تعريف لغة JavaScript قبل الغوص فيها.
1.1 تنفيذ تعليمات JavaScript البرمجية بترتيب تدفق مستند HTML
بادئ ذي بدء، يجب أن يعلم القراء أن عملية تحليل مستندات HTML في المتصفح تتم على النحو التالي: يقوم المتصفح بتحليل بنية الصفحة والمعلومات تدريجيًا من أعلى إلى أسفل وفقًا لتدفق المستند. يجب أيضًا اعتبار كود JavaScript باعتباره برنامجًا نصيًا مضمنًا أحد مكونات مستند HTML، لذلك يتم أيضًا تحديد ترتيب تنفيذ كود JavaScript أثناء التحميل بناءً على الترتيب الذي تظهر به علامة البرنامج النصي <script>. على سبيل المثال، تصفح صفحة التوثيق أدناه وسترى أنه تم تحليل الكود خطوة بخطوة من الأعلى إلى الأسفل.
انسخ رمز الكود كما يلي:
<النص البرمجي>
تنبيه ("النص العلوي")؛
</script>
<html><الرأس>
<النص البرمجي>
تنبيه ("البرنامج النصي الرئيسي")؛
</script>
<العنوان></العنوان>
</الرأس>
<الجسم>
<النص البرمجي>
تنبيه ("نص الصفحة")؛
</script>
</body></html>
<النص البرمجي>
تنبيه ("النص السفلي")؛
</script>
إذا تم استيراد برنامج نصي لملف JavaScript خارجي من خلال سمة src لعلامة البرنامج النصي <script>، فسيتم تنفيذه أيضًا بالترتيب الذي تظهر به بياناته، وتكون عملية التنفيذ جزءًا من تحميل المستند. لن يتأخر التنفيذ لأنه ملف JavaScript خارجي. على سبيل المثال، قم بنقل البرامج النصية الموجودة في منطقتي الرأس والجسم للمستند أعلاه إلى ملفات JavaScript خارجية، ثم قم باستيرادها من خلال السمة src. مع الاستمرار في معاينة مستند الصفحة، سترى نفس ترتيب التنفيذ.
انسخ رمز الكود كما يلي:
<النص البرمجي>
تنبيه ("النص العلوي")؛
</script>
<أتش تي أم أل>
<الرأس>
<script src="//www.VeVB.COm/head.js"></script>
<العنوان></العنوان>
</الرأس>
<الجسم>
<script src="//www.VeVB.COm/body.js"></script>
</الجسم>
</html>
<النص البرمجي>
تنبيه ("النص السفلي")؛
</script>
1.2 العلاقة بين التجميع المسبق وأمر التنفيذ
في جافا سكريبت، الوظيفة هي النوع الأول من جافا سكريبت. عندما نكتب دالة، فإننا في الواقع نقوم فقط بإنشاء كيان من نوع دالة.
مثلما يمكننا كتابتها بهذا الشكل:
انسخ رمز الكود كما يلي:
وظيفة مرحبا ()
{
تنبيه("مرحبا");
}
مرحبًا()؛
فارهيلو = وظيفة ()
{
تنبيه("مرحبا");
}
مرحبًا()؛
في الواقع، كلهم متشابهون. لكن عندما نقوم بتعديل الوظائف سنجد مشاكل غريبة جداً.
انسخ رمز الكود كما يلي:
<scripttype="text/javascript">
وظيفة مرحبا () {
تنبيه("مرحبا");
}
مرحبًا()؛
وظيفة مرحبا () {
تنبيه("مرحبا بالعالم");
}
مرحبًا()؛
</script>
سنرى النتيجة على النحو التالي: يتم إخراج Hello World مرتين على التوالي.
بدلاً من عالم الترحيب والترحيب الذي تخيلناه.
وذلك لأن جافا سكريبت لم يتم تفسيرها وتنفيذها بشكل كامل، وبدلاً من ذلك، يتم "ترجمة" جافا سكريبت قبل التفسير، أثناء عملية الترجمة المسبقة، سيتم تنفيذ الوظائف المحددة أولاً وسيتم إنشاء جميع متغيرات var، والقيمة الافتراضية غير محددة للتحسين كفاءة تنفيذ البرنامج.
بمعنى آخر، تم بالفعل تجميع جزء التعليمات البرمجية أعلاه مسبقًا بواسطة محرك JS في هذا النموذج:
انسخ رمز الكود كما يلي:
<scripttype="text/javascript">
فارهيلو = وظيفة() {
تنبيه("مرحبا");
}
مرحبا = وظيفة () {
تنبيه("مرحبا بالعالم");
}
مرحبًا()؛
مرحبًا()؛
</script>
يمكننا أن نرى بوضوح من الكود أعلاه أن الوظائف هي أيضًا بيانات ومتغيرات، ويمكننا أيضًا تعيين (إعادة تعيين) القيم إلى "الوظائف".
وبطبيعة الحال، من أجل منع هذا الوضع، يمكننا أيضا أن نفعل هذا:
انسخ رمز الكود كما يلي:
<scripttype="text/javascript">
وظيفة مرحبا () {
تنبيه("مرحبا");
}
مرحبًا()؛
</script>
<scripttype="text/javascript">
وظيفة مرحبا () {
تنبيه("مرحبا بالعالم");
}
مرحبًا()؛
</script>
بهذه الطريقة ينقسم البرنامج إلى قسمين، ولن يجمعهم محرك JS معًا.
عندما يقوم محرك JavaScript بتوزيع برنامج نصي، فإنه يعالج جميع المتغيرات والوظائف المعلنة أثناء الترجمة المسبقة.
قم بما يلي:
1. قبل التنفيذ، سيتم تنفيذ عملية مشابهة لـ "التجميع المسبق": أولاً، سيتم إنشاء كائن نشط في بيئة التنفيذ الحالية، وسيتم تعيين تلك المتغيرات المعلنة باستخدام var كسمات للكائن النشط، ولكن في هذا الوقت ، سيكون تعيين هذه المتغيرات غير محدد، وتلك الوظائف المعرفة بالوظيفة ستتم إضافتها أيضًا كخصائص للكائن النشط، وقيمها هي بالضبط تعريف الوظيفة.
2. أثناء مرحلة التفسير والتنفيذ، عندما يحتاج المتغير إلى التحليل، سيتم البحث عنه أولاً من الكائن النشط لبيئة التنفيذ الحالية. إذا لم يتم العثور عليه وكان مالك بيئة التنفيذ لديه سمة النموذج الأولي، فسيتم العثور عليه سيتم البحث عنه من سلسلة النموذج الأولي، وإلا فسيتم البحث عنه وفقًا لسلسلة النطاق. عند مواجهة عبارة مثل var a = ...، سيتم تعيين قيمة للمتغير المقابل (ملاحظة: يتم الانتهاء من تعيين المتغير أثناء مرحلة التفسير والتنفيذ. إذا تم استخدام المتغير قبل ذلك، فستكون قيمته غير محدد). لذلك، سيظهر أنه لن يتم الإبلاغ عن أي خطأ عندما يقوم مترجم JavaScript بتنفيذ البرنامج النصي التالي:
انسخ رمز الكود كما يلي:
تنبيه (أ)؛ // قيمة الإرجاع غير محددة
فار أ =1؛
تنبيه (أ)؛ // القيمة المرجعة 1
نظرًا لأنه تتم معالجة إعلانات المتغيرات في وقت الترجمة المسبق، فإنها تكون مرئية لجميع التعليمات البرمجية أثناء التنفيذ. ومع ذلك، سترى أيضًا أنه عند تنفيذ التعليمات البرمجية أعلاه، تكون القيمة المطلوبة غير محددة، وليست 1. وذلك لأن عملية تهيئة المتغير تحدث أثناء التنفيذ، وليس التجميع المسبق. أثناء التنفيذ، يقوم مترجم JavaScript بتوزيع التعليمات البرمجية بالترتيب. إذا لم يتم تعيين قيمة للمتغير في السطر السابق من التعليمات البرمجية، فسيستخدم مترجم JavaScript القيمة الافتراضية غير المحددة. نظرًا لأنه تم تعيين قيمة للمتغير a في السطر الثاني، فإن السطر الثالث من التعليمات البرمجية سيطالب بأن قيمة المتغير a هي 1، وليست غير محددة.
وبالمثل، في المثال التالي، من القانوني استدعاء الدالة قبل الإعلان عن الدالة ويمكن تحليلها بشكل صحيح، وبالتالي فإن القيمة المرجعة هي 1.
انسخ رمز الكود كما يلي:
f(); // وظيفة الاتصال، قيمة الإرجاع 1
وظيفة و () {
تنبيه (1)؛
}
ومع ذلك، إذا تم تعريف الوظيفة على النحو التالي، فسيطالبك مترجم JavaScript بحدوث خطأ في بناء الجملة.
انسخ رمز الكود كما يلي:
f(); // استدعاء الدالة وإرجاع خطأ في بناء الجملة
فار و = وظيفة(){
تنبيه (1)؛
}
وذلك لأن الوظيفة المحددة في المثال أعلاه تم تعيينها فقط للمتغير f كقيمة، لذلك، خلال فترة ما قبل الترجمة، يمكن لمترجم JavaScript فقط معالجة إعلان المتغير f، ويمكن لقيمة المتغير f فقط. سيتم الضغط عليه أثناء فترة التنفيذ. إذا تم تنفيذ المهام بشكل تسلسلي، فمن الطبيعي أن يحدث خطأ في بناء الجملة، مما يؤدي إلى عدم إمكانية العثور على الكائن f.
وداعا بعض الأمثلة:
انسخ رمز الكود كما يلي:
<نوع البرنامج النصي = "نص/جافا سكريبت">
/*أثناء عملية الترجمة المسبقة، تكون func سمة في الكائن النشط في بيئة النافذة، والقيمة هي دالة تغطي القيمة غير المحددة*/
تنبيه (وظيفة) // وظيفة func () {alert ("مرحبا!")}
var func = "هذا متغير"
وظيفة وظيفة () {
تنبيه ("مرحبا!")
}
/*أثناء التنفيذ، تمت مصادفة var وتم إعادة تعيينه إلى "هذا متغير"*/
تنبيه (وظيفة)؛ // هذا متغير
</script>
انسخ رمز الكود كما يلي:
<نوع البرنامج النصي = "نص/جافا سكريبت">
اسم فار = "فنغ" وظيفة func();
{
/* أولاً، قم بتعيين اسم إلى غير محدد في بيئة func، ثم ابحث عن سمة اسم الكائن النشط في بيئة func أثناء التنفيذ فنغ */
تنبيه (الاسم)؛ // غير محدد var name = "JSF";
تنبيه (الاسم)؛ //JSF
}
وظيفة();
تنبيه (الاسم)؛
// فنغ
</script>
على الرغم من أن تعريفات المتغيرات والوظائف يمكن أن تكون في أي مكان في المستند، إلا أنه من الممارسات الجيدة الإعلان عن المتغيرات والوظائف العامة قبل جميع تعليمات JavaScript البرمجية، وتهيئة المتغيرات وتعيينها. داخل الدالة، يتم الإعلان عن المتغيرات أولاً ثم يتم الرجوع إليها.
1.3 تنفيذ كود JavaScript في الكتل
ما يسمى بكتل التعليمات البرمجية هي أجزاء من التعليمات البرمجية مفصولة بعلامات <script>. على سبيل المثال، تمثل علامتا <script> أدناه كتلتين من تعليمات JavaScript البرمجية.
انسخ رمز الكود كما يلي:
<النص البرمجي>
// كتلة كود جافا سكريبت 1
فار أ =1؛
</script>
<النص البرمجي>
// كتلة كود جافا سكريبت 2
وظيفة و () {
تنبيه (1)؛
}
</script>
عندما يقوم مترجم JavaScript بتنفيذ برنامج نصي، فإنه ينفذه على شكل كتل. بعبارات عامة، إذا واجه المتصفح علامة <script> عند تحليل دفق مستند HTML، فسوف ينتظر مترجم JavaScript حتى يتم تحميل كتلة التعليمات البرمجية، ثم يقوم أولاً بتجميع كتلة التعليمات البرمجية مسبقًا، ثم ينفذها. بعد التنفيذ، يستمر المتصفح في تحليل دفق مستند HTML أدناه، ويكون مترجم JavaScript جاهزًا لمعالجة المجموعة التالية من التعليمات البرمجية.
نظرًا لأنه يتم تنفيذ JavaScript في كتل، إذا قمت باستدعاء متغير أو وظيفة تم الإعلان عنها في كتلة لاحقة في كتلة JavaScript، فستتم مطالبتك بحدوث خطأ في بناء الجملة. على سبيل المثال، عندما ينفذ مترجم JavaScript التعليمات البرمجية التالية، فإنه سيطالب بحدوث خطأ في بناء الجملة، مما يوضح أن المتغير a غير محدد ولا يمكن العثور على الكائن f.
انسخ رمز الكود كما يلي:
<النص البرمجي>
// كتلة كود جافا سكريبت 1
تنبيه (أ)؛
و ()؛
</script>
<النص البرمجي>
// كتلة كود جافا سكريبت 2
فار أ =1؛
وظيفة و () {
تنبيه (1)؛
}
</script>
على الرغم من أن JavaScript يتم تنفيذه في كتل، إلا أن الكتل المختلفة تنتمي إلى نفس النطاق العام، مما يعني أنه يمكن مشاركة المتغيرات والوظائف بين الكتل.
1.4 استخدم آلية الحدث لتغيير ترتيب تنفيذ JavaScript
نظرًا لأن JavaScript يعالج التعليمات البرمجية على شكل أجزاء ويتبع ترتيب التحليل لتدفق مستند HTML، فسوف ترى مثل هذه الأخطاء النحوية في المثال أعلاه. ولكن عند تحميل دفق المستند، لن يحدث مثل هذا الخطأ إذا تم الوصول إليه مرة أخرى. على سبيل المثال، إذا تم وضع التعليمات البرمجية التي تصل إلى المتغيرات والوظائف في المجموعة الثانية من التعليمات البرمجية في وظيفة حدث تهيئة الصفحة، فلن تكون هناك أخطاء في بناء الجملة.
انسخ رمز الكود كما يلي:
<النص البرمجي>
// كتلة كود جافا سكريبت 1
window.onload = function(){ // وظيفة معالجة حدث تهيئة الصفحة
تنبيه (أ)؛
و ()؛
}
</script>
<النص البرمجي>
// كتلة كود جافا سكريبت 2
فار أ =1؛
وظيفة و () {
تنبيه (1)؛
}
</script>
لأسباب أمنية، نسمح عمومًا بتنفيذ كود JavaScript فقط بعد تهيئة الصفحة، وهذا يمكن أن يتجنب تأثير سرعة الشبكة على تنفيذ JavaScript، وكذلك تجنب القيود المفروضة على تنفيذ JavaScript الناتجة عن تدفق مستندات HTML.
يلاحظ
إذا كان هناك العديد من معالجات الأحداث windows.onload في الصفحة، فإن الأخير فقط يكون صالحًا لحل هذه المشكلة، يمكنك وضع جميع البرامج النصية أو وظائف الاستدعاء في نفس معالج الأحداث onload، على سبيل المثال:
انسخ رمز الكود كما يلي:
نافذة.onload = وظيفة () {
f1();
f2();
f3();
}
وبهذه الطريقة، يمكن تغيير ترتيب تنفيذ الوظائف ببساطة عن طريق ضبط ترتيب استدعاء الوظائف في معالج أحداث التحميل.
بالإضافة إلى أحداث تهيئة الصفحة، يمكننا أيضًا تغيير ترتيب تنفيذ كود JavaScript من خلال أحداث تفاعلية متنوعة، مثل أحداث الماوس، وأحداث لوحة المفاتيح، ومشغلات الساعة، وما إلى ذلك. للحصول على شرح مفصل، يرجى الرجوع إلى الفصل 14.
1.5 ترتيب تنفيذ البرامج النصية لمخرجات JavaScript
في تطوير JavaScript، غالبًا ما يتم استخدام طريقة write() لكائن المستند لإخراج نصوص JavaScript النصية. إذن كيف يتم تنفيذ نصوص الإخراج الديناميكية هذه؟ على سبيل المثال:
انسخ رمز الكود كما يلي:
document.write('<script type="text/javascript">');
document.write('f();');
document.write('وظيفة f(){');
document.write('تنبيه(1);');
document.write('}');
document.write('</script>');
بتشغيل الكود أعلاه، سنجد أن: الأسلوب document.write() يقوم أولاً بكتابة سلسلة البرنامج النصي الناتج إلى موقع المستند حيث يوجد البرنامج النصي، بعد تحليل محتوى المستند حيث يوجد document.write() يستمر المتصفح في تحليل محتوى الإخراج document.write ()، ثم تحليل مستندات HTML اللاحقة بالترتيب. بمعنى آخر، سيتم تنفيذ إخراج سلسلة التعليمات البرمجية بواسطة برنامج JavaScript النصي مباشرة بعد الإخراج.
يرجى ملاحظة أن إخراج سلسلة JavaScript النصية باستخدام طريقة document.write() يجب أن يتم وضعها في علامة <script> التي يتم إخراجها في نفس الوقت، وإلا فلن يتمكن مترجم JavaScript من التعرف على رموز JavaScript القانونية هذه وسيقوم بذلك سيتم عرضها كسلسلة عادية في مستند الصفحة. على سبيل المثال، ستعرض التعليمة البرمجية التالية تعليمات JavaScript البرمجية بدلاً من تنفيذها.
انسخ رمز الكود كما يلي:
document.write('f();');
document.write('وظيفة f(){');
document.write('تنبيه(1);');
document.write(');');
ومع ذلك، هناك بعض المخاطر في إخراج وتنفيذ البرامج النصية من خلال طريقة document.write()، لأن محركات JavaScript المختلفة تنفذها بترتيبات مختلفة، وقد تحدث أخطاء في متصفحات مختلفة أثناء التحليل.
Ø المشكلة 1: لا يمكن العثور على المتغيرات أو الوظائف المعلنة في ملف JavaScript الخارجي المستورد من خلال طريقة document.write(). على سبيل المثال، انظر إلى نموذج التعليمات البرمجية أدناه.
انسخ رمز الكود كما يلي:
document.write('<script type="text/javascript" src="//www.VeVB.COm/test.js">
</script>');
document.write('<script type="text/javascript">');
document.write('alert(n);'); // IE يطالب بعدم العثور على المتغير n
document.write('</script>');
تنبيه (n+1)؛ // ستطالب جميع المتصفحات بعدم العثور على المتغير n
رمز ملف JavaScript الخارجي (test.js) هو كما يلي:
انسخ رمز الكود كما يلي:
فار ن = 1;
عند الاختبار في متصفحات مختلفة، ستجد خطأ في بناء الجملة ولا يمكن العثور على المتغير n. بمعنى آخر، إذا قمت بالوصول في كتلة تعليمات برمجية لـ JavaScript إلى المتغيرات الموجودة في ملف JavaScript الخارجي المستورد في مخرجات البرنامج النصي بواسطة طريقة document.write() في كتلة التعليمات البرمجية هذه، فسيتم عرض خطأ في بناء الجملة. في الوقت نفسه، إذا كان في متصفح IE، ليس فقط في البرنامج النصي، ولكن أيضًا في البرنامج النصي للإخراج، فسيطالب بعدم العثور على متغير الإخراج المستورد في ملف JavaScript خارجي (التعبير طويل بعض الشيء ومعقد، يمكن للقراء الذين لا يفهمون محاولة تشغيل الكود أعلاه (يمكن فهمهم).
Ø السؤال 2: تحتوي محركات JavaScript المختلفة على أوامر تنفيذ مختلفة قليلاً لإخراج البرامج النصية للاستيراد الخارجي. على سبيل المثال، انظر إلى نموذج التعليمات البرمجية أدناه.
انسخ رمز الكود كما يلي:
<نوع البرنامج النصي = "نص/جافا سكريبت">
document.write('<script type="text/javascript" src="http://shaozhuqing.com/test1.js">
</script>');
document.write('<script type="text/javascript">');
document.write('تنبيه(2);')
document.write('alert(n+2);');
document.write('</script>');
</script>
<نوع البرنامج النصي = "نص/جافا سكريبت">
تنبيه(ن+3);
</script>
يظهر أدناه رمز ملف JavaScript الخارجي (test1.js).
انسخ رمز الكود كما يلي:
فار ن = 1؛
تنبيه (ن)؛
يظهر تسلسل التنفيذ في متصفح IE في الشكل 1-6.
الشكل 1-6 تسلسل التنفيذ وأخطاء بناء الجملة التي يطالب بها متصفح IE 7
يختلف ترتيب التنفيذ في المتصفحات التي تتوافق مع معايير DOM عن ذلك الموجود في متصفحات IE، ولا توجد أخطاء في بناء الجملة. يوضح الشكل 1-7 ترتيب التنفيذ في متصفح Firefox 3.0.
الشكل 1-7 ظهور تسلسل تنفيذ متصفح Firefox 3 وأخطاء في بناء الجملة
حل أوامر التنفيذ المختلفة للمتصفحات المختلفة والأخطاء المحتملة. يمكننا وضع جميع الملفات الخارجية المستوردة باستخدام البرنامج النصي للإخراج في كتل تعليمات برمجية مستقلة، بحيث يمكن تجنب هذه المشكلة وفقًا لترتيب تنفيذ كتل تعليمات JavaScript البرمجية المقدمة أعلاه. على سبيل المثال، بالنسبة للمثال أعلاه، يمكنك تصميمه على النحو التالي:
انسخ رمز الكود كما يلي:
<نوع البرنامج النصي = "نص/جافا سكريبت">
document.write('<script type="text/javascript" src="//www.VeVB.COm/test1.js"></script>');
</script>
<نوع البرنامج النصي = "نص/جافا سكريبت">
document.write('<script type="text/javascript">');
document.write('alert(2);') ; // النصيحة 2
document.write('alert(n+2);'); // النصيحة 3
document.write('</script>');
تنبيه (ن + 3)؛ // نصيحة 4
</script>
<نوع البرنامج النصي = "نص/جافا سكريبت">
تنبيه (ن + 4)؛ // نصيحة 5
</script>
بهذه الطريقة، يمكن تنفيذ التعليمات البرمجية أعلاه بالترتيب في متصفحات مختلفة، ويكون ترتيب الإخراج هو 1 و2 و3 و4 و5. سبب المشكلة هو: وجود تناقض بين البرنامج النصي المستورد للإخراج وكتلة كود JavaScript الحالية. إذا تم الإخراج بشكل منفصل، فلن يكون هناك أي صراع.