تأليف في كل مكان: تكوين الوظيفة في JavaScript

تم إعداد ترجمة المقالة خصيصًا لطلاب JavaScript Developer.Basic الدورة الأساسية .










المقدمة



يبدو أن مكتبات Lodash و Underscore موجودة في كل مكان ولا تزال تتمتع بطريقة الإنشاء فائقة الكفاءة التي نعرفها اليوم .



دعنا نلقي نظرة فاحصة على وظيفة الإنشاء ونرى كيف يمكن أن تجعل شفرتك أكثر قابلية للقراءة وصيانتها وأنيقة.



أساسيات



سنغطي الكثير من وظائف Lodash لأننا 1) لن نكتب الخوارزميات الأساسية الخاصة بنا - وهذا سيصرفنا عما أقترح التركيز عليه ؛ 2) يتم استخدام مكتبة Lodash من قبل العديد من المطورين ويمكن استبدالها بـ Underscore أو أي مكتبة أخرى أو خوارزمياتك الخاصة دون أي مشاكل.



قبل النظر في بعض الأمثلة البسيطة ، دعنا نلقي نظرة على وظيفة وظيفة الإنشاء بالضبط وكيفية تنفيذ وظيفة الإنشاء الخاصة بك إذا لزم الأمر.



var compose = function(f, g) {
    return function(x) {
        return f(g(x));
    };
};


هذا هو التطبيق الأساسي. ملاحظة: سيتم تنفيذ الوظائف في الوسائط من اليمين إلى اليسار . أي ، يتم تنفيذ الوظيفة الموجودة على اليمين أولاً ، ويتم تمرير نتيجتها إلى الوظيفة الموجودة على يسارها.



الآن دعونا نلقي نظرة على هذا الرمز:



function reverseAndUpper(str) {
  var reversed = reverse(str);
  return upperCase(reversed);
}


تقوم وظيفة reverseAndUpper أولاً بعكس السلسلة المحددة ثم تحويلها إلى أحرف كبيرة. يمكننا إعادة كتابة هذا الكود باستخدام وظيفة الإنشاء الأساسية:



var reverseAndUpper = compose(upperCase, reverse);


يمكن الآن استخدام وظيفة reverseAndUpper:



reverseAndUpper(''); // 


كان من الممكن أن نحصل على نفس النتيجة بكتابة الكود:



function reverseAndUpper(str) {
  return upperCase(reverse(str));
}


يبدو هذا الخيار أكثر أناقة ويسهل صيانته وإعادة استخدامه.



ستكون القدرة على تصميم الوظائف بسرعة وإنشاء خطوط أنابيب معالجة البيانات مفيدة في مواقف مختلفة عندما تحتاج إلى تحويل البيانات أثناء التنقل. تخيل الآن أنك بحاجة إلى تمرير مجموعة إلى دالة ، وتحويل عناصر المجموعة وإرجاع القيمة القصوى بعد اكتمال جميع خطوات خط الأنابيب ، أو تحويل سلسلة معينة إلى قيمة منطقية. تتيح لك وظيفة الإنشاء الجمع بين وظائف متعددة وبالتالي إنشاء وظيفة أكثر تعقيدًا.



دعنا ننفذ وظيفة إنشاء أكثر مرونة يمكن أن تتضمن أي عدد من الوظائف والوسيطات الأخرى. وظيفة الإنشاء السابقة التي نظرنا إليها تعمل فقط مع وظيفتين ولا تأخذ سوى الوسيطة الأولى التي تم تمريرها. يمكننا إعادة كتابته على النحو التالي:



var compose = function() {
  var funcs = Array.prototype.slice.call();
 
  return funcs.reduce(function(f,g) {
    return function() {
      return f(g.apply(this, ));
    };
  });
};


باستخدام وظيفة مثل هذه ، يمكننا كتابة رمز مثل هذا:



Var doSometing = compose(upperCase, reverse, doSomethingInitial);
 
doSomething('foo', 'bar');


هناك العديد من المكتبات التي تنفذ وظيفة الإنشاء. نحتاج إلى وظيفة الإنشاء الخاصة بنا لفهم كيفية عملها. بالطبع ، يتم تطبيقه بشكل مختلف في المكتبات المختلفة. تقوم وظائف الإنشاء من مكتبات مختلفة بنفس الشيء ، لذا فهي قابلة للتبادل. الآن بعد أن فهمنا موضوع وظيفة الإنشاء ، دعنا نستخدم الأمثلة التالية لإلقاء نظرة على وظيفة _.compose من مكتبة Lodash.



أمثلة على



لنبدأ ببساطة:



function notEmpty(str) {
    return ! _.isEmpty(str);
}


الدالة notEmpty هي إنكار القيمة التي تُرجعها الدالة _.isEmpty.



يمكننا تحقيق نفس النتيجة باستخدام وظيفة _.compose من مكتبة Lodash. دعنا نكتب الوظيفة لا:



function not(x) { return !x; }
 
var notEmpty = _.compose(not, _.isEmpty);


يمكنك الآن استخدام الدالة notEmpty مع أي وسيطة:



notEmpty('foo'); // true
notEmpty(''); // false
notEmpty(); // false
notEmpty(null); // false


هذا مثال بسيط جدا. لنلقِ نظرة على شيء أكثر تعقيدًا: تُرجع

الدالة findMaxForCollection القيمة القصوى من مجموعة كائنات ذات خصائص id و val (value).



function findMaxForCollection(data) {
    var items = _.pluck(data, 'val');
    return Math.max.apply(null, items);
}
 
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
 
findMaxForCollection(data);


يمكن استخدام وظيفة الإنشاء لحل هذه المشكلة:



var findMaxForCollection = _.compose(function(xs) { return Math.max.apply(null, xs); }, _.pluck);
 
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
 
findMaxForCollection(data, 'val'); // 6


هناك عمل يجب القيام به هنا.



يتوقع _.pluck مجموعة باعتبارها الوسيطة الأولى ووظيفة رد الاتصال باعتبارها الوسيطة الثانية. هل من الممكن تطبيق وظيفة _.pluck جزئيًا؟ في هذه الحالة ، يمكنك استخدام curry لتغيير ترتيب الوسائط.



function pluck(key) {
    return function(collection) {
        return _.pluck(collection, key);
    }
}


تحتاج وظيفة findMaxForCollection إلى التعديل أكثر قليلاً. لنقم بإنشاء دالة max خاصة بنا.



function max(xs) {
    return Math.max.apply(null, xs);
}


الآن يمكننا أن نجعل وظيفة التركيب أكثر أناقة:



var findMaxForCollection = _.compose(max, pluck('val'));
 
findMaxForCollection(data);


كتبنا دالة نتف خاصة بنا ولا يمكننا استخدامها إلا مع خاصية "val". قد لا تفهم سبب كتابة طريقة الجلب الخاصة بك إذا كان لدى Lodash بالفعل وظيفة جاهزة ومريحة _.pluck. تكمن المشكلة في أنها _.pluckتتوقع مجموعة باعتبارها الوسيطة الأولى ، ونريد أن نفعل ذلك بشكل مختلف. من خلال تغيير ترتيب الوسائط ، يمكننا تطبيق الوظيفة جزئيًا عن طريق تمرير المفتاح باعتباره الوسيطة الأولى ؛ ستقبل الدالة المرتجعة البيانات.

يمكننا تحسين طريقة أخذ العينات لدينا أكثر من ذلك بقليل. لدى Lodash طريقة سهلة _.curryتتيح لك كتابة وظيفتنا على النحو التالي :



function plucked(key, collection) {
    return _.pluck(collection, key);
}
 
var pluck = _.curry(plucked);


نحن فقط نلف وظيفة النقط الأصلية لتبديل الحجج. الآن ستعيد pluck الوظيفة حتى تنتهي من معالجة جميع المتغيرات. لنرى كيف تبدو الشفرة النهائية:



function max(xs) {
    return Math.max.apply(null, xs);
}
 
function plucked(key, collection) {
    return _.pluck(collection, key);
}
 
var pluck = _.curry(plucked);
 
var findMaxForCollection = _.compose(max, pluck('val'));
 
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
 
findMaxForCollection(data); // 6


يمكن قراءة دالة findMaxForCollection من اليمين إلى اليسار ، أي أنه يتم أولاً تمرير خاصية val لكل عنصر في المجموعة إلى الدالة ، ثم تُرجع الحد الأقصى لجميع القيم المقبولة.



var findMaxForCollection = _.compose(max, pluck('val'));


هذا الرمز أسهل في الصيانة وقابل لإعادة الاستخدام وأكثر أناقة. دعونا نلقي نظرة على المثال الأخير ، فقط للاستمتاع مرة أخرى بأناقة تكوين الوظيفة.



لنأخذ البيانات من المثال السابق ونضيف خاصية جديدة تسمى نشطة. الآن تبدو البيانات كما يلي:



var data = [{id: 1, val: 5, active: true}, 
            {id: 2, val: 6, active: false }, 
            {id: 3, val: 2, active: true }];




دعنا نسمي هذه الوظيفة getMaxIdForActiveItems (بيانات) . يأخذ مجموعة من الكائنات ، ويصفي جميع الكائنات النشطة ، ويعيد القيمة القصوى من العناصر التي تمت تصفيتها.



function getMaxIdForActiveItems(data) {
    var filtered = _.filter(data, function(item) {
        return item.active === true;
    });
 
    var items = _.pluck(filtered, 'val');
    return Math.max.apply(null, items);
}


هل يمكنك جعل هذا الرمز أكثر أناقة؟ لديها بالفعل وظائف max and pluck ، لذلك علينا فقط إضافة عامل تصفية:



var getMaxIdForActiveItems = _.compose(max, pluck('val'), _.filter);
 
getMaxIdForActiveItems(data, function(item) {return item.active === true; }); // 5


_.filterتظهر نفس المشكلة مع الوظيفة كما في _.pluck: لا يمكننا تطبيق هذه الوظيفة جزئيًا ، لأنها تتوقع المجموعة باعتبارها الوسيطة الأولى لها. يمكننا تغيير ترتيب المعطيات في الفلتر بلف الوظيفة الأولية:



function filter(fn) {
    return function(arr) {
        return arr.filter(fn);
    };
}


دعنا نضيف وظيفة isActiveتأخذ كائنًا وتتحقق مما إذا كانت العلامة النشطة مضبوطة على true.



function isActive(item) {
    return item.active === true;
}


يمكن تطبيق دالة filterذات دالة isActiveجزئيًا ، لذلك سنقوم فقط بتمرير البيانات إلى دالة getMaxIdForActiveItems .



var getMaxIdForActiveItems = _.compose(max, pluck('val'), filter(isActive));


الآن نحتاج فقط إلى نقل البيانات:



getMaxIdForActiveItems(data); // 5


الآن لا شيء يمنعنا من إعادة كتابة هذه الوظيفة بحيث تجد القيمة القصوى بين الكائنات غير النشطة وتعيدها:



var isNotActive = _.compose(not, isActive);

var getMaxIdForNonActiveItems = _.compose(max, pluck('val'), filter(isNotActive));


خاتمة



كما لاحظت ، فإن تكوين الوظيفة هو ميزة مثيرة تجعل شفرتك أنيقة وقابلة لإعادة الاستخدام. الشيء الرئيسي هو تطبيقه بشكل صحيح.



الروابط



لوداش

يا تسطير ، أنت تفعل ذلك بشكل خاطئ! (مرحبًا Underscore ، أنت تفعل كل شيء بشكل خاطئ!)

@sharifsbeat







اقرأ أكثر:






All Articles