Elegante Muster in modernem JavaScript (Bill Sourour Team Cycle)

Hallo habr Der damals bekannte JavaScript-Lehrer Bill Sourour schrieb mehrere Artikel über moderne Muster in JS. Als Teil dieses Artikels werden wir versuchen, die von ihm geteilten Ideen zu überprüfen. Nicht, dass es einige einzigartige Muster waren, aber ich hoffe, dass der Artikel seinen Leser finden wird. Dieser Artikel ist aus Sicht von Habrs Politik seitdem keine "Übersetzung" Ich beschreibe meine Gedanken, auf die mich Bills Artikel hingewiesen haben.

Rooro


Die Abkürzung bedeutet Empfangen eines Objekts, Zurückgeben eines Objekts - Abrufen eines Objekts, Zurückgeben eines Objekts. Ich biete einen Link zum Originalartikel: Link

Bill schrieb, er habe eine Möglichkeit gefunden, Funktionen zu schreiben, bei denen die meisten nur einen Parameter akzeptieren - ein Objekt mit Funktionsargumenten. Sie geben auch ein Ergebnisobjekt zurück. Bill war von der Umstrukturierung dieser Idee (eine der ES6-Funktionen) inspiriert.

Für diejenigen, die nichts über die Destrukturierung wissen, werde ich während der Geschichte die notwendigen Erklärungen geben.

Stellen Sie sich vor, wir haben Benutzerdaten, die ihre Rechte für bestimmte Abschnitte der Anwendung enthalten, die im Datenobjekt dargestellt werden. Wir müssen bestimmte Informationen basierend auf diesen Daten anzeigen. Dazu könnten wir folgende Implementierung anbieten:

//   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) 

Mit dem obigen Code würden wir das gewünschte Ergebnis erzielen. Es gibt jedoch einige Fallstricke beim Schreiben von Code auf diese Weise.

Erstens ist der Aufruf der Funktion findUsersByRule sehr zweifelhaft. Beachten Sie, wie mehrdeutig die letzten beiden Parameter sind. Was passiert, wenn unsere Anwendung fast nie Kontaktinformationen (withContactInfo) benötigt, aber fast immer inaktive Benutzer (includeInactive)? Wir müssen immer logische Werte übergeben. Während sich die Funktionsdeklaration neben ihrem Aufruf befindet, ist dies nicht so beängstigend. Stellen Sie sich jedoch vor, Sie sehen einen solchen Aufruf irgendwo in einem anderen Modul. Sie müssen nach einem Modul mit einer Funktionsdeklaration suchen, um zu verstehen, warum zwei logische Werte in reiner Form darauf übertragen werden.

Zweitens müssen wir Folgendes schreiben, wenn wir einige Parameter obligatorisch machen wollen:

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

In diesem Fall führt unsere Funktion zusätzlich zu ihren Suchaufgaben auch eine Validierung durch, und wir wollten nur Benutzer anhand bestimmter Parameter finden. Natürlich kann die Suchfunktion Validierungsfunktionen übernehmen, aber dann wird die Liste der Eingabeparameter erweitert. Dies ist auch ein Minus eines solchen Codierungsmusters.

Bei der Destrukturierung wird eine komplexe Struktur in einfache Teile zerlegt. In JavaScript ist eine solch komplexe Struktur normalerweise ein Objekt oder ein Array. Mit der Destrukturierungssyntax können Sie kleine Fragmente aus Arrays oder Objekten extrahieren. Diese Syntax kann verwendet werden, um Variablen oder ihren Zweck zu deklarieren. Sie können verschachtelte Strukturen auch bereits mit der Syntax der verschachtelten Destrukturierung verwalten.

Bei Verwendung der Destrukturierung sieht die Funktion aus unserem vorherigen Beispiel folgendermaßen aus:

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

Bitte beachten Sie, dass unsere Funktion fast identisch aussieht, außer dass wir unsere Parameter in Klammern setzen. Anstatt drei verschiedene Parameter zu empfangen, erwartet unsere Funktion jetzt ein einzelnes Objekt mit Eigenschaften: rule , withContactInfo und includeInactive .

Dies ist viel weniger zweideutig, viel einfacher zu lesen und zu verstehen. Darüber hinaus ist das Überspringen oder eine andere Reihenfolge unserer Parameter kein Problem mehr, da sie jetzt als Eigenschaften des Objekts bezeichnet werden. Wir können der Funktionsdeklaration auch sicher neue Parameter hinzufügen. Außerdem seit Da die Destrukturierung den übergebenen Wert kopiert, wirken sich seine Änderungen in der Funktion nicht auf das Original aus.

Das Problem mit den erforderlichen Parametern kann auch eleganter gelöst werden.

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

Wenn wir den Regelwert nicht übergeben, funktioniert die standardmäßig übergebene Funktion, wodurch eine Ausnahme ausgelöst wird.

Funktionen in JS können nur einen Wert zurückgeben, sodass Sie ein Objekt verwenden können, um weitere Informationen zu übertragen. Natürlich benötigen wir die Funktion nicht immer, um viele Informationen zurückzugeben. In einigen Fällen sind wir mit der Rückgabe des findUserId zufrieden. Beispielsweise gibt findUserId ganz natürlich einen Bezeichner unter bestimmten Bedingungen zurück.

Dieser Ansatz vereinfacht auch die Zusammensetzung von Funktionen. In der Tat sollten Funktionen bei der Komposition nur einen Parameter annehmen. Das RORO-Muster entspricht demselben Vertrag.

Bill Sourour: „Wie jede Vorlage sollte RORO als ein weiteres Werkzeug in unserer Toolbox angesehen werden. "Wir verwenden es dort, wo es von Vorteil ist, wodurch die Parameterliste verständlicher und flexibler und der Rückgabewert aussagekräftiger wird."

Eisfabrik


Den Originalartikel finden Sie unter diesem Link.

Laut dem Autor ist diese Vorlage eine Funktion, die ein eingefrorenes Objekt erstellt und zurückgibt.

Bill denkt nach. dass dieses Muster in einigen Situationen die für uns üblichen ES6-Klassen ersetzen kann. Zum Beispiel haben wir einen bestimmten Lebensmittelkorb, in dem wir Produkte hinzufügen / entfernen können.

ES6-Klasse:

 // 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 }) 

Objekte, die mit dem new Schlüsselwort erstellt wurden, können geändert werden, d. H. Wir können die Klasseninstanzmethode überschreiben.

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

Es sollte auch beachtet werden, dass Klassen in JS bei der Prototypdelegierung implementiert werden. Daher können wir die Implementierung der Methode im Prototyp der Klasse ändern, und diese Änderungen wirken sich auf alle vorhandenen Instanzen aus (darüber habe ich im Artikel über OOP ausführlicher gesprochen).

 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!" 

Stimmen Sie zu, solche Funktionen können uns viel Ärger bereiten.

Ein weiteres häufiges Problem ist das Zuweisen einer Instanzmethode zu einem Ereignishandler.

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

Durch Klicken auf die Schaltfläche wird der Warenkorb nicht geleert. Die Methode weist unserer Schaltfläche mit dem Namen db eine neue Eigenschaft zu und setzt diese Eigenschaft auf [], anstatt die Datenbank des Warenkorbobjekts zu beeinflussen. Es gibt jedoch keine Fehler in der Konsole, und Ihr gesunder Menschenverstand wird Ihnen sagen, dass der Code funktionieren sollte, aber nicht.

Damit dieser Code funktioniert, müssen Sie eine Pfeilfunktion schreiben:

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

Oder binden Sie den Kontext mit einer Bindung:

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

Die Eisfabrik wird uns helfen, diese Fallen zu vermeiden.

 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 }) 

Merkmale dieses Musters:

  • Das neue Schlüsselwort muss nicht verwendet werden
  • keine Notwendigkeit, dies zu binden
  • Wagen ist voll tragbar
  • Es können lokale Variablen deklariert werden, die von außen nicht sichtbar sind

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

  • Muster unterstützt die Vererbung
  • Das Erstellen von Objekten mit Ice Factory ist langsamer und benötigt mehr Speicher als das Verwenden einer Klasse (In vielen Situationen benötigen wir möglicherweise Klassen, daher empfehle ich diesen Artikel ).
  • Dies ist eine allgemeine Funktion, die als Rückruf zugewiesen werden kann

Fazit


Wenn wir über die Architektur der zu entwickelnden Software sprechen, sollten wir immer bequeme Kompromisse eingehen. Auf diesem Weg gibt es keine strengen Regeln und Einschränkungen. Jede Situation ist einzigartig. Je mehr Muster in unserem Arsenal vorhanden sind, desto wahrscheinlicher ist es, dass wir in einer bestimmten Situation die beste Architekturoption auswählen.

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


All Articles