ما هي نتيجة البرنامج التالي؟
انسخ رمز الكود كما يلي:
فار فو = 1;
شريط الوظائف () {
إذا (! فو) {
فار فو = 10؛
}
تنبيه (فو)؛
}
حاجِز()؛
والنتيجة هي 10؛
ماذا عن هذا؟
انسخ رمز الكود كما يلي:
فار أ = 1؛
الدالة ب () {
أ = 10؛
يعود؛
الدالة أ () {}
}
ب()؛
تنبيه (أ)؛
والنتيجة هي 1.
هل يخيفك؟ ماذا حدث؟ قد يكون هذا غريبًا وخطيرًا ومربكًا، ولكنه أيضًا في الواقع ميزة مفيدة جدًا ومثيرة للإعجاب في لغة JavaScript. لا أعرف إذا كان هناك اسم قياسي لهذا السلوك، لكني أحب هذا المصطلح: "الرفع". ستقدم هذه المقالة شرحًا تمهيديًا لهذه الآلية، لكن دعونا أولاً نحصل على بعض الفهم الضروري لنطاق JavaScript.
نطاق جافا سكريبت
بالنسبة للمبتدئين في جافا سكريبت، يعد النطاق أحد أكثر المجالات إرباكًا؛ في الواقع، لا يقتصر الأمر على المبتدئين فقط. لقد التقيت ببعض مبرمجي JavaScript ذوي الخبرة، لكنهم لا يفهمون النطاق بعمق. السبب وراء إرباك نطاق JavaScript هو أن بناء جملة البرنامج نفسه يشبه لغة عائلة C، مثل برنامج C التالي:
انسخ رمز الكود كما يلي:
#تشمل <stdio.h>
إنت الرئيسي () {
كثافة العمليات س = 1؛
printf("%d, ", x);
إذا (1) {
كثافة العمليات س = 2؛
printf("%d, ", x);
}
printf("%d/n", x);
}
نتيجة الإخراج هي 1 2 1. وذلك لأن لغات عائلة C لها نطاق كتلة. عندما يدخل التحكم في البرنامج إلى كتلة، مثل كتلة if، يمكن الإعلان عن المتغيرات التي تؤثر فقط على الكتلة دون التأثير على التأثيرات خارج الكتلة. اِختِصاص. ولكن في جافا سكريبت، هذا لا يعمل. ألق نظرة على الكود التالي:
انسخ رمز الكود كما يلي:
فار س = 1;
console.log(x);
إذا (صحيح) {
فار س = 2;
console.log(x);
}
console.log(x);
ستكون النتيجة 1 2 2. لأن جافا سكريبت هو نطاق الوظيفة. هذا هو الاختلاف الأكبر عن عائلة لغات C. إذا كان في هذا البرنامج لا ينشئ نطاقًا جديدًا.
بالنسبة للعديد من مبرمجي C وC++ وJava، هذا ليس ما يتوقعونه ويرحبون به. لحسن الحظ، نظرًا لمرونة وظائف JavaScript، توجد طرق للتغلب على هذه المشكلة. إذا كان يجب عليك إنشاء نطاق مؤقت، فافعل شيئًا كهذا:
انسخ رمز الكود كما يلي:
وظيفة فو () {
فار س = 1;
إذا (س) {
(وظيفة () {
فار س = 2;
// بعض التعليمات البرمجية الأخرى
}());
}
// x لا يزال 1.
}
هذه الطريقة مرنة ويمكن استخدامها في أي مكان تريد إنشاء نطاق مؤقت فيه. ليس فقط داخل الكتلة. ومع ذلك، أوصي بشدة أن تأخذ الوقت الكافي لفهم نطاق JavaScript. إنها مفيدة جدًا وواحدة من ميزات JavaScript المفضلة لدي. إذا فهمت النطاق، فإن الرفع المتغير سيكون أكثر منطقية بالنسبة لك.
إعلان المتغير والتسمية والترويج
في JavaScript، هناك أربع طرق أساسية للمتغيرات للدخول إلى النطاق:
•1 لغة مدمجة: تحتوي جميع النطاقات على هذه اللغة والوسائط (ملاحظة المترجم: بعد الاختبار، لا تكون الوسائط مرئية في النطاق العام)
•2 المعلمات الرسمية: ستكون المعلمات الرسمية للوظيفة جزءًا من نطاق نص الوظيفة؛
•3 إعلان الدالة: مثل هذا النموذج: function foo(){};
•4 إعلان المتغير: هكذا: var foo;
يتم دائمًا "رفع" إعلانات الوظائف والإعلانات المتغيرة بهدوء إلى أعلى نص الطريقة بواسطة المترجم. يعني كود كالتالي
انسخ رمز الكود كما يلي:
وظيفة فو () {
حاجِز()؛
فار س = 1;
}
سيتم تفسيرها في الواقع على النحو التالي:
انسخ رمز الكود كما يلي:
وظيفة فو () {
فار س؛
حاجِز()؛
س = 1؛
}
بغض النظر عما إذا كان من الممكن تنفيذ الكتلة التي تم تعريف المتغير فيها. الوظيفتان التاليتان هما في الواقع نفس الشيء:
انسخ رمز الكود كما يلي:
وظيفة فو () {
إذا (خطأ) {
فار س = 1;
}
يعود؛
فار ص = 1;
}
وظيفة فو () {
فار س، ص؛
إذا (خطأ) {
س = 1؛
}
يعود؛
ص = 1؛
}
لاحظ أن تعيينات المتغيرات لا يتم رفعها، بل مجرد إعلانات. ومع ذلك، فإن إعلان الوظيفة مختلف قليلاً، ويتم أيضًا ترقية نص الوظيفة. لكن يرجى ملاحظة أن هناك طريقتين للإعلان عن دالة:
انسخ رمز الكود كما يلي:
اختبار الوظيفة () {
foo(); // TypeError "foo ليس دالة"
شريط ()؛ // "سيتم تشغيل هذا!"
var foo = function () { // نقاط متغيرة للتعبير عن الوظيفة
تنبيه("هذا لن يعمل!");
}
function bar() { // وظيفة إعلان الوظيفة المسماة bar
تنبيه ("سيتم تشغيل هذا!")؛
}
}
امتحان()؛
في هذا المثال، يتم رفع الإعلانات الوظيفية فقط مع نص الوظيفة. سيتم رفع إعلان foo، ولكن لن يتم تعيين نص الوظيفة الذي يشير إليه إلا أثناء التنفيذ.
ما ورد أعلاه يغطي بعض أساسيات التعزيز، ولا يبدو أنها مربكة إلى هذا الحد. ومع ذلك، في بعض السيناريوهات الخاصة، لا يزال هناك درجة معينة من التعقيد.
ترتيب التحليل المتغير
أهم شيء يجب أخذه في الاعتبار هو ترتيب الدقة المتغير. هل تتذكر الطرق الأربع التي تدخل بها التسمية إلى النطاق الذي قدمته سابقًا؟ الترتيب الذي يتم به تحليل المتغيرات هو الترتيب الذي أدرجته بها.
انسخ رمز الكود كما يلي:
<النص البرمجي>
وظيفة أ () {
}
فار أ ؛
تنبيه (أ)؛ // اطبع النص الوظيفي لـ a
</script>
<النص البرمجي>
فار أ ؛
وظيفة (){
}
تنبيه (أ)؛ // اطبع النص الوظيفي لـ a
</script>
// لكن انتبه إلى الفرق بين طريقتي الكتابة التاليتين:
<النص البرمجي>
فار أ=1;
وظيفة (){
}
تنبيه (أ)؛ // اطبع 1
</script>
<النص البرمجي>
وظيفة (){
}
فار أ=1;
تنبيه (أ)؛ // اطبع 1
</script>
هناك 3 استثناءات هنا:
1 تعمل وسيطات الاسم المضمنة بشكل غريب، ويبدو أنه يجب الإعلان عنها بعد المعلمات الرسمية للوظيفة، ولكن قبل إعلان الوظيفة. هذا يعني أنه إذا كانت هناك وسيطات في المعلمة الرسمية، فستكون لها الأولوية على المعلمة المضمنة. هذه ميزة سيئة للغاية، لذا تجنب استخدام الوسائط في المعلمات الرسمية؛
2 سيؤدي تحديد هذا المتغير في أي مكان إلى حدوث خطأ في بناء الجملة، وهي ميزة جيدة؛
3. إذا كانت هناك معلمات رسمية متعددة لها نفس الاسم، فإن المعلمة الأخيرة لها الأولوية، حتى لو كانت قيمتها غير محددة أثناء التشغيل الفعلي؛
وظيفة مسماة
يمكنك إعطاء وظيفة اسمًا. إذا كان الأمر كذلك، فهو ليس إعلان وظيفة، ولن يتم ترقية اسم الوظيفة المحددة (إن وجد، مثل البريد العشوائي أدناه، ملاحظة المترجم) في تعريف نص الوظيفة، ولكن سيتم تجاهله. إليك بعض التعليمات البرمجية لمساعدتك على فهم:
انسخ رمز الكود كما يلي:
foo(); // TypeError "foo ليس دالة"
شريط ()؛ // صالح
baz(); // TypeError "baz ليس دالة"
البريد العشوائي () ؛ // خطأ مرجعي "لم يتم تعريف البريد العشوائي"
var foo = function () {}; يشير // foo إلى دالة مجهولة
شريط الوظيفة () {}؛
var baz = function spam() {}; // وظيفة مُسمّاة، يتم ترقية baz فقط، ولن يتم الترويج للبريد العشوائي.
فو ()؛ // صالح
شريط ()؛ // صالح
باز ()؛ // صالح
البريد العشوائي () ؛ // خطأ مرجعي "لم يتم تعريف البريد العشوائي"
كيفية كتابة الكود
الآن بعد أن فهمت تحديد النطاق والرفع المتغير، ماذا يعني ذلك بالنسبة لتشفير JavaScript؟ الشيء الأكثر أهمية هو تحديد المتغيرات الخاصة بك دائمًا باستخدام var. وأوصي بشدة أنه بالنسبة للاسم، يجب أن يكون هناك دائمًا إعلان var واحد فقط في النطاق. إذا قمت بذلك، فلن تواجه مشكلات في رفع النطاق والمتغير.
ماذا تقصد بمواصفات اللغة؟
أجد الوثائق المرجعية لـ ECMAScript مفيدة دائمًا. إليك ما وجدته حول النطاق والرفع المتغير:
إذا تم الإعلان عن متغير في فئة نص دالة، فهو نطاق الوظيفة. وبخلاف ذلك، فهو ذو نطاق عالمي (كخاصية عالمية). سيتم إنشاء المتغيرات عندما يدخل التنفيذ إلى النطاق. لن تقوم الكتل بتعريف نطاقات جديدة، فقط الإعلانات والإجراءات الخاصة بالوظيفة (يعتقد المترجم أنها تنفيذ كود عالمي) هي التي ستنشئ نطاقات جديدة. تتم تهيئة المتغيرات إلى غير محددة عند إنشائها. إذا كانت هناك عملية إسناد في بيان تعريف المتغير، فلن تحدث عملية الإسناد إلا عند تنفيذها، وليس عند إنشائها.
آمل أن تجلب هذه المقالة بصيصًا من الضوء للمبرمجين الذين يشعرون بالارتباك بشأن جافا سكريبت. كما أنني أبذل قصارى جهدي لتجنب التسبب في المزيد من الارتباك. إذا قلت شيئًا خاطئًا أو تجاهلت شيئًا، فيرجى إبلاغي بذلك.
ملحق المترجم
ذكرني أحد الأصدقاء بمشكلة الترويج للوظائف المسماة في النطاق العالمي ضمن IE:
وهذه هي الطريقة التي اختبرتها عندما قمت بترجمة المقال:
انسخ رمز الكود كما يلي:
<النص البرمجي>
دالة () {
رسائل إلكترونية مزعجة()؛
var baz = function spam() {alert('هذا بريد عشوائي')};
}
ر ()؛
</script>
طريقة الكتابة هذه، أي الترويج للوظائف المسماة في النطاق غير العام، لها نفس الأداء ضمن ie وff.
انسخ رمز الكود كما يلي:
<النص البرمجي>
رسائل إلكترونية مزعجة()؛
var baz = function spam() {alert('هذا بريد عشوائي')};
</script>
ومن ثم يمكن تنفيذ البريد العشوائي ضمن أي، ولكن ليس ضمن ff. وهذا يوضح أن المتصفحات المختلفة تتعامل مع هذه التفاصيل بشكل مختلف.
قادني هذا السؤال أيضًا إلى التفكير في سؤالين آخرين: 1: بالنسبة للمتغيرات ذات النطاق العام، هناك فرق بين var وnon-var، ولن يتم ترقية المتغير. على سبيل المثال، من بين البرنامجين التاليين، سيبلغ البرنامج الثاني عن خطأ:
انسخ رمز الكود كما يلي:
<النص البرمجي>
تنبيه (أ)؛
فار أ=1;
</script>
انسخ رمز الكود كما يلي:
<النص البرمجي>
تنبيه (أ)؛
أ=1;
</script>
2: لن يتم ترقية المتغيرات المحلية التي تم إنشاؤها في التقييم (ليس هناك طريقة للقيام بذلك).
انسخ رمز الكود كما يلي:
<النص البرمجي>
فار أ = 1؛
وظيفة ر () {
تنبيه (أ)؛
إيفال('فار أ = 2');
تنبيه (أ)؛
}
ر ()؛
تنبيه (أ)؛
</script>