أنماط أنيقة في جافا سكريبت الحديثة (دورة فريق بيل سورور)

مرحبا يا هبر! كتب بيل سورور مدرس جافا سكريبت المعروف في ذلك الوقت عدة مقالات عن الأنماط الحديثة في JS. كجزء من هذه المقالة ، سنحاول مراجعة الأفكار التي شاركها. لم يكن الأمر يتعلق ببعض الأنواع الفريدة من نوعها ، لكنني آمل أن يجد المقال قارئًا له. هذه المقالة ليست "ترجمة" من وجهة نظر سياسة هبر منذ ذلك الحين أصف أفكاري التي أشارت إليها مقالات بيل.

RORO


يعني الاختصار تلقي كائن ، إرجاع كائن - الحصول على كائن ، إرجاع كائن. أقدم رابطًا للمقال الأصلي: link

كتب بيل أنه توصل إلى طريقة لكتابة الدوال التي يقبل معظمها فيها معلمة واحدة فقط - كائن ذو وسيطات دالة. يقومون أيضًا بإرجاع كائن النتائج. استلهم مشروع القانون من إعادة هيكلة هذه الفكرة (واحدة من ميزات ES6).

بالنسبة لأولئك الذين لا يعرفون التدمير ، سأقدم التوضيحات اللازمة خلال القصة.

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

//   const user = { name: 'John Doe', login: 'john_doe', password: 12345, active: true, rules: { finance: true, analitics: true, hr: false } }; //   const users = [user]; //,     function findUsersByRule ( rule, withContactInfo, includeInactive) { //        active const filtredUsers= users.filter(item => includeInactive ? item.rules[rule] : item.active && item.rules[rule]); //  ()   ( )     withContactInfo return withContactInfo ? filtredUsers.reduce((acc, curr) => { acc[curr.id] = curr; return acc; }, {}) : filtredUsers.map(item => item.id) } //     findUsersByRule( 'finance', true, true) 

باستخدام الكود أعلاه ، سنحقق النتيجة المرجوة. ومع ذلك ، هناك العديد من المزالق في كتابة التعليمات البرمجية بهذه الطريقة.

أولاً ، استدعاء دالة findUsersByRule مشكوك فيه للغاية. لاحظ مدى غموض المعلمتين الأخيرتين. ماذا يحدث إذا كان تطبيقنا لا يحتاج أبدًا إلى معلومات الاتصال (withContactInfo) ولكن دائمًا ما يحتاج إلى مستخدمين غير نشطين (includeInactive)؟ سيكون لدينا دائما لتمرير القيم المنطقية. الآن بينما يكون إعلان الوظيفة بجوار مكالمته ، فإن هذا ليس مخيفًا للغاية ، لكن تخيل أنك ترى هذه المكالمة في مكان ما في وحدة نمطية أخرى. سيتعين عليك البحث عن وحدة نمطية مع تعريف دالة من أجل فهم سبب نقل قيمتين منطقيتين في شكل خالص إليها.

ثانياً ، إذا أردنا جعل بعض المعلمات إلزامية ، فسيتعين علينا كتابة شيء مثل هذا:

 function findUsersByRule ( role, withContactInfo, includeInactive) { if (!role) { throw Error(...) ; } //...  } 

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

يتضمن التدمير تقسيم بنية معقدة إلى أجزاء بسيطة. في JavaScript ، تكون هذه البنية المعقدة عادةً كائنًا أو صفيفًا. باستخدام بناء الجملة المدمر ، يمكنك استخراج أجزاء صغيرة من المصفوفات أو الكائنات. يمكن استخدام بناء الجملة هذا لإعلان المتغيرات أو الغرض منها. يمكنك أيضًا إدارة الهياكل المتداخلة باستخدام بناء جملة التدمير المتداخل بالفعل.

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

 function findUsersByRule ({ rule, withContactInfo, includeInactive}) { //    } findUsersByRule({ rule: 'finance', withContactInfo: true, includeInactive: true}) 

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

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

يمكن أيضًا حل مشكلة المعلمات المطلوبة بطريقة أكثر أناقة.

 function requiredParam (param) { const requiredParamError = new Error( `Required parameter, "${param}" is missing.` ) } function findUsersByRule ({ rule = requiredParam('rule'), withContactInfo, includeInactive} = {}) {...} 

إذا لم نقم بتمرير قيمة القاعدة ، فستعمل الوظيفة التي تم تمريرها افتراضيًا ، مما سيؤدي إلى استثناء.

يمكن للوظائف في JS إرجاع قيمة واحدة فقط ، لذلك يمكنك استخدام كائن لنقل المزيد من المعلومات. بالطبع ، نحن لسنا بحاجة دائمًا إلى الدالة لإرجاع الكثير من المعلومات ، وفي بعض الحالات سنكون راضين عن عودة البدائية ، على سبيل المثال ، findUserId طبيعيًا بكل شرط.

أيضا ، هذا النهج يبسط تكوين وظائف. في الواقع ، مع التكوين ، يجب أن تأخذ الدالات معلمة واحدة فقط. نمط RORO يلتزم بنفس العقد.

فاتورة سورور: "مثل أي قالب ، يجب أن ينظر إلى RORO كأداة أخرى في صندوق الأدوات لدينا. "نحن نستخدمها حيثما تكون مفيدة ، مما يجعل قائمة المعلمات أكثر قابلية للفهم ومرونة ، والقيمة المرجعة أكثر تعبيرًا."

مصنع الثلج


يمكنك العثور على المقال الأصلي على هذا الرابط.

وفقًا للمؤلف ، يعد هذا القالب دالة تقوم بإنشاء وإرجاع كائن متجمد.

بيل يعتقد. أنه في بعض الحالات ، يمكن لهذا النمط استبدال فصول ES6 المعتادة بالنسبة لنا. على سبيل المثال ، لدينا سلة طعام معينة يمكننا من خلالها إضافة / إزالة المنتجات.

فئة ES6:

 // ShoppingCart.js class ShoppingCart { constructor({db}) { this.db = db } addProduct (product) { this.db.push(product) } empty () { this.db = [] } get products () { return Object .freeze([...this.db]) } removeProduct (id) { // remove a product } // other methods } // someOtherModule.js const db = [] const cart = new ShoppingCart({db}) cart.addProduct({ name: 'foo', price: 9.99 }) 

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

 const db = [] const cart = new ShoppingCart({db}) cart.addProduct = () => 'nope!' //   JS  cart.addProduct({ name: 'foo', price: 9.99 }) // output: "nope!"     

يجب أن نتذكر أيضًا أنه يتم تنفيذ الفصول في JS بناءً على تفويض النموذج الأولي ، وبالتالي ، يمكننا تغيير تنفيذ الطريقة في النموذج الأولي للفئة وستؤثر هذه التغييرات على جميع الحالات الحالية (تحدثت عن ذلك بمزيد من التفاصيل في المقالة حول OOP ).

 const cart = new ShoppingCart({db: []}) const other = new ShoppingCart({db: []}) ShoppingCart.prototype .addProduct = () => 'nope!' //     JS cart.addProduct({ name: 'foo', price: 9.99 }) // output: "nope!" other.addProduct({ name: 'bar', price: 8.88 }) // output: "nope!" 

موافق ، يمكن أن تسبب لنا هذه الميزات الكثير من المتاعب.

مشكلة شائعة أخرى هي تعيين أسلوب مثيل لمعالج الأحداث.

 document .querySelector('#empty') .addEventListener( 'click', cart.empty ) 

لن يؤدي النقر فوق الزر إلى إفراغ السلة. تقوم الطريقة بتعيين خاصية جديدة لزرنا المسمى db وتعيين هذه الخاصية على [] بدلاً من التأثير على ديسيبل كائن العربة. ومع ذلك ، لا توجد أخطاء في وحدة التحكم ، وسيخبرك الحس السليم أن الشفرة يجب أن تعمل ، لكنها ليست كذلك.

لجعل هذا الرمز يعمل ، عليك أن تكتب وظيفة سهم:

 document .querySelector("#empty") .addEventListener( "click", () => cart.empty() ) 

أو ربط السياق بربط:

 document .querySelector("#empty") .addEventListener( "click", cart.empty.bind(cart) ) 

سيساعدنا مصنع الثلج في تجنب هذه الفخاخ.

 function makeShoppingCart({ db }) { return Object.freeze({ addProduct, empty, getProducts, removeProduct, // others }) function addProduct (product) { db.push(product) } function empty () { db = [] } function getProducts () { return Object .freeze([...db]) } function removeProduct (id) { // remove a product } // other functions } // someOtherModule.js const db = [] const cart = makeShoppingCart({ db }) cart.addProduct({ name: 'foo', price: 9.99 }) 

ميزات هذا النمط:

  • لا حاجة لاستخدام الكلمة الرئيسية الجديدة
  • لا حاجة لربط هذا
  • عربة محمولة بالكامل
  • يمكن التصريح عن المتغيرات المحلية التي لن تكون مرئية من الخارج

 function makeThing(spec) { const secret = 'shhh!' return Object.freeze({ doStuff }) function doStuff () { //    secret } } // secret    const thing = makeThing() thing.secret // undefined 

  • نمط يدعم الميراث
  • يكون إنشاء كائنات باستخدام Ice Factory أبطأ ويحتاج إلى ذاكرة أكبر من استخدام الفصل (في كثير من الحالات ، قد نحتاج إلى فئات ، لذلك أوصي بهذا المقال )
  • هذه وظيفة شائعة يمكن تعيينها كرد اتصال

استنتاج


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

Source: https://habr.com/ru/post/ar472816/


All Articles