JavaScript هي لغة موجهة نحو الوظائف للغاية. إنه يمنحنا الكثير من الحرية. يمكن إنشاء دالة في أي لحظة، وتمريرها كوسيطة إلى دالة أخرى، ثم استدعاؤها من مكان مختلف تمامًا من التعليمات البرمجية لاحقًا.
نحن نعلم بالفعل أن الوظيفة يمكنها الوصول إلى المتغيرات خارجها (المتغيرات "الخارجية").
ولكن ماذا يحدث إذا تغيرت المتغيرات الخارجية منذ إنشاء الدالة؟ هل ستحصل الدالة على قيم أحدث أم القيم القديمة؟
وماذا لو تم تمرير دالة كوسيطة واستدعائها من مكان آخر من التعليمات البرمجية، فهل ستتمكن من الوصول إلى المتغيرات الخارجية في المكان الجديد؟
دعونا نوسع معرفتنا لفهم هذه السيناريوهات والسيناريوهات الأكثر تعقيدًا.
سنتحدث عن متغيرات let/const
هنا
في JavaScript، هناك ثلاث طرق للإعلان عن متغير: let
، const
(المتغيرات الحديثة)، و var
(بقايا الماضي).
في هذه المقالة سوف نستخدم متغيرات let
في الأمثلة.
المتغيرات التي تم الإعلان عنها باستخدام const
تتصرف بنفس الطريقة، لذا فإن هذه المقالة تتحدث عن const
أيضًا.
يحتوي var
القديم على بعض الاختلافات الملحوظة، وسيتم تناولها في مقالة "var" القديمة.
إذا تم الإعلان عن متغير داخل كتلة التعليمات البرمجية {...}
، فسيكون مرئيًا فقط داخل تلك الكتلة.
على سبيل المثال:
{ // قم ببعض المهام باستخدام المتغيرات المحلية التي لا ينبغي رؤيتها بالخارج دع الرسالة = "مرحبا"؛ // مرئي فقط في هذه الكتلة تنبيه (رسالة)؛ // مرحبًا } تنبيه (رسالة)؛ // خطأ: لم يتم تعريف الرسالة
يمكننا استخدام هذا لعزل جزء من التعليمات البرمجية الذي يقوم بمهمته الخاصة، مع المتغيرات التي تنتمي إليه فقط:
{ // إظهار الرسالة دع الرسالة = "مرحبا"؛ تنبيه (رسالة)؛ } { // أظهر رسالة أخرى دع الرسالة = "وداعا"؛ تنبيه (رسالة)؛ }
سيكون هناك خطأ بدون كتل
يرجى ملاحظة أنه بدون كتل منفصلة سيكون هناك خطأ، إذا let
اسم المتغير الموجود:
// إظهار الرسالة دع الرسالة = "مرحبا"؛ تنبيه (رسالة)؛ // أظهر رسالة أخرى دع الرسالة = "وداعا"؛ // خطأ: تم الإعلان عن المتغير بالفعل تنبيه (رسالة)؛
بالنسبة إلى if
و for
while
وما إلى ذلك، فإن المتغيرات المعلنة في {...}
تكون أيضًا مرئية فقط داخل:
إذا (صحيح) { دع العبارة = "مرحبًا!"; تنبيه(عبارة); // مرحبًا! } تنبيه(عبارة); // خطأ، لا يوجد مثل هذا المتغير!
هنا، بعد if
، لن يرى alert
أدناه phrase
، ومن هنا الخطأ.
هذا رائع، لأنه يسمح لنا بإنشاء متغيرات محلية خاصة بفرع if
.
الشيء نفسه ينطبق على حلقات for
و while
:
لـ (دع i = 0; i < 3; i++) { // المتغير i يكون مرئيًا فقط داخل هذا for تنبيه (ط)؛ // 0، ثم 1، ثم 2 } تنبيه (ط)؛ // خطأ، لا يوجد مثل هذا المتغير
بصريًا، let i
خارج {...}
. لكن بناء for
خاص هنا: المتغير المعلن بداخله يعتبر جزءًا من الكتلة.
تسمى الوظيفة "متداخلة" عندما يتم إنشاؤها داخل وظيفة أخرى.
من الممكن بسهولة القيام بذلك باستخدام JavaScript.
يمكننا استخدامه لتنظيم الكود الخاص بنا، مثل هذا:
وظيفة sayHiBye(الاسم الأول، الاسم الأخير) { // دالة متداخلة مساعدة لاستخدامها أدناه الدالة getFullName() { إرجاع الاسم الأول + "" + الاسم الأخير؛ } تنبيه ("مرحبًا،" + getFullName() ); تنبيه ("وداعا،" + getFullName() ); }
هنا تم إنشاء الدالة المتداخلة getFullName()
من أجل الراحة. يمكنه الوصول إلى المتغيرات الخارجية وبالتالي يمكنه إرجاع الاسم الكامل. الوظائف المتداخلة شائعة جدًا في JavaScript.
والأمر الأكثر إثارة للاهتمام هو أنه يمكن إرجاع دالة متداخلة: إما كخاصية لكائن جديد أو كنتيجة لوحدها. ويمكن بعد ذلك استخدامه في مكان آخر. بغض النظر عن المكان، فإنه لا يزال لديه إمكانية الوصول إلى نفس المتغيرات الخارجية.
أدناه، يقوم makeCounter
بإنشاء وظيفة "counter" التي تُرجع الرقم التالي في كل استدعاء:
وظيفة ماك كونتر () { دع العد = 0؛ وظيفة العودة () { عدد الإرجاع++; }; } Let counter = makeCounter(); تنبيه (عداد ())؛ // 0 تنبيه (عداد ())؛ // 1 تنبيه (عداد ())؛ // 2
على الرغم من كونها بسيطة، إلا أن المتغيرات المعدلة قليلاً من هذا الرمز لها استخدامات عملية، على سبيل المثال، كمولد أرقام عشوائية لتوليد قيم عشوائية للاختبارات الآلية.
كيف يعمل هذا؟ إذا أنشأنا عدة عدادات، فهل ستكون مستقلة؟ ما الذي يحدث مع المتغيرات هنا؟
يعد فهم مثل هذه الأشياء أمرًا رائعًا للمعرفة الشاملة بجافا سكريبت ومفيدًا للسيناريوهات الأكثر تعقيدًا. لذلك دعونا نتعمق قليلاً.
هنا يكون التنين!
التفسير الفني المتعمق ينتظرنا.
بقدر ما أرغب في تجنب التفاصيل اللغوية منخفضة المستوى، فإن أي فهم بدونها سيكون ناقصًا وغير مكتمل، لذا كن مستعدًا.
وللتوضيح، يتم تقسيم الشرح إلى خطوات متعددة.
في JavaScript، تحتوي كل وظيفة قيد التشغيل، وكتلة التعليمات البرمجية {...}
، والبرنامج النصي ككل على كائن داخلي (مخفي) مرتبط يعرف باسم البيئة المعجمية .
يتكون كائن البيئة المعجمية من جزأين:
سجل البيئة – كائن يقوم بتخزين كافة المتغيرات المحلية كخصائص له (وبعض المعلومات الأخرى مثل قيمة this
).
إشارة إلى البيئة المعجمية الخارجية ، تلك المرتبطة بالشفرة الخارجية.
"المتغير" هو مجرد خاصية للكائن الداخلي الخاص، Environment Record
. "الحصول على متغير أو تغييره" يعني "الحصول على خاصية ذلك الكائن أو تغييرها".
في هذا الكود البسيط بدون وظائف، هناك بيئة معجمية واحدة فقط:
هذا هو ما يسمى بالبيئة المعجمية العالمية ، المرتبطة بالنص بأكمله.
في الصورة أعلاه، المستطيل يعني سجل البيئة (مخزن متغير) والسهم يعني المرجع الخارجي. البيئة المعجمية العالمية ليس لها مرجع خارجي، ولهذا السبب يشير السهم إلى null
.
مع بدء تنفيذ التعليمات البرمجية واستمرارها، تتغير البيئة المعجمية.
إليك رمزًا أطول قليلاً:
توضح المستطيلات الموجودة على الجانب الأيمن كيف تتغير البيئة المعجمية العالمية أثناء التنفيذ:
عند بدء تشغيل البرنامج النصي، يتم ملء البيئة المعجمية مسبقًا بجميع المتغيرات المعلنة.
في البداية، هم في حالة "غير مهيأة". هذه حالة داخلية خاصة، مما يعني أن المحرك يعرف شيئًا عن المتغير، لكن لا يمكن الرجوع إليه حتى يتم الإعلان عنه باستخدام let
. إنه تقريبًا كما لو أن المتغير غير موجود.
ثم let phrase
يظهر. لا توجد مهمة بعد، لذا فإن قيمتها undefined
. يمكننا استخدام المتغير من هذه النقطة فصاعدًا.
يتم تعيين قيمة phrase
.
phrase
تغير القيمة.
كل شيء يبدو بسيطًا في الوقت الحالي، أليس كذلك؟
المتغير هو خاصية لكائن داخلي خاص، مرتبط بالكتل/الوظيفة/البرنامج النصي التي يتم تنفيذها حاليًا.
إن العمل مع المتغيرات هو في الواقع العمل مع خصائص ذلك الكائن.
البيئة المعجمية هي كائن المواصفات
"البيئة المعجمية" هي كائن مواصفات: فهي موجودة فقط "نظريًا" في مواصفات اللغة لوصف كيفية عمل الأشياء. لا يمكننا إدخال هذا الكائن في الكود الخاص بنا والتعامل معه مباشرة.
قد تقوم محركات JavaScript أيضًا بتحسينها، وتجاهل المتغيرات غير المستخدمة لحفظ الذاكرة وتنفيذ حيل داخلية أخرى، طالما بقي السلوك المرئي كما هو موضح.
الدالة هي أيضًا قيمة، مثل المتغير.
والفرق هو أنه تتم تهيئة إعلان الوظيفة بشكل كامل على الفور.
عندما يتم إنشاء بيئة معجمية، يصبح إعلان الدالة على الفور دالة جاهزة للاستخدام (على عكس let
، فهي غير قابلة للاستخدام حتى الإعلان).
لهذا السبب يمكننا استخدام دالة، مُعلنة كإعلان دالة، حتى قبل الإعلان نفسه.
على سبيل المثال، هذه هي الحالة الأولية للبيئة المعجمية العالمية عندما نضيف دالة:
وبطبيعة الحال، ينطبق هذا السلوك فقط على تصريحات الدالة، وليس على تعبيرات الدالة حيث نقوم بتعيين دالة لمتغير، مثل let say = function(name)...
.
عند تشغيل دالة، في بداية المكالمة، يتم إنشاء بيئة معجمية جديدة تلقائيًا لتخزين المتغيرات المحلية ومعلمات المكالمة.
على سبيل المثال، بالنسبة لـ say("John")
، يبدو الأمر كما يلي (يكون التنفيذ على السطر، مُسمى بسهم):
أثناء استدعاء الوظيفة لدينا بيئتان معجميتان: البيئة الداخلية (لاستدعاء الوظيفة) والبيئة الخارجية (العالمية):
تتوافق البيئة المعجمية الداخلية مع التنفيذ الحالي لـ say
. لها خاصية واحدة: name
، وسيطة الوظيفة. لقد أطلقنا على say("John")
لذا فإن قيمة name
هي "John"
.
البيئة المعجمية الخارجية هي البيئة المعجمية العالمية. أنه يحتوي على متغير phrase
والوظيفة نفسها.
البيئة المعجمية الداخلية لديها إشارة إلى البيئة outer
.
عندما يريد الكود الوصول إلى متغير - يتم البحث في البيئة المعجمية الداخلية أولاً، ثم الخارجية، ثم الخارجية وهكذا حتى البيئة العالمية.
إذا لم يتم العثور على متغير في أي مكان، فهذا خطأ في الوضع الصارم (بدون use strict
، يؤدي تعيين متغير غير موجود إلى إنشاء متغير عام جديد، للتوافق مع الكود القديم).
في هذا المثال، تتم عملية البحث على النحو التالي:
بالنسبة لمتغير name
، فإن alert
say
بداخله يجده على الفور في البيئة المعجمية الداخلية.
عندما يريد الوصول إلى phrase
، فلا توجد phrase
محليًا، لذا فهو يتبع الإشارة إلى البيئة المعجمية الخارجية ويجدها هناك.
دعنا نعود إلى مثال makeCounter
.
وظيفة ماك كونتر () { دع العد = 0؛ وظيفة العودة () { عدد الإرجاع++; }; } Let counter = makeCounter();
في بداية كل استدعاء makeCounter()
، يتم إنشاء كائن Lexical Environment جديد لتخزين المتغيرات لتشغيل makeCounter
هذا.
إذن لدينا بيئتان معجميتان متداخلتان، تمامًا كما في المثال أعلاه:
الأمر المختلف هو أنه أثناء تنفيذ makeCounter()
، يتم إنشاء دالة متداخلة صغيرة من سطر واحد فقط: return count++
. نحن لم نقم بتشغيله بعد، فقط قم بإنشائه.
تتذكر جميع الوظائف البيئة المعجمية التي تم إنشاؤها فيها. من الناحية الفنية، لا يوجد سحر هنا: جميع الوظائف لها خاصية مخفية تسمى [[Environment]]
، والتي تحافظ على الإشارة إلى البيئة المعجمية حيث تم إنشاء الوظيفة:
إذن، counter.[[Environment]]
لديه إشارة إلى {count: 0}
البيئة المعجمية. هذه هي الطريقة التي تتذكر بها الوظيفة مكان إنشائها، بغض النظر عن مكان استدعائها. يتم تعيين مرجع [[Environment]]
مرة واحدة وإلى الأبد في وقت إنشاء الوظيفة.
لاحقًا، عندما يتم استدعاء counter()
، يتم إنشاء بيئة معجمية جديدة للاستدعاء، ويتم أخذ مرجع البيئة المعجمية الخارجي الخاص بها من counter.[[Environment]]
:
الآن عندما يبحث الكود الموجود داخل counter()
عن متغير count
، فإنه يبحث أولاً في البيئة المعجمية الخاصة به (فارغة، حيث لا توجد متغيرات محلية هناك)، ثم البيئة المعجمية لاستدعاء makeCounter()
الخارجي، حيث يجده ويغيره .
يتم تحديث المتغير في البيئة المعجمية التي يعيش فيها.
وهذه هي الحالة بعد الإعدام:
إذا قمنا باستدعاء counter()
عدة مرات، فسيتم زيادة المتغير count
إلى 2
، 3
وهكذا، في نفس المكان.
إنهاء
هناك مصطلح برمجي عام "الإغلاق" يجب على المطورين معرفته بشكل عام.
الإغلاق هو دالة تتذكر متغيراتها الخارجية ويمكنها الوصول إليها. في بعض اللغات، هذا غير ممكن، أو يجب كتابة الوظيفة بطريقة خاصة لتحقيق ذلك. ولكن كما هو موضح أعلاه، في JavaScript، جميع الوظائف هي عمليات إغلاق بشكل طبيعي (هناك استثناء واحد فقط، سيتم تغطيته في بناء جملة "الوظيفة الجديدة").
أي: يتذكرون تلقائيًا مكان إنشائهم باستخدام خاصية [[Environment]]
المخفية، ومن ثم يمكن للتعليمات البرمجية الخاصة بهم الوصول إلى المتغيرات الخارجية.
عند إجراء مقابلة، يتلقى مطور الواجهة الأمامية سؤالاً حول "ما هو الإغلاق؟"، ستكون الإجابة الصحيحة هي تعريف الإغلاق وشرح أن جميع الوظائف في JavaScript هي عمليات إغلاق، وربما بضع كلمات أخرى حول التفاصيل الفنية: خاصية [[Environment]]
وكيفية عمل البيئات المعجمية.
عادة، تتم إزالة البيئة المعجمية من الذاكرة مع كافة المتغيرات بعد انتهاء استدعاء الدالة. وذلك لأنه لا توجد مراجع لذلك. مثل أي كائن JavaScript، يتم الاحتفاظ به في الذاكرة فقط عندما يكون من الممكن الوصول إليه.
ومع ذلك، إذا كانت هناك وظيفة متداخلة لا يزال من الممكن الوصول إليها بعد انتهاء الوظيفة، فهي تحتوي على خاصية [[Environment]]
تشير إلى البيئة المعجمية.
في هذه الحالة، تظل البيئة المعجمية قابلة للوصول حتى بعد اكتمال الوظيفة، لذلك تظل حية.
على سبيل المثال:
الدالة و() { دع القيمة = 123؛ وظيفة العودة () { تنبيه (قيمة)؛ } } دع ز = و()؛ // g.[[البيئة]] يخزن مرجعًا للبيئة المعجمية // لاستدعاء f() المقابل
يرجى ملاحظة أنه إذا تم استدعاء f()
عدة مرات، وتم حفظ الوظائف الناتجة، فسيتم أيضًا الاحتفاظ بجميع كائنات البيئة المعجمية المقابلة في الذاكرة. في الكود أدناه، كل منهم 3:
الدالة و() { Let value = Math.random(); وظيفة الإرجاع () { تنبيه (قيمة)؛ }; } // 3 وظائف في المصفوفة، كل واحدة منها ترتبط بالبيئة المعجمية // من تشغيل f () المقابل Let arr = [f(), f(), f()];
يموت كائن البيئة المعجمية عندما يصبح غير قابل للوصول (تمامًا مثل أي كائن آخر). بمعنى آخر، فهو موجود فقط عندما تكون هناك دالة متداخلة واحدة على الأقل تشير إليه.
في الكود أدناه، بعد إزالة الوظيفة المتداخلة، يتم تنظيف البيئة المعجمية المحيطة بها (وبالتالي value
) من الذاكرة:
الدالة و() { دع القيمة = 123؛ وظيفة العودة () { تنبيه (قيمة)؛ } } دع ز = و()؛ // أثناء وجود الدالة g، تبقى القيمة في الذاكرة ز = فارغة؛ // ... والآن تم تنظيف الذاكرة
كما رأينا، من الناحية النظرية، عندما تكون الدالة حية، يتم أيضًا الاحتفاظ بجميع المتغيرات الخارجية.
لكن من الناحية العملية، تحاول محركات JavaScript تحسين ذلك. يقومون بتحليل استخدام المتغير وإذا كان من الواضح من الكود أنه لم يتم استخدام متغير خارجي - فسيتم إزالته.
أحد الآثار الجانبية المهمة في V8 (Chrome وEdge وOpera) هو أن هذا المتغير لن يكون متاحًا في تصحيح الأخطاء.
حاول تشغيل المثال أدناه في Chrome مع فتح أدوات المطور.
عندما يتوقف مؤقتًا، في وحدة التحكم، اكتب alert(value)
.
الدالة و() { Let value = Math.random(); الدالة ز () { مصحح الأخطاء؛ // في وحدة التحكم: اكتب تنبيه (قيمة)؛ لا يوجد مثل هذا المتغير! } العودة ز؛ } دع ز = و()؛ ز ()؛
كما ترون – لا يوجد مثل هذا المتغير! من الناحية النظرية، ينبغي أن يكون الوصول إليه متاحًا، لكن المحرك قام بتحسينه.
قد يؤدي ذلك إلى مشكلات تصحيح مضحكة (إن لم تكن مستهلكة للوقت). إحداها – يمكننا رؤية متغير خارجي يحمل نفس الاسم بدلاً من المتغير المتوقع:
Let value = "مفاجأة!"; الدالة و() { Let value = "أقرب قيمة"; الدالة ز () { مصحح الأخطاء؛ // في وحدة التحكم: اكتب تنبيه (قيمة)؛ مفاجأة! } العودة ز؛ } دع ز = و()؛ ز ()؛
من الجيد معرفة هذه الميزة في V8. إذا كنت تقوم بتصحيح الأخطاء باستخدام Chrome/Edge/Opera، فسوف تواجه ذلك عاجلاً أم آجلاً.
وهذا ليس خطأ في مصحح الأخطاء، ولكنه ميزة خاصة لـ V8. ربما سيتم تغييره في وقت ما. يمكنك دائمًا التحقق من ذلك عن طريق تشغيل الأمثلة الموجودة في هذه الصفحة.
الأهمية: 5
تستخدم الدالة sayHi اسم متغير خارجي. عند تشغيل الدالة، ما هي القيمة التي ستستخدمها؟
اسمحوا الاسم = "جون"؛ الدالة sayHi() { تنبيه ("مرحبًا،" + الاسم)؛ } الاسم = "بيت"؛ sayHi(); // ماذا سيظهر: "جون" أو "بيت"؟
مثل هذه المواقف شائعة في كل من التطوير من جانب المتصفح والخادم. قد تتم جدولة الوظيفة للتنفيذ في وقت لاحق من إنشائها، على سبيل المثال بعد إجراء المستخدم أو طلب الشبكة.
لذا فإن السؤال هو: هل يلتقط أحدث التغييرات؟
الجواب هو: بيت .
تحصل الدالة على متغيرات خارجية كما هي الآن، وتستخدم أحدث القيم.
لا يتم حفظ قيم المتغيرات القديمة في أي مكان. عندما تريد دالة متغيرًا، فإنها تأخذ القيمة الحالية من البيئة المعجمية الخاصة بها أو البيئة الخارجية.
الأهمية: 5
تقوم الدالة makeWorker
أدناه بإنشاء دالة أخرى وإرجاعها. يمكن استدعاء هذه الوظيفة الجديدة من مكان آخر.
هل سيكون بإمكانه الوصول إلى المتغيرات الخارجية من مكان إنشائه أو مكان الاستدعاء أو كليهما؟
وظيفة ميك ووركر () { اسمحوا الاسم = "بيت"؛ وظيفة العودة () { تنبيه (الاسم)؛ }; } اسمحوا الاسم = "جون"؛ // إنشاء دالة دع العمل = makeWorker(); // نسميها عمل()؛ // ماذا سيظهر؟
القيمة التي سوف تظهر؟ "بيت" أم "جون"؟
الجواب هو: بيت .
تحصل وظيفة work()
في الكود أدناه على name
من مكان أصلها من خلال مرجع البيئة المعجمية الخارجية:
لذا، النتيجة هي "Pete"
هنا.
لكن إذا لم يكن هناك let name
في makeWorker()
، فسيذهب البحث إلى الخارج ويأخذ المتغير العام كما نرى من السلسلة أعلاه. في هذه الحالة ستكون النتيجة "John"
.
الأهمية: 5
هنا نقوم بإنشاء عدادين: counter
و counter2
باستخدام نفس وظيفة makeCounter
.
هل هم مستقلون؟ ما هو العداد الثاني الذي سيظهر؟ 0,1
أو 2,3
أو أي شيء آخر؟
وظيفة ماك كونتر () { دع العد = 0؛ وظيفة العودة () { عدد الإرجاع++; }; } Let counter = makeCounter(); Let counter2 = makeCounter(); تنبيه (عداد ())؛ // 0 تنبيه (عداد ())؛ // 1 تنبيه (counter2 ())؛ // ؟ تنبيه (counter2 ())؛ // ؟
الجواب: 0,1.
يتم إنشاء الدوال counter
و counter2
من خلال استدعاءات مختلفة لـ makeCounter
.
لذلك لديهم بيئات معجمية خارجية مستقلة، كل واحدة لها count
الخاص.
الأهمية: 5
هنا يتم إنشاء كائن مضاد بمساعدة وظيفة المنشئ.
هل ستنجح؟ ماذا سوف تظهر؟
وظيفة العداد () { دع العد = 0؛ هذا.up = وظيفة () { إرجاع ++ عدد؛ }; هذا.down = وظيفة () { العودة --العد؛ }; } دع العداد = عداد جديد ()؛ تنبيه (counter.up ())؛ // ؟ تنبيه (counter.up ())؛ // ؟ تنبيه (counter.down ())؛ // ؟
بالتأكيد سوف تعمل على ما يرام.
يتم إنشاء كلا الدالتين المتداخلتين داخل نفس البيئة المعجمية الخارجية، لذا فهما يتشاركان في الوصول إلى نفس متغير count
:
وظيفة العداد () { دع العد = 0؛ هذا.up = وظيفة () { إرجاع ++ عدد؛ }; هذا.down = وظيفة () { العودة --العد؛ }; } دع العداد = عداد جديد ()؛ تنبيه (counter.up ())؛ // 1 تنبيه (counter.up ())؛ // 2 تنبيه (counter.down ())؛ // 1
الأهمية: 5
انظر إلى الكود. ماذا ستكون نتيجة المكالمة في السطر الأخير؟
دع العبارة = "مرحبا"؛ إذا (صحيح) { السماح للمستخدم = "جون"؛ الدالة sayHi() { تنبيه(`${phrase}, ${user}`); } } sayHi();
والنتيجة خطأ .
يتم الإعلان عن الدالة sayHi
داخل if
، لذا فهي تعيش بداخلها فقط. لا يوجد sayHi
في الخارج.
الأهمية: 4
اكتب sum
الدالة الذي يعمل على النحو التالي: sum(a)(b) = a+b
.
نعم، بهذه الطريقة تمامًا، باستخدام الأقواس المزدوجة (وليس خطأً في الكتابة).
على سبيل المثال:
مجموع(1)(2) = 3 مجموع(5)(-1) = 4
لكي يعمل القوس الثاني، يجب أن يُرجع القوس الأول دالة.
مثله:
مجموع الدالة (أ) { وظيفة العودة (ب) { العودة أ + ب؛ // يأخذ "أ" من البيئة المعجمية الخارجية }; } تنبيه(مجموع(1)(2)); // 3 تنبيه(مجموع(5)(-1)); // 4
الأهمية: 4
ماذا ستكون نتيجة هذا الكود؟
دع س = 1؛ وظيفة وظيفة () { console.log(x); // ؟ دع س = 2؛ } وظيفة();
ملاحظة: هناك مأزق في هذه المهمة. الحل ليس واضحا.
والنتيجة هي: خطأ .
حاول تشغيله:
دع س = 1؛ وظيفة وظيفة () { console.log(x); // خطأ مرجعي: لا يمكن الوصول إلى "x" قبل التهيئة دع س = 2؛ } وظيفة();
في هذا المثال يمكننا ملاحظة الفرق الغريب بين المتغير "غير الموجود" والمتغير "غير المهيأ".
كما قرأت في المقالة النطاق المتغير، الإغلاق، يبدأ المتغير في الحالة "غير المهيأة" من اللحظة التي يدخل فيها التنفيذ في كتلة التعليمات البرمجية (أو الوظيفة). ويبقى غير مهيأ حتى بيان let
المقابل.
بمعنى آخر، المتغير موجود تقنيًا، لكن لا يمكن استخدامه قبل let
.
الكود أعلاه يوضح ذلك.
وظيفة وظيفة () { // المتغير المحلي x معروف للمحرك منذ بداية الدالة، // ولكن "غير مهيأ" (غير قابل للاستخدام) حتى يسمح ("المنطقة الميتة") // ومن هنا الخطأ console.log(x); // خطأ مرجعي: لا يمكن الوصول إلى "x" قبل التهيئة دع س = 2؛ }
هذه المنطقة من عدم قابلية الاستخدام المؤقت للمتغير (من بداية كتلة التعليمات البرمجية حتى let
) تسمى أحيانًا "المنطقة الميتة".
الأهمية: 5
لدينا طريقة مدمجة arr.filter(f)
للمصفوفات. يقوم بتصفية كافة العناصر من خلال الدالة f
. إذا أعادت true
، فسيتم إرجاع هذا العنصر في المصفوفة الناتجة.
قم بإنشاء مجموعة من المرشحات "الجاهزة للاستخدام":
inBetween(a, b)
– بين a
و b
أو يساويهما (شاملاً).
inArray([...])
– في المصفوفة المحددة.
يجب أن يكون الاستخدام هكذا:
arr.filter(inBetween(3,6))
– يحدد فقط القيم بين 3 و6.
arr.filter(inArray([1,2,3]))
- يحدد فقط العناصر المتطابقة مع أحد أعضاء [1,2,3]
.
على سبيل المثال:
/* .. الكود الخاص بك لـ inBetween و inArray */ دع arr = [1, 2, 3, 4, 5, 6, 7]; تنبيه( arr.filter(inBetween(3, 6)) ); // 3,4,5,6 تنبيه( arr.filter(inArray([1, 2, 10])) ); // 1,2
افتح صندوق الرمل مع الاختبارات.
وظيفة بين (أ، ب) { وظيفة العودة (خ) { إرجاع x >= a && x <= b; }; } دع arr = [1, 2, 3, 4, 5, 6, 7]; تنبيه( arr.filter(inBetween(3, 6)) ); // 3,4,5,6
وظيفة في صفيف (arr) { وظيفة العودة (خ) { العودة arr.includes(x); }; } دع arr = [1, 2, 3, 4, 5, 6, 7]; تنبيه( arr.filter(inArray([1, 2, 10])) ); // 1,2
افتح الحل بالاختبارات في وضع الحماية.
الأهمية: 5
لدينا مجموعة من الكائنات لفرزها:
السماح للمستخدمين = [ { الاسم: "جون"، العمر: 20 عامًا، اللقب: "جونسون" }، { الاسم: "بيت"، العمر: 18 عامًا، اللقب: "بيترسون" }، { الاسم: "آن"، العمر: 19 عامًا، اللقب: "هاثاواي" } ];
الطريقة المعتادة للقيام بذلك ستكون:
// بالاسم (آن، جون، بيت) users.sort((a, b) => a.name > b.name ? 1 : -1); // حسب العمر (بيت، آن، جون) users.sort((a, b) => a.age > b.age ? 1 : -1);
هل يمكننا أن نجعلها أقل إسهابا، مثل هذا؟
users.sort(byField('name')); users.sort(byField('age'));
لذلك، بدلاً من كتابة دالة، فقط ضع byField(fieldName)
.
اكتب الدالة byField
التي يمكن استخدامها لذلك.
افتح صندوق الرمل مع الاختبارات.
الوظيفة حسب الحقل (اسم الحقل) { العودة (أ، ب) => أ[اسم الحقل] > ب[اسم الحقل]؟ 1 : -1; }
افتح الحل بالاختبارات في وضع الحماية.
الأهمية: 5
يقوم التعليمة البرمجية التالية بإنشاء مجموعة من shooters
.
تهدف كل وظيفة إلى إخراج رقمها. لكن هناك خطأ ما…
وظيفة ميك ارمي () { دع الرماة = []؛ دعني = 0؛ بينما (ط < 10) { دع مطلق النار = وظيفة () {// إنشاء وظيفة مطلق النار، تنبيه (ط)؛ // يجب أن يظهر رقمه }; shooters.push(shooter); // وإضافته إلى المصفوفة أنا++; } // ... وإرجاع مجموعة الرماة عودة الرماة. } Let army = makeArmy(); // يظهر جميع الرماة الرقم 10 بدلاً من أرقامهم 0، 1، 2، 3... الجيش[0](); // 10 من مطلق النار رقم 0 الجيش[1](); // 10 من مطلق النار رقم 1 الجيش[2](); // 10 ...وهكذا.
لماذا يظهر جميع الرماة نفس القيمة؟
قم بإصلاح الكود بحيث يعمل على النحو المنشود.
افتح صندوق الرمل مع الاختبارات.
دعونا نتفحص ما يحدث بالضبط داخل makeArmy
، وسيصبح الحل واضحًا.
يقوم بإنشاء مجموعة فارغة shooters
:
دع الرماة = []؛
يملأها بالوظائف عبر shooters.push(function)
في الحلقة.
كل عنصر عبارة عن دالة، وبالتالي فإن المصفوفة الناتجة تبدو كما يلي:
الرماة = [ وظيفة () { تنبيه (أنا)؛ }, وظيفة () { تنبيه (أنا)؛ }, وظيفة () { تنبيه (أنا)؛ }, وظيفة () { تنبيه (أنا)؛ }, وظيفة () { تنبيه (أنا)؛ }, وظيفة () { تنبيه (أنا)؛ }, وظيفة () { تنبيه (أنا)؛ }, وظيفة () { تنبيه (أنا)؛ }, وظيفة () { تنبيه (أنا)؛ }, وظيفة () { تنبيه (أنا)؛ } ];
يتم إرجاع المصفوفة من الدالة.
بعد ذلك، لاحقًا، سيتم استدعاء أي عضو، على سبيل المثال army[5]()
، للحصول على عنصر army[5]
من المصفوفة (وهي دالة) واستدعاءه.
الآن لماذا تظهر كل هذه الوظائف نفس القيمة، 10
؟
هذا لأنه لا يوجد متغير محلي i
داخل وظائف shooter
. عندما يتم استدعاء مثل هذه الوظيفة، فإنها تأخذ i
من بيئتها المعجمية الخارجية.
ثم ماذا ستكون قيمة i
؟
ولو نظرنا إلى المصدر:
وظيفة ميك ارمي () { ... دعني = 0؛ بينما (ط < 10) { دع مطلق النار = وظيفة () {// وظيفة مطلق النار تنبيه (ط)؛ // يجب أن يظهر رقمه }; shooters.push(shooter); // إضافة دالة إلى المصفوفة أنا++; } ... }
يمكننا أن نرى أن جميع وظائف shooter
يتم إنشاؤها في البيئة المعجمية لوظيفة makeArmy()
. ولكن عندما يتم استدعاء army[5]()
، يكون makeArmy
قد أنهى مهمته بالفعل، والقيمة النهائية لـ i
هي 10
( while
تتوقف عند i=10
).
ونتيجة لذلك، تحصل جميع وظائف shooter
على نفس القيمة من البيئة المعجمية الخارجية، وهي القيمة الأخيرة، i=10
.
كما ترون أعلاه، في كل تكرار للكتلة while {...}
، يتم إنشاء بيئة معجمية جديدة. لذا، لإصلاح ذلك، يمكننا نسخ قيمة i
إلى متغير داخل الكتلة while {...}
، مثل هذا:
وظيفة ميك ارمي () { دع الرماة = []؛ دعني = 0؛ بينما (ط < 10) { دع ي = أنا؛ دع مطلق النار = وظيفة () {// وظيفة مطلق النار تنبيه(ي); // يجب أن يظهر رقمه }; shooters.push(shooter); أنا++; } عودة الرماة. } Let army = makeArmy(); // الآن يعمل الكود بشكل صحيح الجيش[0](); // 0 الجيش[5](); // 5
هنا let j = i
يعلن عن متغير "التكرار المحلي" j
وينسخ i
فيه. يتم نسخ العناصر الأولية "حسب القيمة"، لذلك نحصل في الواقع على نسخة مستقلة من i
، تنتمي إلى تكرار الحلقة الحالي.
يعمل الرماة بشكل صحيح، لأن قيمة i
أصبحت الآن أقرب قليلاً. ليس في البيئة المعجمية makeArmy()
، ولكن في البيئة المعجمية التي تتوافق مع تكرار الحلقة الحالية:
ويمكن تجنب مثل هذه المشكلة أيضًا إذا for
في البداية، مثل هذا:
وظيفة ميك ارمي () { دع الرماة = []؛ من أجل (دع i = 0؛ i < 10؛ i++) { دع مطلق النار = وظيفة () {// وظيفة مطلق النار تنبيه (ط)؛ // يجب أن يظهر رقمه }; shooters.push(shooter); } عودة الرماة. } Let army = makeArmy(); الجيش[0](); // 0 الجيش[5](); // 5
هذا هو نفسه في الأساس، لأنه for
كل تكرار يولد بيئة معجمية جديدة، مع المتغير الخاص به i
. لذا shooter
الذي تم إنشاؤه في كل تكرار يشير إلى i
الخاص به، من هذا التكرار ذاته.
الآن، بعد أن بذلت الكثير من الجهد في قراءة هذا، والوصفة النهائية بسيطة جدًا - فقط استخدم for
، قد تتساءل - هل كان الأمر يستحق ذلك؟
حسنًا، إذا كان بإمكانك الإجابة على السؤال بسهولة، فلن تقرأ الحل. لذا، نأمل أن تكون هذه المهمة قد ساعدتك على فهم الأمور بشكل أفضل قليلاً.
علاوة على ذلك، هناك بالفعل حالات يفضل فيها while
فعل for
، وسيناريوهات أخرى، حيث تكون مثل هذه المشكلات حقيقية.
افتح الحل بالاختبارات في وضع الحماية.