
كيف تبدأ بسرعة مع VUE3.0: أدخل
التوصيات ذات الصلة بالتعلم: البرنامج التعليمي لتعلم JavaScript
لقد رأيت العديد من الشروحات حول البرمجة الوظيفية، ولكن معظمها على المستوى النظري، وبعضها مخصص فقط للغات البرمجة الوظيفية البحتة مثل هاسكل. الغرض من هذه المقالة هو التحدث عن الممارسة المحددة للبرمجة الوظيفية في JavaScript في نظري، والسبب في كونها "في نظري" يعني أن ما أقوله لا يمثل سوى رأيي الشخصي، والذي قد يتعارض مع بعض المفاهيم الصارمة.
ستحذف هذه المقالة الكثير من المقدمة الرسمية للمفاهيم، وتركز على إظهار الكود الوظيفي في JavaScript، وما الفرق بين الكود الوظيفي والكتابة العامة، وما هي الفوائد التي يمكن أن يجلبها لنا الكود الوظيفي، وما هي بعض النماذج الوظيفية؟
أعتقد أنه يمكن فهم البرمجة الوظيفية على أنها طريقة برمجة تستخدم الوظائف باعتبارها الناقل الرئيسي.
تتم مقارنة استخدام الوظائف لتفكيك التعبيرات العامة وتجريدها مع الضرورات. النقاط الرئيسية هي كما يلي:
دلالات أكثر وضوحًا، وقابلية إعادة استخدام أعلى، وقابلية صيانة أعلى، وحدود أفضل للنطاق، وتأثيرات جانبية أقل. المثال التالي هو تعبير وظيفي محدد
لرمز Javascript
// كل كلمة في المصفوفة، قم بتكبيرها الحرف الأول // الكتابة العامة const arr = ['apple', 'pen', 'apple-pen'] for(const i in arr){
const c = arr[i][0];
arr[i] = c.toUpperCase() + arr[i].slice(1);
console.log(arr);
// طريقة الكتابة الوظيفية - function UpperFirst(word) {
إرجاع كلمة[0].toUpperCase() + word.slice(1);
وظيفة wordToUpperCase(arr) {
إرجاع arr.map(upperFirst });
console.log(wordToUpperCase(['apple', 'pen', 'apple-pen']));
// طريقة الكتابة الوظيفية 2 console.log(arr.map(['apple', 'pen', 'apple-pen'], word => word[0].toUpperCase() + word.slice(1))) عندما يصبح الوضع أكثر تعقيدًا ،
ستواجه طريقة كتابة التعبيرات العديد من المشكلات:
المعنى غير واضح، ويصبح من الصعب الحفاظ عليه تدريجيًا، وقابلية إعادة الاستخدام ضعيفة، وسيتم إنشاء المزيد من التعليمات البرمجية، وسيتم إنشاء العديد من المتغيرات الوسيطة. البرمجة الوظيفية جيدة في حل المشكلات المذكورة أعلاه. أولاً، ارجع إلى طريقة الكتابة الوظيفية 1، التي تستخدم التغليف الوظيفي لتحليل الوظائف (التفاصيل ليست فريدة)، وتغليفها في وظائف مختلفة، ثم تستخدم الاستدعاءات المجمعة لتحقيق الغرض. وهذا يجعل التعبير واضحًا وسهل الصيانة وإعادة الاستخدام والتوسيع. ثانيًا، باستخدام وظائف ذات ترتيب أعلى، يحل Array.map محل...of لاجتياز المصفوفة، مما يقلل من المتغيرات والعمليات الوسيطة.
الفرق الرئيسي بين طريقة الكتابة الوظيفية 1 وطريقة الكتابة الوظيفية 2 هو أنه يمكنك التفكير فيما إذا كان يمكن إعادة استخدام الوظيفة لاحقًا، وإذا لم يكن الأمر كذلك، فإن الأخيرة أفضل.
من طريقة الكتابة الوظيفية 2 أعلاه، يمكننا أن نرى أنه أثناء عملية كتابة التعليمات البرمجية الوظيفية، من السهل التسبب في امتداد أفقي، أي إنتاج طبقات متعددة من التداخل، دعنا نعطي مثالًا متطرفًا أدناه.
كود جافا سكريبت
// حساب مجموع الأرقام // طريقة الكتابة العامة console.log(1 + 2 + 3 - 4)
// دالة الكتابة الوظيفية sum(a, b) {
العودة أ + ب }
الوظيفة الفرعية (أ، ب) {
العودة أ - ب }
console.log(sub(sum(sum(1, 2), 3), 4); يوضح هذا المثال فقط حالة متطرفة من الامتداد الأفقي. مع استمرار زيادة عدد المستويات المتداخلة من الوظائف، ستصبح التعليمات البرمجية أقل قابلية للقراءة يتم تقليل الأداء بشكل كبير ومن السهل ارتكاب الأخطاء.
في هذه الحالة، يمكننا النظر في طرق تحسين متعددة، مثل تحسين السلسلة التالية.
// تحسين الكتابة (حسنًا، لقد قرأت ذلك بشكل صحيح، هذه هي الكتابة المتسلسلة لـ lodash) كود Javascript const utils = {
سلسلة (أ) {
this._temp = a;
رد هذا؛
},
مجموع (ب) {
this._temp += b;
رد هذا؛
},
الفرعية (ب) {
this._temp -= b;
رد هذا؛
},
قيمة() {
const _temp = this._temp;
this._temp = غير محدد؛
عودة _درجة الحرارة؛
} };
console.log(utils.chain(1).sum(2).sum(3).sub(4).value()); بعد إعادة الكتابة بهذه الطريقة، سيصبح الهيكل العام أكثر وضوحًا، وكل رابط من السلسلة سيكون ما يجب فعله يمكن أيضًا عرضه بسهولة. مثال جيد آخر للمقارنة بين تداخل الوظائف وتسلسلها هو وظيفة رد الاتصال ونمط الوعد.
كود جافا سكريبت
// طلب واجهتين بالتسلسل // وظيفة رد الاتصال import $ from 'jquery'; $.post('a/url/to/target', (rs) => {
إذا (ص) {
$.post('a/url/to/another/target', (rs2) => {
إذا (RS2) {
$.post('a/url/to/third/target');
}
});
} });
// طلب استيراد واعد من 'catta'; // catta هي أداة طلب خفيفة الوزن تدعم الجلب وjsonp وajax، ولا تحتوي على تبعيات request('a/url/to/target')
.then(rs => rs ? $.post('a/url/to/another/target') : Promise.reject())
.then(rs2 => rs2 ? $.post('a/url/to/third/target') : Promise.reject()); مع زيادة مستوى تداخل وظيفة رد الاتصال والتعقيد على مستوى واحد، سيصبح الأمر كذلك منتفخة ويصعب صيانتها، لكن البنية التسلسلية لـ Promise لا يزال من الممكن أن تتوسع عموديًا عندما يكون التعقيد مرتفعًا، وتكون العزلة الهرمية واضحة جدًا.
يمكنلنموذج الإغلاق الوظيفي المشترك
الاحتفاظ بالمتغيرات المحلية في كتلة التعليمات البرمجية التي لم يتم إصدارها، ويسمى
مفهوم الإغلاق مجردًا نسبيًا، وأعتقد أن الجميع يعرفون
ما هو مفيد هل يمكن أن تجلب لنا الحقائب؟
دعونا نلقي نظرة أولاً على كيفية إنشاء إغلاق:
كود Javascript
// إنشاء وظيفة إغلاق makeCounter() {
دع ك = 0؛
وظيفة العودة () {
عودة ++ ك؛
}; }
عداد ثابت = makeCounter();
console.log(counter()); // 1 console.log(counter()); // 2 makeCounter تشير كتلة التعليمات البرمجية لهذه الوظيفة إلى المتغير المحلي k في الوظيفة التي تم إرجاعها، مما يتسبب في فشل المتغير المحلي يتم تنفيذ الوظيفة، ويعيد النظام تدويرها، مما يؤدي إلى الإغلاق. تتمثل وظيفة هذا الإغلاق في "الاحتفاظ" بالمتغير المحلي بحيث يمكن إعادة استخدام المتغير عند استدعاء الوظيفة الداخلية؛ وعلى عكس المتغيرات العامة، لا يمكن الرجوع إلى هذا المتغير إلا داخل الوظيفة.
بمعنى آخر، تقوم عمليات الإغلاق فعليًا بإنشاء بعض "المتغيرات المستمرة" الخاصة بالوظيفة.
إذن من هذا المثال، يمكننا أن نستنتج أن شروط إنشاء الإغلاق هي:
هناك وظائف داخلية وخارجية تشير الوظيفة الداخلية إلى المتغيرات المحلية للوظيفة الخارجية. الغرض الرئيسي من الإغلاق هو التعريف بعض الوظائف: المتغيرات الثابتة ذات المجال المحدود، ويمكن استخدام هذه المتغيرات للتخزين المؤقت أو العمليات الحسابية المتوسطة، وما إلى ذلك.
كود جافا سكريبت
// أداة تخزين مؤقت بسيطة // تقوم الوظيفة المجهولة بإنشاء إغلاق ذاكرة التخزين المؤقت const = (function() {
متجر ثابت = {}؛
يعود {
الحصول على (المفتاح) {
مخزن العودة [المفتاح]؛
},
مجموعة (مفتاح، فال) {
store[key] = val;
}
} }());
Cache.set('a', 1); Cache.get('a'); // 1 المثال أعلاه هو تنفيذ أداة تخزين مؤقت بسيطة. تقوم الوظيفة المجهولة بإنشاء إغلاق بحيث يمكن الرجوع إلى كائن المتجر دائمًا. ، لن يتم إعادة تدويرها.
مساوئ عمليات الإغلاق: لن يتم تحرير المتغيرات المستمرة بشكل طبيعي وتستمر في احتلال مساحة الذاكرة، مما قد يؤدي بسهولة إلى إهدار الذاكرة، لذلك يلزم بشكل عام بعض آليات التنظيف اليدوية الإضافية.
تسمى الوظيفة التي تقبل أو ترجع وظيفة ذات ترتيب أعلى،
وهي تبدو وكأنها كلمة باردة جدًا، ولكن في الواقع نستخدمها كثيرًا، لكننا لا نعرف أسمائها. تدعم لغة JavaScript في الأصل وظائف ذات ترتيب أعلى، لأن وظائف JavaScript مواطنة من الدرجة الأولى، ويمكن استخدامها كمعلمات وكقيمة إرجاع لوظيفة أخرى.
يمكننا غالبًا رؤية العديد من الوظائف الأصلية عالية الترتيب في JavaScript، مثل Array.map وArray.reduce وArray.filter،
فلنأخذ الخريطة كمثال، دعونا نرى كيف يستخدم
عبارة عن مجموعة من بمعنى آخر، يخضع كل عنصر في المجموعة لنفس التحويل لإنشاء
خريطة مجموعة جديدة، باعتبارها دالة عالية الترتيب، فهي تقبل معلمة دالة كرمز
جافا سكريبت
المنطقي للتعيين// أضف واحدًا إلى كل عنصر في المجموعة. المصفوفة لتكوين مصفوفة جديدة // طريقة الكتابة العامة const arr = [1,2,3]; const rs = []; rs.push(++n) } console.log(rs) // إعادة كتابة الخريطة const arr = [1,2,3]; const rs = arr.map(n => ++n)
; عمليات إضافية، وهناك خطر تغيير المصفوفة الأصلية
، لكن وظيفة الخريطة تغلف العمليات الضرورية، لذلك نحتاج فقط إلى الاهتمام بتنفيذ الوظيفة لمنطق التعيين، مما يقلل من كمية التعليمات البرمجية ومخاطر الجانب تأثيرات.
يعطيبعض معلمات الدالة وينشئ دالة جديدة تقبل معلمات أخرى.
قد لا تسمع هذا المصطلح كثيرًا، ولكن أي شخص استخدم undescore أو lodash قد شاهده.
هناك وظيفة _.partial سحرية، وهي عبارة عن
كود جافا سكريبت
// احصل على المسار النسبي للملف الهدف إلى المسار الأساسي // طريقة الكتابة العامة هي const BASE = '/path/to/base'; المسار النسبي (BASE، '/بعض/المسار')؛
// _.parical إعادة الكتابة const BASE = '/path/to/base'; const RewriteFromBase = _.partial(path.relative, BASE);
const RelativePath =relativeFromBase('/some/path'); من خلال _.partial، نحصل على الوظيفة الجديدة النسبية FromBase. عندما يتم استدعاء هذه الوظيفة، فهي تعادل استدعاء path.relative، ويتم تمرير المعلمة الأولى إلى BASE بشكل افتراضي يتم إلحاق المعلمات اللاحقة التي تم تمريرها بالترتيب.
في هذه الحالة، ما نريد تحقيقه حقًا هو الحصول على المسار بالنسبة إلى BASE في كل مرة، وليس بالنسبة لأي مسار. يتيح لنا Currying الاهتمام فقط ببعض معلمات الوظيفة، مما يجعل الغرض من الوظيفة أكثر وضوحًا وتسميتها أبسط.
يجمعبين قدرات وظائف متعددة لإنشاء وظيفة جديدة
ربما تكون قد رأيتها لأول مرة في lodash، طريقة الإنشاء (التي تسمى الآن التدفق)
كود Javascript
// قم بكتابة حرف كبير على كل كلمة في المصفوفة، وقم بتنفيذ Base64. // طريقة الكتابة العامة (أحدها) const arr = ['pen', 'applypen']; const rs = []; rs.push(btoa(w.toUpperCase())); } console.log(rs); // _. إعادة كتابة التدفق const arr = ['pen', 'apple', 'applypen']; const UpperAndBase64 = _.partialRight(_.map, _.flow(_.upperCase, btoa)); console.log(upperAndBase64(arr));
_.flow يدمج إمكانات تحويل الأحرف الكبيرة ووظائف تحويل Base64 لإنشاء وظيفة جديدة. مريحة للاستخدام كوظيفة معلمة أو إعادة استخدامها لاحقًا.
من وجهة نظري الخاصة، قد يختلف فهمي للبرمجة الوظيفية لجافا سكريبت عن العديد من المفاهيم التقليدية. لا أعتقد فقط أن الوظائف عالية الترتيب تعتبر برمجة وظيفية، بل أعتقد أن البعض الآخر، مثل استدعاءات الوظائف العادية والهياكل المتسلسلة وما إلى ذلك، ينتمي إلى فئة البرمجة الوظيفية، طالما أنهم يستخدمون الوظائف باعتبارها الوظائف الرئيسية. الناقل.
وأعتقد أن البرمجة الوظيفية ليست ضرورية، ولا ينبغي أن تكون قاعدة أو شرطًا إلزاميًا. مثل الأفكار الموجهة للكائنات أو الأفكار الأخرى، فهي أيضًا إحدى الطرق. في معظم الحالات، يجب أن نكون مزيجًا من عدة مفاهيم، بدلًا من أن نقتصر على مفاهيم.
التوصيات ذات الصلة: البرنامج التعليمي لـ JavaScript
ما ورد أعلاه هو مناقشة تفصيلية لبرمجة JavaScript الوظيفية لمزيد من المعلومات، يرجى الانتباه إلى المقالات الأخرى ذات الصلة على موقع PHP الصيني!
