JavaScript Design Patterns: The Observer Pattern

i JavaScript er der et problem, der ofte opstår. Du har brug for en måde at opdatere dele af en side som svar på bestemte begivenheder med de data, Disse giver. Sig for eksempel brugerinput, som du derefter projicerer i en eller flere komponenter. Dette fører til en masse push-and-pull i koden for at holde alt synkroniseret.

det er her observatørens designmønster kan hjælpe — det muliggør en-til-mange databinding mellem elementer. Denne envejs databinding kan være begivenhedsdrevet. Med dette mønster kan du opbygge genanvendelig kode, der løser dine specifikke behov.

i denne artikel vil jeg gerne udforske observatørens designmønster. Det vil hjælpe dig med at løse et fælles problem, du ser i klientsiden scripting. Det er en-til-mange, envejs og begivenhedsdrevet databinding. Det er et problem, der ofte opstår, når du har mange elementer, der skal være synkroniserede.

jeg bruger ECMAScript 6 til at illustrere mønsteret. Ja, der vil være klasser, pilfunktioner og konstanter. Du er velkommen til at udforske disse emner alene, hvis du ikke allerede er bekendt. Jeg bruger dele af ES6, der kun introducerer syntaktisk sukker, så det er bærbart med ES5, hvis det er nødvendigt.

og jeg vil bruge Test-Driven-Development (TDD) til at arbejde på mønsteret. På denne måde har du en måde at vide, hvordan hver komponent er nyttig.

de nye sprogfunktioner i ES6 giver en kortfattet kode. Så lad os komme i gang.

Begivenhedsobservatøren

et billede på højt niveau af mønsteret ser sådan ud:

EventObserver│ ├── subscribe: adds new observable events│ ├── unsubscribe: removes observable events|└── broadcast: executes all events with bound data

når jeg har uddybet observatørmønsteret, tilføjer jeg et ordantal, der bruger det. Ordet tæller komponent vil tage denne observatør og bringe det hele sammen.

for at initialisere EventObserver gør:

class EventObserver { constructor() { this.observers = ; }}

Start med en tom liste over observerede begivenheder, og gør dette for hver ny forekomst. Fra nu af, lad os tilføje flere metoder inde EventObserver at konkretisere design mønster.

Abonnementsmetoden

for at tilføje nye begivenheder gør:

subscribe(fn) { this.observers.push(fn);}

Grib listen over observerede begivenheder, og skub et nyt element til arrayet. Listen over begivenheder er en liste over tilbagekaldsfunktioner.

en måde at teste denne metode i almindelig JavaScript er som følger:

// Arrangeconst observer = new EventObserver();const fn = () => {};// Actobserver.subscribe(fn);// Assertassert.strictEqual(observer.observers.length, 1);

jeg bruger Node påstande til at teste denne komponent i Node. De nøjagtige samme påstande findes også som Chai-påstande.

Bemærk listen over observerede begivenheder består af ydmyge tilbagekald. Vi kontrollerer derefter længden på listen og hævder, at tilbagekaldelsen er på listen.

Afmeldingsmetoden

for at fjerne begivenheder gør:

unsubscribe(fn) { this.observers = this.observers.filter((subscriber) => subscriber !== fn);}

filtrer ud fra listen, uanset hvad der matcher tilbagekaldsfunktionen. Hvis der ikke er nogen match, tilbagekaldelsen bliver på listen. Filteret returnerer en ny liste og tildeler listen over observatører igen.

for at teste denne dejlige metode skal du gøre:

// Arrangeconst observer = new EventObserver();const fn = () => {};observer.subscribe(fn);// Actobserver.unsubscribe(fn);// Assertassert.strictEqual(observer.observers.length, 0);

tilbagekaldelsen skal matche den samme funktion, der er på listen. Hvis der er et match, fjerner afmeldingsmetoden det fra listen. Bemærk testen bruger funktionsreferencen til at tilføje og fjerne den.

Udsendelsesmetoden

at kalde alle begivenheder gør:

broadcast(data) { this.observers.forEach((subscriber) => subscriber(data));}

dette gentager gennem listen over observerede begivenheder og udfører alle tilbagekald. Med dette får du det nødvendige en-til-mange-forhold til de abonnerede begivenheder. Du passerer i parameteren data, som gør tilbagekaldsdataene bundet.

ES6 gør koden mere effektiv med en pilfunktion. Bemærk funktionen (subscriber) => subscriber(data), der udfører det meste af arbejdet. Denne one-liner pil funktion nyder godt af denne korte ES6 syntaks. Dette er en klar forbedring i JavaScript programmeringssprog.

for at teste denne udsendelsesmetode skal du gøre:

// Arrangeconst observer = new EventObserver();let subscriberHasBeenCalled = false;const fn = (data) => subscriberHasBeenCalled = data;observer.subscribe(fn);// Actobserver.broadcast(true);// Assertassert(subscriberHasBeenCalled);

brug let i stedet for en const så vi kan ændre værdien af variablen. Dette gør variablen mutabel, som giver mig mulighed for at omfordele sin værdi inde i tilbagekaldelsen. Brug af en let i din kode sender et signal til andre programmører om, at variablen ændrer sig på et tidspunkt. Dette tilføjer læsbarhed og klarhed til din JavaScript-kode.

denne test giver mig den nødvendige tillid til at sikre, at observatøren arbejder som jeg forventer. Med TDD handler det om at opbygge genanvendelig kode i almindelig JavaScript. Der er fordele ved at skrive testbar kode i almindelig JavaScript. Test alt, og behold det, der er godt til genbrug af kode.

med dette har vi uddybet EventObserver. Spørgsmålet er, hvad kan du bygge med dette?

Observatørmønsteret i aktion: en Blogordtælling Demo

til demoen er det tid til at indføre et blogindlæg, hvor det holder ordtællingen for dig. Hvert tastetryk, du indtaster som input, synkroniseres af observatørens designmønster. Tænk på det som gratis tekstinput, hvor hver begivenhed fyrer en opdatering til, hvor du har brug for den.

for at få et ordtælling fra gratis tekstinput kan man gøre:

const getWordCount = (text) => text ? text.trim().split(/\s+/).length : 0;

færdig! Der foregår meget i denne tilsyneladende enkle rene funktion, så hvad med en ydmyg enhedstest? På denne måde er det klart, hvad jeg havde til hensigt at gøre:

// Arrangeconst blogPost = 'This is a blog \n\n post with a word count. ';// Actconst count = getWordCount(blogPost);// Assertassert.strictEqual(count, 9);

Bemærk Den noget skøre inputstreng indeni blogPost. Jeg har til hensigt, at denne funktion dækker så mange kantsager som muligt. Så længe det giver mig en ordentlig ord tæller vi er på vej, faktisk, i den rigtige retning.

som en sidebemærkning er dette TDD ‘ s virkelige magt. Man kan gentage denne implementering og dække så mange brugssager som muligt. Enhedstesten fortæller dig, hvordan jeg forventer, at dette opfører sig. Hvis adfærden har en fejl, af en eller anden grund, er det let at gentage og tilpasse det. Med testen er der nok beviser tilbage til, at enhver anden person kan foretage ændringer.

tid til at forbinde disse genanvendelige komponenter til DOM. Dette er den del, hvor du kommer til at bruge almindeligt JavaScript og svejse det lige ind i Bro.sereren.

en måde at gøre det på ville være at have følgende HTML på siden:

<textarea placeholder="Enter your blog post..." class="blogPost"></textarea>

fulgt op af dette JavaScript:

const wordCountElement = document.createElement('p');wordCountElement.className = 'wordCount';wordCountElement.innerHTML = 'Word Count: <strong>0</strong>';document.body.appendChild(wordCountElement);const blogObserver = new EventObserver();blogObserver.subscribe((text) => { const blogCount = document.getElementById('blogWordCount'); blogCount.textContent = getWordCount(text);});const blogPost = document.getElementById('blogPost');blogPost.addEventListener('keyup', () => blogObserver.broadcast(blogPost.value));

Tag al din genanvendelige kode og sæt observatørens designmønster på plads. Dette sporer ændringer i tekstområdet og giver dig et ordantal lige under det. Jeg bruger body.appendChild() i DOM API til at tilføje dette nye element. Derefter vedhæftes begivenhedslytterne for at bringe det til live.

Bemærk med pilfunktioner er det muligt at forbinde one-liner-begivenheder. Faktisk sender du begivenhedsdrevne ændringer til alle abonnenter med dette. Den () => blogObserver.broadcast() gør hovedparten af arbejdet her. Det passerer endda i de seneste ændringer i tekstområdet lige ind i tilbagekaldsfunktionen. Ja, klientsiden scripting er super cool.

ingen demo er komplet uden en, du kan røre ved og justere, nedenfor er CodePen:

se pennen Observatørmønsteret ved SitePoint (@SitePoint) på CodePen.

nu vil jeg ikke kalde denne funktion komplet. Det er kun et udgangspunkt for observatørens designmønster. Spørgsmålet i mit sind er, hvor langt er du villig til at gå?

ser fremad

det er op til dig at tage denne ide endnu længere. Der er mange måder, du kan bruge observer-designmønsteret til at opbygge nye funktioner.

du kan forbedre demoen med:

  • en anden komponent, der tæller antallet af afsnit
  • en anden komponent, der viser en forhåndsvisning af indtastet tekst
  • forbedre forhåndsvisningen med understøttelse af markering, for eksempel

dette er kun et par ideer, du kan gøre for at synke dybere ned i dette. Forbedringerne ovenfor vil udfordre dine programmeringskoteletter.

konklusion

observatørens designmønster kan hjælpe dig med at løse virkelige problemer i JavaScript. Dette løser det flerårige problem med at holde en masse elementer synkroniseret med de samme data. Som det ofte er tilfældet, når bro.ser fyrer specifikke begivenheder. Jeg er sikker på, at de fleste af jer nu er stødt på et sådant problem og har kørt mod værktøjer og tredjepartsafhængigheder.

dette designmønster udstyrer dig til at gå så langt som din fantasi er villig til at gå. I programmeringen abstraherer du løsningen i et mønster og bygger genanvendelig kode. Der er ingen grænse for, hvor langt dette vil tage dig.

jeg håber du ser, hvor meget, med lidt disciplin og indsats, du kan gøre i almindelig JavaScript. De nye funktioner på sproget, såsom ES6, hjælper dig med at skrive en kortfattet kode, der kan genbruges.

denne artikel blev fagfællebedømt af Giulio Mainardi. Tak til alle sitepoints peer-korrekturlæsere for at gøre SitePoint-indhold til det bedste, det kan være!

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.