JavaScript Design Patterns: Observatörsmönstret

i JavaScript finns det ett problem som uppstår ofta. Du behöver ett sätt att uppdatera delar av en sida som svar på vissa händelser, med de data som dessa tillhandahåller. Säg till exempel användarinmatning som du sedan projicerar i en eller flera komponenter. Detta leder till mycket push-and-pull i koden för att hålla allt synkroniserat.

det är här observer — designmönstret kan hjälpa-det möjliggör en-till-många databindning mellan element. Denna enkelriktade databindning kan vara händelsestyrd. Med detta mönster kan du bygga återanvändbar kod som löser för dina specifika behov.

i den här artikeln skulle jag vilja utforska observatörens designmönster. Det hjälper dig att lösa ett vanligt problem som du ser i skript på klientsidan. Det är en-till-många, enkelriktad och händelsestyrd databindning. Det är ett problem som uppstår ofta när du har många element som måste synkroniseras.

Jag använder ECMAScript 6 för att illustrera mönstret. Ja, det kommer att finnas klasser, pilfunktioner och konstanter. Känn dig fri att utforska dessa ämnen på egen hand om du inte redan är bekant. Jag använder delar av ES6 som endast introducerar syntaktiskt socker, så det är bärbart med ES5 om det behövs.

och jag använder testdriven utveckling (TDD) för att arbeta med mönstret. På så sätt har du ett sätt att veta hur varje komponent är användbar.

de nya språkfunktionerna i ES6 ger en viss kortfattad kod. Så, låt oss komma igång.

Händelseobservatören

en vy på hög nivå av mönstret ser ut så här:

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

när jag har lagt ut observatörsmönstret lägger jag till ett ordräkning som använder det. Ordet räkna komponenten kommer att ta denna observatör och föra samman allt.

för att initiera EventObserver gör:

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

börja med en tom lista över observerade händelser och gör det för varje ny instans. Från och med nu, låt oss lägga till fler metoder inuti EventObserver för att krossa designmönstret.

prenumerera metoden

för att lägga till nya händelser gör:

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

ta tag i listan över observerade händelser och tryck på ett nytt objekt i matrisen. Listan över händelser är en lista över återuppringningsfunktioner.

ett sätt att testa denna metod i vanlig JavaScript är följande:

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

Jag använder Node assertions för att testa denna komponent i Node. Exakt samma påståenden finns som Chai påståenden också.

notera listan över observerade händelser består av ödmjuka återuppringningar. Vi kontrollerar sedan längden på listan och hävdar att återuppringningen finns på listan.

Avregistreringsmetoden

för att ta bort händelser gör:

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

filtrera bort från listan vad som matchar återuppringningsfunktionen. Om det inte finns någon match, återuppringning får stanna på listan. Filtret returnerar en ny lista och tilldelar listan över observatörer.

för att testa denna fina metod, gör:

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

återuppringningen måste matcha samma funktion som finns på listan. Om det finns en matchning tar avregistreringsmetoden bort den från listan. Obs! testet använder funktionsreferensen för att lägga till och ta bort den.

Sändningsmetoden

för att ringa alla händelser gör:

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

detta upprepas genom listan över observerade händelser och utför alla återuppringningar. Med detta får du det nödvändiga en-till-många förhållandet till de tecknade händelserna. Du passerar i parametern data som gör att återuppringningsdata är bundna.

ES6 gör koden mer effektiv med en pilfunktion. Notera funktionen (subscriber) => subscriber(data) som gör det mesta av arbetet. Denna one-liner arrow-funktion drar nytta av denna korta ES6-syntax. Detta är en bestämd förbättring av JavaScript-programmeringsspråket.

för att testa denna Sändningsmetod, gör:

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

använd let istället för en const så att vi kan ändra värdet på variabeln. Detta gör variabeln muterbar vilket gör att jag kan omfördela sitt värde inuti återuppringningen. Att använda en let i din kod skickar en signal till andra programmerare att variabeln ändras någon gång. Detta ger läsbarhet och tydlighet i din JavaScript-kod.

detta test ger mig det förtroende som krävs för att säkerställa att observatören fungerar som jag förväntar mig. Med TDD handlar det om att bygga återanvändbar kod i vanlig JavaScript. Det finns fördelar med att skriva testbar kod i vanlig JavaScript. Testa allt och behåll det som är bra för återanvändning av kod.

med detta har vi utarbetat EventObserver. Frågan är, vad kan du bygga med detta?

Observatörsmönstret i aktion: en Blog Word Count Demo

för demo, dags att införa ett blogginlägg där det håller ordräkningen för dig. Varje tangenttryckning du anger som inmatning kommer att synkroniseras av observatörens designmönster. Tänk på det som fri textinmatning där varje händelse avfyrar en uppdatering till var du behöver den för att gå.

för att få ett ordräkning från fri textinmatning kan man göra:

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

klart! Det händer mycket i denna till synes enkla rena funktion, så vad sägs om ett ödmjukt enhetstest? På så sätt är det klart vad jag tänkte att detta skulle göra:

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

notera den något galna inmatningssträngen inuti blogPost. Jag tänker att denna funktion ska täcka så många kantfall som möjligt. Så länge det ger mig ett ordentligt ordräkning går vi faktiskt i rätt riktning.

som en sidoanteckning är detta den verkliga kraften i TDD. Man kan iterera på denna implementering och täcka så många användningsfall som möjligt. Enhetstestet berättar hur jag förväntar mig att detta ska bete sig. Om beteendet har en brist, av någon anledning, är det lätt att iterera och tweak det. Med testet finns det tillräckligt med bevis kvar för någon annan person att göra ändringar.

dags att koppla upp dessa återanvändbara komponenter till DOM. Det här är den del där du kommer att använda vanligt JavaScript och svetsa det direkt i webbläsaren.

ett sätt att göra det skulle vara att ha följande HTML på sidan:

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

följt av denna 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));

ta all din återanvändbara kod och sätt in observer designmönstret. Detta spårar ändringar i textområdet och ger dig ett ordräkning precis under det. Jag använder body.appendChild() i DOM API för att lägga till det här nya elementet. Sedan bifogar evenemangslyssnarna för att få det till liv.

Obs med pilfunktioner är det möjligt att koppla upp one-liner händelser. Faktum är att du sänder händelsestyrda ändringar till alla abonnenter med detta. () => blogObserver.broadcast() gör huvuddelen av arbetet här. Det passerar även i de senaste ändringarna i textområdet direkt in i återuppringningsfunktionen. Ja, klientsidan skript är super cool.

ingen demo är komplett utan en du kan röra och tweak, nedan är CodePen:

se pennan Observatörsmönstret av SitePoint (@SitePoint) på CodePen.

nu skulle jag inte kalla den här funktionen komplett. Det är bara en utgångspunkt för observatörens designmönster. Frågan i mitt sinne är, hur långt är du villig att gå?

blickar framåt

det är upp till dig att ta den här tanken ännu längre. Det finns många sätt du kan använda observer designmönster för att bygga nya funktioner.

du kan förbättra demo med:

  • en annan komponent som räknar antalet stycken
  • en annan komponent som visar en förhandsgranskning av inmatad text
  • förbättra förhandsgranskningen med markdown-stöd, till exempel

det här är bara några tips du kan göra för att sjunka djupare in i detta. Förbättringarna ovan kommer att utmana dina programmeringskoteletter.

slutsats

observer design pattern kan hjälpa dig att lösa verkliga problem i JavaScript. Detta löser det ständiga problemet med att hålla en massa element synkroniserade med samma data. Så ofta är fallet när webbläsaren avfyrar specifika händelser. Jag är säker på att de flesta av er nu har stött på ett sådant problem och har kört mot verktyg och beroenden från tredje part.

detta designmönster utrustar dig att gå så långt som din fantasi är villig att gå. I programmeringen abstraherar du lösningen i ett mönster och bygger återanvändbar kod. Det finns ingen gräns för hur långt detta tar dig.

jag hoppas att du ser hur mycket, med lite disciplin och ansträngning, du kan göra i vanlig JavaScript. De nya funktionerna på språket, till exempel ES6, hjälper dig att skriva en kortfattad kod som kan återanvändas.

denna artikel granskades av Giulio Mainardi. Tack till alla sitepoints peer reviewers för att göra SitePoint-innehåll så bra det kan vara!

Lämna ett svar

Din e-postadress kommer inte publiceras.