هذه المقالة مخصصة لشخص يتخذ أولى خطواته الخجولة على المسار الشائك لتعلم JavaScript. على الرغم من حقيقة أنه في عام 2018 ، أستخدم بنية ES5 بحيث يمكن للمبتدئين الشباب فهم مادة Padawans الذين يدرسون دورة JavaScript Level 1 في HTML Academy.واحدة من الميزات التي تميز JS عن العديد من لغات البرمجة الأخرى هي أن الوظيفة في هذه اللغة هي "كائن من الدرجة الأولى". أو ، بالروسية ، الوظيفة هي معنى. نفس الرقم أو السلسلة أو الكائن. يمكننا كتابة دالة إلى متغير ، يمكننا وضعها في صفيف أو في خاصية كائن. يمكننا حتى إضافة وظيفتين. في الواقع ، لن يأتي أي شيء ذي معنى من هذا ، ولكن كحقيقة - يمكننا!
function hello(){}; function world(){}; console.log(hello + world);
الشيء الأكثر إثارة للاهتمام هو أنه يمكننا إنشاء وظائف تعمل على وظائف أخرى - قبولها كحجج أو إعادتها كقيمة. تسمى هذه الوظائف دالات
الترتيب الأعلى . واليوم سنتحدث نحن ، الفتيات والفتيان ، عن كيفية تكييف هذه الفرصة مع احتياجات الاقتصاد الوطني. على طول الطريق ، ستتعلم المزيد عن بعض الميزات المفيدة للوظائف في JS.
خط الأنابيب
لنفترض أن لدينا شيئًا تحتاج إلى صنع الكثير من القطع به. لنفترض أن أحد المستخدمين قام بتحميل ملف نصي يقوم بتخزين البيانات بتنسيق JSON ، ونريد معالجة محتوياته. أولاً ، نحتاج إلى قص أحرف المسافات الزائدة ، والتي يمكن أن "تنمو" حول الحواف نتيجة لإجراءات المستخدم أو نظام التشغيل. ثم تحقق من عدم وجود رمز ضار في النص (من يدري ، هؤلاء المستخدمون). ثم انتقل من نص إلى كائن باستخدام طريقة
JSON.parse
. ثم قم بإزالة البيانات التي نحتاجها من هذا الكائن. وفي النهاية - أرسل هذه البيانات إلى الخادم. تحصل على شيء مثل هذا:
function trim(){}; function sanitize(){}; function parse(){}; function extractData(){}; function send(){}; var textFromFile = getTextFromFile(); send(extractData(parse(sanitize(trim(testFromFile))));
يبدو موافق جدا. بالإضافة إلى ذلك ، ربما لم تلاحظ وجود قوس إغلاق مفقود. بالطبع ، سيخبرك IDE بذلك ، ولكن لا تزال هناك مشكلة. لحلها ، تم اقتراح
عامل جديد |> مؤخرًا. في الواقع ، إنها ليست جديدة ، ولكنها مستعارة بصدق من اللغات الوظيفية ، لكن هذه ليست النقطة. باستخدام هذا العامل ، يمكن إعادة كتابة السطر الأخير على النحو التالي:
textFromFile |> trim |> sanitize |> parse |> extractData |> send;
يأخذ العامل |> المعامل الأيسر ويمرره إلى المعامل الأيمن كحجة. على سبيل المثال ،
"Hello" |> console.log
يساوي
console.log("Hello")
. هذا مناسب جدًا للحالات التي يتم فيها استدعاء العديد من الوظائف على طول السلسلة. ومع ذلك ، قبل تقديم هذا المشغل ، سيمر الكثير من الوقت (إذا تم قبول هذا الاقتراح على الإطلاق) ، لكنك تحتاج إلى العيش بطريقة أو بأخرى الآن. لذلك ، يمكننا كتابة
دراجتنا وظيفة تحاكي هذا السلوك:
function pipe(){ var args = Array.from(arguments); var result = args.shift(); while(args.length){ var f = args.shift(); result = f(result); } return result; } pipe( textFromFile, trim, sanitize, parse, extractData, send );
إذا كنت مبتدئًا في Javascriptist (javascript؟ Javascript؟) ، فقد يبدو السطر الأول من الوظيفة غير مفهوم لك. الأمر بسيط: داخل الدالة ، نستخدم الكلمة الرئيسية للوسائط للوصول إلى كائن يشبه الصفيف يحتوي على جميع الوسائط التي تم تمريرها إلى الدالة. هذا مريح للغاية عندما لا نعرف مسبقًا عدد الحجج التي ستحصل عليها. الجسم الضخم يشبه المصفوفة ، لكن ليس تمامًا. لذلك ، نقوم بتحويله إلى صفيف عادي باستخدام أسلوب
Array.from
. آمل أن يكون الرمز الإضافي قابلاً للقراءة بالفعل: نبدأ من اليسار إلى اليمين لاستخراج العناصر من المصفوفة وتطبيقها على بعضها البعض بنفس الطريقة التي يفعلها عامل التشغيل |>.
تسجيل الدخول
هنا مثال آخر قريب من الحياة الحقيقية. لنفترض أن لدينا بالفعل وظيفة
f
تؤدي شيئًا مفيدًا. وفي عملية اختبار الكود الخاص بنا ، نريد معرفة المزيد عن كيفية عمل ذلك بالضبط. في أي لحظة تسمى ، ما هي الحجج التي يتم تمريرها إليها ، والقيم التي يتم إرجاعها.
بالطبع ، مع كل استدعاء دالة ، يمكننا كتابة هذا:
var result = f(a, b); console.log(" f " + a + " " + b + " " + result); console.log(" : " + Date.now());
لكن ، أولاً ، إنها مرهقة إلى حد ما. وثانيًا ، من السهل جدًا نسيانها. في يوم من الأيام سنكتب ببساطة
f(a, b)
، ومنذ ذلك الحين ستستقر ظلمة الجهل في أذهاننا. سوف يتوسع مع كل تحدٍ جديد ، لا نعرف عنه شيئًا.
من الناحية المثالية ، أود أن يتم تسجيل الدخول تلقائيًا. بحيث في كل مرة تتصل فيها بـ
f
، يتم كتابة جميع الأشياء التي نحتاجها إلى وحدة التحكم. ولحسن الحظ ، لدينا طريقة للقيام بذلك. تلبية ميزة الترتيب العالي الجديدة!
function addLogger(f){ return function(){ var args = Array.from(arguments); var result = f.apply(null, args); console.log(" " + f.name + " " + args.join() + " " + result + "\n" + " : " + Date.now()); return result; } } function sum(a, b){ return a + b; } var sumWithLogging = addLogger(sum); sum(1, 2);
تقوم الدالة بوظيفة وإرجاع دالة تستدعي الوظيفة التي تم تمريرها إلى الدالة عند إنشاء الوظيفة. عذرًا ، لم أستطع التوقف عن كتابة هذا. الآن بالروسية: تقوم وظيفة
addLogger
بإنشاء
addLogger
حول الوظيفة التي يتم تمريرها إليها كوسيطة. التفاف هو أيضا وظيفة. عندما يتم استدعاؤه ، فإنه يجمع مجموعة من الحجج بنفس الطريقة التي فعلناها في المثال السابق. بعد ذلك ، باستخدام طريقة
التطبيق ، تستدعي وظيفة ملفوفة بنفس الحجج وتتذكر النتيجة. بعد ذلك ، يكتب المجمع كل شيء إلى وحدة التحكم.
هنا لدينا حالة هجوم الرجل الكلاسيكي في الوسط. إذا كنت تستخدم مجمِّعًا بدلاً من
f
، فعندئذٍ من وجهة نظر الكود الذي يستخدمه ، فلا يوجد فرق عمليًا. قد تفترض الشفرة أنها تتواصل مع
f
مباشرة. في هذه الأثناء ، يرفع الغلاف كل شيء إلى الرفيق الرائد.
Eins، zwei، drei، vier ...
ومهمة أخرى قريبة من الممارسة. لنفترض أننا بحاجة إلى ترقيم بعض الكيانات. في كل مرة يظهر كيان جديد ، نحصل على رقم جديد له ، أكثر من الرقم السابق. للقيام بذلك ، نبدأ دالة بالشكل التالي:
var lastNumber = 0; function getNewNumber(){ return lastNumber++; }
ثم لدينا نوع جديد من الكيانات. قل قبل ذلك ، قمنا بترقيم الأرانب ، والآن هناك أيضًا أرانب. إذا كنت تستخدم وظيفة واحدة لكل من هؤلاء والآخرين ، فإن كل رقم يصدر للأرانب سيحدث "فجوة" في سلسلة الأرقام الصادرة للأرانب. لذا نحتاج إلى الوظيفة الثانية ومعها المتغير الثاني:
var lastHareNumber = 0; function getNewHareNumber(){ return lastHareNumber++; } var lastRabbitNumber = 0; function getNewRabbitNumber(){ return lastRabbitNumber++; }
تشعر أن هذا الرمز رائحة سيئة؟ أود الحصول على شيء أفضل. أولاً ، أود أن أكون قادرًا على إعلان هذه الوظائف بشكل مطوّل ، دون تكرار الرمز. وثانيًا ، أود أن "أحزم" بطريقة أو بأخرى المتغير الذي تستخدمه الدالة في الوظيفة نفسها حتى لا تسد مساحة الاسم مرة أخرى.
ثم انفجر رجل ، على دراية بمفهوم OOP ، ويقول:"الابتدائية ، واتسون." من الضروري جعل عدد المولدات ليس كائنات ، ولكن كائنات. تم تصميم الكائنات فقط لتخزين الوظائف التي تعمل مع البيانات ، إلى جانب هذه البيانات. ثم يمكننا كتابة شيء مثل:
var numberGenerator = new NumberGenerator(); var n = numberGenerator.get();
سأجيب عليه:
- لأكون صادقًا ، أنا أتفق معك تمامًا. ومن حيث المبدأ ، هذا نهج أكثر صحة مما سأقدمه الآن. ولكن هنا لدينا مقال حول الوظائف ، وليس حول OOP. لذا هل يمكنك أن تحافظ على هدوئك لفترة من الوقت ودعني أنهي؟
هنا (مفاجأة!) ستساعدنا وظيفة الترتيب الأعلى مرة أخرى.
function createNumberGenerator(){ var n = 0; return function(){ return n++; } } var getNewHareNumber = createNumberGenerator(); var getNewRabbitNumber = createNumberGenerator(); console.log( getNewHareNumber(), getNewHareNumber(), getNewHareNumber(), getNewRabbitNumber(), getNewRabbitNumber(), );
وهنا قد يكون لدى بعض الناس سؤال ، ربما حتى في شكل فاحش: ما الذي يحدث بحق الجحيم؟ لماذا نقوم بإنشاء متغير غير مستخدم في الوظيفة نفسها؟ كيف تصل إليها وظيفة داخلية إذا أكملت وظيفة خارجية تنفيذها منذ فترة طويلة؟ لماذا تحصل وظيفتان تم إنشاؤهما تشير إلى نفس المتغير على نتائج مختلفة؟ إجابة واحدة على كل هذه الأسئلة هي
الإغلاق .
في كل مرة يتم
createNumberGenerator
دالة
createNumberGenerator
، يقوم مترجم JS بإنشاء شيء سحري يسمى "سياق التنفيذ". بشكل تقريبي ، هذا هو الكائن الذي يتم فيه تخزين جميع المتغيرات المعلنة في هذه الوظيفة. لا يمكننا الوصول إليها ككائن جافا سكريبت عادي ، ولكن مع ذلك.
إذا كانت الوظيفة "بسيطة" (على سبيل المثال ، إضافة أرقام) ، فعند انتهاء عملها ، يصبح سياق التنفيذ عديم الفائدة. هل تعرف ماذا يحدث للبيانات غير الضرورية في JS؟ يلتهمهم شيطان لا يُشبع اسمه جامع القمامة. ومع ذلك ، إذا كانت الوظيفة "معقدة" ، فقد يحدث أن يحتاج شخص ما إلى سياقها حتى بعد تنفيذ هذه الوظيفة. في هذه الحالة ، ينقذه جامع القمامة ، ويبقى معلقة في مكان ما في ذاكرته ، حتى يتمكن أولئك الذين يحتاجون إليه من الوصول إليه.
وبالتالي ، فإن الدالة التي تم إرجاعها بواسطة
createNumberGenerator
ستتمكن دائمًا من الوصول إلى نسختها الخاصة من المتغير
n
. يمكنك اعتبارها حقيبة القابضة من D&D. تضع يدك في حقيبتك وتجد نفسك في "جيب" شخصي متعدد الأبعاد حيث يمكنك تخزين كل ما تريده.
تنحي
هناك شيء مثل "القضاء على الارتداد". هذا عندما لا نريد استدعاء بعض الوظائف كثيرًا. لنفترض أن هناك زرًا معينًا ، ينقر عليه يتم تشغيل عملية "باهظة الثمن" (طويلة ، أو تناول الكثير من الذاكرة ، أو الإنترنت ، أو التضحية بالعذارى). قد يحدث أن يبدأ مستخدم غير صبور في النقر على هذا الزر بتردد أكثر من عشرة هرتز. علاوة على ذلك ، فإن العملية المذكورة أعلاه لها طبيعة لا معنى لها في تشغيلها عشر مرات على التوالي ، لأن النتيجة النهائية لن تتغير. ثم نطبق "إزالة الثرثرة".
جوهرها بسيط للغاية: نحن نقوم بالوظيفة ليس على الفور ، ولكن بعد مرور بعض الوقت. إذا مضى هذا الوقت ، تم استدعاء الوظيفة مرة أخرى ، "نقوم بإعادة ضبط المؤقت". وبالتالي ، يمكن للمستخدم النقر على الزر ألف مرة على الأقل - هناك حاجة واحدة فقط للتضحية. ومع ذلك ، كلمات أقل ، كود أكثر:
function debounce(f, delay){ var lastTimeout; return function(){ if(lastTimeout){ clearTimeout(lastTimeout); } var args = Array.from(arguments); lastTimeout = setTimeout(function(){ f.apply(null, args); }, delay); } } function sacrifice(name){ console.log(name + " * *"); } function sacrificeDebounced = debounce(sacrifice, 500); sacrificeDebounced(""); sacrificeDebounced(""); sacrificeDebounced("");
في نصف ثانية سيتم التضحية بنا ، وستبقى كاتيا وسفيتا على قيد الحياة بفضل وظيفتنا السحرية.
إذا قرأت بعناية الأمثلة السابقة ، يجب أن يكون لديك فهم جيد لكيفية عمل كل شيء هنا. تؤدي الدالة
debounce
التي تم إنشاؤها بواسطة
debounce
إلى تأجيل تنفيذ الوظيفة الأصلية باستخدام
setTimeout . في هذه الحالة ، يتم تخزين معرف المهلة في متغير lastTimeout ، والذي يمكن الوصول إليه من خلال الغلاف بسبب الإغلاق. إذا كان معرف المهلة موجودًا بالفعل في هذا المتغير ،
فسيقوم المُلصق بإلغاء هذه المهلة باستخدام
clearTimeout . إذا كانت المهلة السابقة قد اكتملت بالفعل ، فلن يحدث شيء. إذا لم يكن كذلك ، فإن الأسوأ بالنسبة له.
على هذا ، ربما ، سأنتهي. آمل أن تكون قد تعلمت اليوم الكثير من الأشياء الجديدة ، والأهم من ذلك - أنك فهمت كل ما تعلمته. أراك مرة أخرى.