Hea JavaScripti arendajana püüate kirjutada puhta, tervisliku ja hooldatava koodi. Lahendate huvitavaid väljakutseid, mis on küll unikaalsed, kuid ei vaja tingimata ainulaadseid lahendusi. Tõenäoliselt olete sattunud kirjutama koodi, mis näeb välja sarnane täiesti varem lahendatud probleemi lahendusega. Te ei pruugi seda teada, kuid olete kasutanud JavaScripti kujundusmuster . Kujundusmustrid on korduvkasutatavad lahendused tarkvara kujundamisel sageli esinevatele probleemidele.
Mis tahes keele eluea jooksul teeb ja testib paljusid taaskasutatavaid lahendusi suur hulk selle keele kogukonna arendajaid. Just paljude arendajate kombineeritud kogemuse tõttu on sellised lahendused nii kasulikud, kuna need aitavad meil koodi optimeeritult kirjutada, lahendades samal ajal käsitletava probleemi.
Peamised eelised, mida disainimustrid saavad, on järgmised:
Ma tean, et olete valmis siinkohal hüppama, kuid enne kui saate disainimustrite kohta kõike teada saada, vaatame üle mõned JavaScripti põhitõed.
JavaScript on tänapäeval üks populaarsemaid veebiarenduse programmeerimiskeeli. Algselt tehti see kui mingi algse veebibrauseri jaoks omamoodi 'liim' mitmesuguste kuvatavate HTML-elementide jaoks, mida nimetatakse kliendipoolseks skriptikeeleks. Netscape Navigatoriks nimetatud sel ajal sai see kuvada ainult staatilist HTML-i. Nagu võite arvata, viis sellise skriptikeele idee toona brauserisõdadesse brauseriarendustööstuse suurte mängijate, näiteks Netscape Communications (tänapäeval Mozilla), Microsofti ja teiste vahel.
Kõik suured mängijad tahtsid selle skriptikeele enda rakendamise läbi suruda, nii et Netscape tegi JavaScripti (tegelikult tegi seda Brendan Eich), Microsoft tegi JScripti ja nii edasi. Nagu saate kujutada, olid nende rakenduste erinevused suured, nii et veebibrauserite arendamine toimus iga brauseri kohta koos kõige paremini vaadatavate kleebistega, mis olid kaasas veebilehega. Peagi selgus, et vajame standardset, brauseriteülest lahendust, mis ühtlustaks arendusprotsessi ja lihtsustaks veebilehtede loomist. Seda, mida nad välja mõtlesid, nimetatakse ECMAScript .
ECMAScript on standardiseeritud skriptikeele spetsifikatsioon, mida kõik kaasaegsed brauserid üritavad toetada, ja ECMAScriptit on mitu (võiks öelda murde). Kõige populaarsem on selle artikli JavaScripti teema. Alates esmasest väljaandmisest on ECMAScript standardiseerinud palju olulisi asju ja neile, kes rohkem spetsiifika vastu huvi tunnevad, on Vikipeedias saadaval üksikasjalik loetelu standardiseeritud üksustest iga ECMAScript'i versiooni jaoks. Brauseri tugi ECMAScript versioonidele 6 (ES6) ja uuematele versioonidele on endiselt puudulik ja selle täielikuks toetamiseks tuleb see üle kanda ES5-le.
Selle artikli sisu täielikuks mõistmiseks teeme sissejuhatuse väga olulistesse keeleomadustesse, millest peame olema teadlikud enne JavaScripti kujundusmustritesse sukeldumist. Kui keegi peaks teilt küsima: „Mis on JavaScript?” võite vastata kuskil järgmistest ridadest:
JavaScript on kerge, tõlgendatud, objektorienteeritud programmeerimiskeel, millel on esmaklassilised funktsioonid, mida tuntakse kõige sagedamini veebilehtede skriptikeelena.
Eespool nimetatud määratlus tähendab öelda, et JavaScripti koodil on väike mälu jalajälg, seda on lihtne rakendada ja õppida, selle süntaks sarnaneb populaarsetele keeltele nagu C ++ ja Java. See on skriptikeel, mis tähendab, et selle koodi tõlgendatakse kompileerimise asemel. See toetab protseduurilisi, objektile orienteeritud ja funktsionaalseid programmeerimisstiile, mis muudab selle arendajatele väga paindlikuks.
Siiani oleme heitnud pilgu kõigile omadustele, mis kõlavad nagu paljud teisedki sealsed keeled, nii et vaatame, mis on JavaScripti spetsiifiline teiste keelte osas. Loetlen mõned omadused ja annan endast parima, et selgitada, miks need väärivad erilist tähelepanu.
mis on cpp-fail
See omadus oli mul varem JavaScripti alustades raskesti mõistetav, kuna tulin C / C ++ taustast. JavaScript käsitleb funktsioone esmaklassiliste kodanikena, mis tähendab, et saate funktsioone parameetritena edastada teistele funktsioonidele nagu iga muud muutujat.
// we send in the function as an argument to be // executed from inside the calling function function performOperation(a, b, cb) { var c = a + b; cb(c); } performOperation(2, 3, function(result) { // prints out 5 console.log('The result of the operation is ' + result); })
Nagu paljude teiste objektile orienteeritud keelte puhul, toetab JavaScript objekte ja üks esimesi termineid, mis objektidele mõeldes pähe tuleb, on klassid ja pärand. Siinkohal muutub see veidi keeruliseks, kuna keel ei toeta klasse selle lihtsas keeles, vaid kasutab pigem prototüübipõhist või eksemplaripõhist pärandit.
Just nüüd, ES6-s, on ametlik termin klass on sisse viidud, mis tähendab, et brauserid seda endiselt ei toeta (kui mäletate, siis kirjutamise ajal on viimane täielikult toetatud ECMAScript versioon 5.1). Oluline on siiski märkida, et kuigi mõiste „klass” on JavaScripti sisse viidud, kasutab see ikkagi kapoti all prototüübipõhist pärandit.
Prototüüpipõhine programmeerimine on objektile suunatud programmeerimise stiil, kus käitumise taaskasutamine (tuntud kui pärimine) viiakse läbi prototüüpidena toimivate delegatsioonide kaudu olemasolevate objektide taaskasutamise protsessi kaudu. Siinkohal sukeldume üksikasjalikumalt, kui jõuame artikli kujundusmustrite jaotisse, kuna seda omadust kasutatakse paljudes JavaScripti kujundusmustrites.
Kui teil on JavaScripti kasutamisel kogemusi, olete terminiga kindlasti tuttav tagasihelistamisfunktsioon . Neile, kes seda terminit ei tunne, on tagasihelistamisfunktsioon funktsioon, mis saadetakse parameetrina (pidage meeles, et JavaScript käsitleb funktsioone esmaklassiliste kodanikena) teisele funktsioonile ja käivitatakse pärast sündmuse tulekahju. Seda kasutatakse tavaliselt selliste sündmuste tellimiseks nagu hiireklõps või klaviatuuri nupuvajutus.
Iga kord, kui sündmus, mille juurde on lisatud kuulaja, käivitub (vastasel juhul on sündmus kadunud), saadetakse sõnum sünkroonselt töödeldavate sõnumite järjekorda FIFO viisil (esimene-esimene-väljund ). Seda nimetatakse sündmuse silmus .
Igal järjekorras oleval sõnumil on sellega seotud funktsioon. Kui sõnum on eemaldatud, käivitab käitamise funktsioon enne mis tahes muu teate töötlemist täielikult. See tähendab, et kui funktsioon sisaldab muid funktsioonikõnesid, tehakse need kõik enne uue sõnumi töötlemist järjekorrast. Seda nimetatakse lõpuleviimiseks.
while (queue.waitForMessage()) { queue.processNextMessage(); }
queue.waitForMessage()
ootab sünkroonselt uusi sõnumeid. Igal töödeldaval sõnumil on oma virn ja neid töödeldakse seni, kuni virn on tühi. Kui see on lõppenud, töödeldakse järjekorrast uut sõnumit, kui see on olemas.
Võib-olla olete kuulnud ka seda, et JavaScripti ei blokeerita, see tähendab, et asünkroonse toimingu sooritamisel suudab programm töödelda muid asju, näiteks kasutaja sisendi vastuvõtmist, oodates asünkroonse toimingu lõpuleviimist, mitte blokeerides peamist teostusniit. See on JavaScripti väga kasulik omadus ja just sellel teemal võiks kirjutada terve artikli; see jääb siiski selle artikli reguleerimisalast välja.
Nagu ma varem ütlesin, on disainimustrid taaskasutatavad lahendused tarkvara kujundamisel sageli esinevatele probleemidele. Vaatame mõningaid disainimustrite kategooriaid.
Kuidas saab mustri luua? Oletame, et tundsite ära sageli esineva probleemi ja teil on sellele probleemile oma ainulaadne lahendus, mida pole ülemaailmselt tunnustatud ja dokumenteeritud. Kasutate seda lahendust iga kord, kui selle probleemiga kokku puutute, ja arvate, et see on korduvkasutatav ja et arendajaskond võiks sellest kasu saada.
Kas sellest saab kohe muster? Õnneks ei. Sageli võib olla hea koodikirjutamise tava ja lihtsalt eksida midagi, mis näeb välja nagu muster, kui tegelikult see pole muster.
Kuidas saate teada, kui see, mida arvate, et tunnete ära, on tegelikult kujundusmuster?
Saades teiste arendajate arvamusi selle kohta, teades mustri loomise protsessi ja tutvudes olemasolevate mustritega hästi. On etapp, mille muster peab läbima, enne kui sellest saab täisväärtuslik muster, ja seda nimetatakse protomustriks.
Protomuster on tulevane muster kui see läbib teatud arendajate ja stsenaariumide testimise perioodi, kus muster osutub kasulikuks ja annab õigeid tulemusi. Selleks, et kogukond tunneks täieõiguslikku mustrit, tuleb teha üsna palju tööd ja dokumente - millest suurem osa jääb käesoleva artikli reguleerimisalast välja.
Kuna kujundusmuster esindab head tava, esindab anti-muster halba tava.
Anti-mustri näide oleks Object
muutmine klassi prototüüp. Peaaegu kõik JavaScripti objektid pärivad Object
(pidage meeles, et JavaScript kasutab prototüübipõhist pärimist), nii et kujutage ette stsenaariumi, kus te seda prototüüpi muutsite. Muutused Object
prototüüpi oleks näha kõigis objektides, mis pärinevad sellest prototüübist - mis oleks kõige rohkem JavaScripti objektid . See on katastroof, mis ootab juhtumist.
Teine, ülalnimetatuga sarnane näide on objektide muutmine, mis teile ei kuulu. Selle näiteks on funktsiooni tühistamine objektilt, mida kogu rakenduses kasutatakse paljudes stsenaariumides. Kui töötate suure meeskonnaga, kujutage ette segadust, mis see tekitaks; võite kiiresti kokku puutuda kokkupõrgete, ühildumatute rakenduste ja hoolduse õudusunenägudega.
Sarnaselt sellele, kuidas on kasulik teada kõiki häid tavasid ja lahendusi, on ka väga oluline teada ka halbu. Nii saate need ära tunda ja vältida vea tegemist juba eos.
Kujundusmustreid saab kategoriseerida mitmel viisil, kuid kõige populaarsem on järgmine:
Need mustrid käsitlevad objektide loomise mehhanisme, mis optimeerivad objekti loomist võrreldes põhilise lähenemisviisiga. Objekti loomise põhivorm võib põhjustada disainiprobleeme või disaini keerukust. Loomingulised kujundusmustrid lahendavad selle probleemi, kontrollides kuidagi objektide loomist. Selle kategooria populaarsemad kujundusmustrid on:
Need mustrid käsitlevad objektide suhteid. Nad tagavad, et kui süsteemi üks osa muutub, ei pea kogu süsteem koos sellega muutuma. Selle kategooria kõige populaarsemad mustrid on:
Seda tüüpi mustrid tuvastavad, rakendavad ja parandavad süsteemi erinevate objektide vahelist suhtlust. Need aitavad tagada, et süsteemi erinevates osades oleks teave sünkroonitud. Nende mustrite populaarsed näited on:
Seda tüüpi kujundusmustrid käsitlevad mitmekeermelisi programmeerimisparadigmasid. Mõned populaarsed on:
Arhitektuurilistel eesmärkidel kasutatavad kujundusmustrid. Mõned kuulsamad on:
Järgmises jaotises vaatleme mõningaid eelmainitud kujundusmustreid lähemalt koos paremaks mõistmiseks toodud näidetega.
Kõik kujundusmustrid tähistavad teatud tüüpi probleemi konkreetset tüüpi lahendusi. Pole olemas universaalset mustrite komplekti, mis sobiks alati kõige paremini. Peame õppima, millal konkreetne muster osutub kasulikuks ja kas see annab tegelikku väärtust. Kui oleme tutvunud mustritega ja stsenaariumidega, milleks need kõige paremini sobivad, saame hõlpsasti kindlaks teha, kas konkreetne muster sobib antud probleemile hästi või mitte.
Pidage meeles, et vale mustri rakendamine antud probleemile võib põhjustada soovimatuid mõjusid, nagu ebavajalik koodi keerukus, ebavajalik üldine jõudlus või isegi uue anti-mustri kudemine.
Need on kõik olulised asjad, mida tuleks kaaluda, kui mõeldakse kujundusmustri rakendamisele meie koodile. Vaatame mõningaid kujundusmustreid, mis mulle isiklikult kasulikud leidsid, ja usume, et iga vanem JavaScripti arendaja peaks olema tuttav.
Klassikalistele objektile orienteeritud keeltele mõeldes on konstruktor klassi erifunktsioon, mis lähtestab objekti mõne vaikeväärtuse ja / või saadetud väärtusega.
Tavalised viisid JavaScripti objektide loomiseks on kolm järgmist võimalust.
// either of the following ways can be used to create a new object var instance = {}; // or var instance = Object.create(Object.prototype); // or var instance = new Object();
Pärast objekti loomist on nendele objektidele omaduste lisamiseks (alates ES3-st) neli võimalust. Need on järgmised:
// supported since ES3 // the dot notation instance.key = 'A key's value'; // the square brackets notation instance['key'] = 'A key's value'; // supported since ES5 // setting a single property using Object.defineProperty Object.defineProperty(instance, 'key', { value: 'A key's value', writable: true, enumerable: true, configurable: true }); // setting multiple properties using Object.defineProperties Object.defineProperties(instance, { 'firstKey': { value: 'First key's value', writable: true }, 'secondKey': { value: 'Second key's value', writable: false } });
Kõige populaarsem viis objektide loomiseks on lokkisulg ja omaduste lisamiseks punktmärk või nurksulg. Kõik, kellel on JavaScripti kogemusi, on neid kasutanud.
Varasemalt mainisime, et JavaScript ei toeta omakeelseid klasse, kuid toetab konstruktoreid funktsiooni väljakutse eesliite 'uus' abil. Nii saame funktsiooni kasutada konstruktorina ja initsialiseerida selle omadused samamoodi nagu klassikalise keelekonstruktori puhul.
// we define a constructor for Person objects function Person(name, age, isDeveloper) { this.name = name; this.age = age; this.isDeveloper = isDeveloper || false; this.writesCode = function() { console.log(this.isDeveloper? 'This person does write code' : 'This person does not write code'); } } // creates a Person instance with properties name: Bob, age: 38, isDeveloper: true and a method writesCode var person1 = new Person('Bob', 38, true); // creates a Person instance with properties name: Alice, age: 32, isDeveloper: false and a method writesCode var person2 = new Person('Alice', 32); // prints out: This person does write code person1.writesCode(); // prints out: this person does not write code person2.writesCode();
Siin on aga veel arenguruumi. Kui mäletate, mainisin juba varem, et JavaScript kasutab prototüübipõhist pärandit. Eelmise lähenemisviisi probleem seisneb selles, et meetod writesCode
saab uuesti määratletud Person
iga eksemplari jaoks konstruktor. Seda saame vältida, seadistades meetodi funktsiooni prototüüpi:
// we define a constructor for Person objects function Person(name, age, isDeveloper) false; // we extend the function's prototype Person.prototype.writesCode = function() { console.log(this.isDeveloper? 'This person does write code' : 'This person does not write code'); } // creates a Person instance with properties name: Bob, age: 38, isDeveloper: true and a method writesCode var person1 = new Person('Bob', 38, true); // creates a Person instance with properties name: Alice, age: 32, isDeveloper: false and a method writesCode var person2 = new Person('Alice', 32); // prints out: This person does write code person1.writesCode(); // prints out: this person does not write code person2.writesCode();
Nüüd mõlemad Person
konstruktor pääseb juurde writesCode()
jagatud eksemplarile meetod.
Mis puutub eripäradesse, siis ei lakka JavaScript kunagi imestamast. Teine JavaScripti omapärane asi (vähemalt objektorienteeritud keelte osas) on see, et JavaScript ei toeta juurdepääsu modifikaatoreid. Klassikalises OOP-keeles määrab kasutaja klassi ja määrab oma liikmetele juurdepääsuõigused. Kuna JavaScript oma tavalisel kujul ei toeta klasse ega juurdepääsu modifikaatoreid, mõtlesid JavaScripti arendajad välja võimaluse seda käitumist vajadusel jäljendada.
Enne kui läheme mooduli mustri spetsiifikasse, räägime sulgemise kontseptsioonist. A sulgemine on funktsioon, millel on juurdepääs vanema ulatusele, isegi pärast seda, kui vanemfunktsioon on suletud. Need aitavad meil ulatuse abil jäljendada juurdepääsu modifikaatorite käitumist. Näitame seda näite abil:
// we used an immediately invoked function expression // to create a private variable, counter var counterIncrementer = (function() { var counter = 0; return function() { return ++counter; }; })(); // prints out 1 console.log(counterIncrementer()); // prints out 2 console.log(counterIncrementer()); // prints out 3 console.log(counterIncrementer());
Nagu näete, oleme IIFE abil sidunud loendurimuutuja funktsioonile, millele kutsuti ja mis suleti, kuid millele pääseb juurde seda suurendava lapse funktsiooni abil. Kuna me ei pääse loendurimuutujale juurde funktsiooni avaldise väljastpoolt, muutsime selle ulatuseks manipuleerimise abil privaatseks.
Sulgureid kasutades saame luua privaatse ja avaliku osaga objekte. Neid nimetatakse moodulid ja on väga kasulikud alati, kui tahame objekti teatud osi peita ja paljastada liidese ainult mooduli kasutajale. Näitame seda näites:
// through the use of a closure we expose an object // as a public API which manages the private objects array var collection = (function() { // private members var objects = []; // public members return { addObject: function(object) { objects.push(object); }, removeObject: function(object) { var index = objects.indexOf(object); if (index >= 0) { objects.splice(index, 1); } }, getObjects: function() { return JSON.parse(JSON.stringify(objects)); } }; })(); collection.addObject('Bob'); collection.addObject('Alice'); collection.addObject('Franck'); // prints ['Bob', 'Alice', 'Franck'] console.log(collection.getObjects()); collection.removeObject('Alice'); // prints ['Bob', 'Franck'] console.log(collection.getObjects());
Kõige kasulikum, mida see muster tutvustab, on objekti privaatsete ja avalike osade selge eraldamine, mis on kontseptsioon, mis on väga sarnane klassikaliselt objektile orienteeritud arendajatelt.
Kõik pole siiski nii täiuslik. Kui soovite muuta liikme nähtavust, peate koodi muutma kõikjal, kus olete seda liiget kasutanud, kuna avalikele ja privaatsetele osadele juurdepääs on erinev. Samuti ei pääse objektile pärast nende loomist lisatud meetodid juurdepääsu objekti privaatsetele liikmetele.
See muster on mooduli mustri täiustus, nagu ülalpool illustreeritud. Peamine erinevus seisneb selles, et kirjutame kogu objektiloogika mooduli privaatsesse ulatusse ja paljastame seejärel anonüümse objekti tagastamise teel lihtsalt need osad, mida soovime avalikud olla. Samuti võime muuta eraliikmete nimetusi, kui kaardistame eraliikmeid nende vastavate avalike liikmetega.
// we write the entire object logic as private members and // expose an anonymous object which maps members we wish to reveal // to their corresponding public members var namesCollection = (function() { // private members var objects = []; function addObject(object) { objects.push(object); } function removeObject(object) { var index = objects.indexOf(object); if (index >= 0) { objects.splice(index, 1); } } function getObjects() { return JSON.parse(JSON.stringify(objects)); } // public members return { addName: addObject, removeName: removeObject, getNames: getObjects }; })(); namesCollection.addName('Bob'); namesCollection.addName('Alice'); namesCollection.addName('Franck'); // prints ['Bob', 'Alice', 'Franck'] console.log(namesCollection.getNames()); namesCollection.removeName('Alice'); // prints ['Bob', 'Franck'] console.log(namesCollection.getNames());
Paljastav moodulimuster on üks vähemalt kolmest mooduli mustri rakendamise viisist. Erinevused paljastava moodulimustri ja teiste moodulimustri variantide vahel seisnevad eelkõige selles, kuidas viidatakse avalikele liikmetele. Selle tulemusena on paljastavat moodulimustrit palju lihtsam kasutada ja muuta; see võib aga teatud stsenaariumide korral osutuda habras, näiteks kasutada RMP objekte prototüüpidena pärandiahelas. Probleemsed olukorrad on järgmised:
kiireim viis c ++ õppimiseks
Üksikmustrit kasutatakse stsenaariumides, kui vajame täpselt ühte klassi eksemplari. Näiteks peab meil olema objekt, mis sisaldab millegi jaoks mingeid seadistusi. Nendel juhtudel ei ole vaja uut objekti luua, kui konfiguratsiooniobjekti on kusagil süsteemis vaja.
var singleton = (function() { // private singleton value which gets initialized only once var config; function initializeConfiguration(values){ this.randomNumber = Math.random(); values = values || {}; this.number = values.number || 5; this.size = values.size || 10; } // we export the centralized method for retrieving the singleton value return { getConfig: function(values) { // we initialize the singleton value only once if (config === undefined) { config = new initializeConfiguration(values); } // and return the same config value wherever it is asked for return config; } }; })(); var configObject = singleton.getConfig({ 'size': 8 }); // prints number: 5, size: 8, randomNumber: someRandomDecimalValue console.log(configObject); var configObject1 = singleton.getConfig({ 'number': 8 }); // prints number: 5, size: 8, randomNumber: same randomDecimalValue as in first config console.log(configObject1);
Nagu näites näha, on genereeritud juhuslik arv alati sama, nagu ka saadetud konfiguratsiooniväärtused.
Oluline on märkida, et üksikväärtuse hankimiseks peab pääsupunkt olema ainult üks ja väga tuntud. Selle mustri kasutamise negatiivne külg on see, et seda on üsna raske testida.
Vaatleja muster on väga kasulik tööriist, kui meil on stsenaarium, kus peame oma süsteemi erinevate osade vahelist suhtlust optimeeritult parandama. See soodustab esemete lahtist sidumist.
Sellel mustril on erinevaid versioone, kuid kõige põhilisemal kujul on meil mustril kaks peamist osa. Esimene on subjekt ja teine vaatlejad.
Katsealune tegeleb kõigi toimingutega, mis on seotud teatud teemaga, mille vaatlejad tellivad. Need toimingud tellivad vaatleja teatud teemale, tühistavad vaatleja teatud teema tellimuse ja teavitavad vaatlejaid teatud teemast, kui sündmus avaldatakse.
Siiski on selle mustri variatsioon, mida nimetatakse kirjastaja / tellija mustriks ja mida ma selles osas näitena kasutan. Peamine erinevus klassikalise vaatlusmustri ja kirjastaja / tellija mustri vahel on see, et kirjastaja / tellija soodustab veelgi vabamat sidumist kui vaatleja muster.
Vaatlusmustris omab subjekt viiteid tellitud vaatlejatele ja kutsub meetodeid otse objektidelt endilt, samas kui avaldaja / tellija mustris on meil kanaleid, mis toimivad sidemehena abonendi ja kirjastaja vahel. Avaldaja käivitab sündmuse ja täidab lihtsalt selle sündmuse jaoks saadetud tagasihelistamisfunktsiooni.
Kavatsen kuvada kirjastaja / tellija mustri lühikese näite, kuid huviliste jaoks on veebist hõlpsasti leitav klassikaline vaatlusmustri näide.
var publisherSubscriber = {}; // we send in a container object which will handle the subscriptions and publishings (function(container) { // the id represents a unique subscription id to a topic var id = 0; // we subscribe to a specific topic by sending in // a callback function to be executed on event firing container.subscribe = function(topic, f) { if (!(topic in container)) { container[topic] = []; } container[topic].push({ 'id': ++id, 'callback': f }); return id; } // each subscription has its own unique ID, which we use // to remove a subscriber from a certain topic container.unsubscribe = function(topic, id) { var subscribers = []; for (var subscriber of container[topic]) { if (subscriber.id !== id) { subscribers.push(subscriber); } } container[topic] = subscribers; } container.publish = function(topic, data) { for (var subscriber of container[topic]) { // when executing a callback, it is usually helpful to read // the documentation to know which arguments will be // passed to our callbacks by the object firing the event subscriber.callback(data); } } })(publisherSubscriber); var subscriptionID1 = publisherSubscriber.subscribe('mouseClicked', function(data) { console.log('I am Bob's callback function for a mouse clicked event and this is my event data: ' + JSON.stringify(data)); }); var subscriptionID2 = publisherSubscriber.subscribe('mouseHovered', function(data) { console.log('I am Bob's callback function for a hovered mouse event and this is my event data: ' + JSON.stringify(data)); }); var subscriptionID3 = publisherSubscriber.subscribe('mouseClicked', function(data) { console.log('I am Alice's callback function for a mouse clicked event and this is my event data: ' + JSON.stringify(data)); }); // NOTE: after publishing an event with its data, all of the // subscribed callbacks will execute and will receive // a data object from the object firing the event // there are 3 console.logs executed publisherSubscriber.publish('mouseClicked', {'data': 'data1'}); publisherSubscriber.publish('mouseHovered', {'data': 'data2'}); // we unsubscribe from an event by removing the subscription ID publisherSubscriber.unsubscribe('mouseClicked', subscriptionID3); // there are 2 console.logs executed publisherSubscriber.publish('mouseClicked', {'data': 'data1'}); publisherSubscriber.publish('mouseHovered', {'data': 'data2'});
See kujundusmuster on kasulik olukordades, kui peame ühe vallandatava sündmuse korral tegema mitu toimingut. Kujutage ette, et teil on stsenaarium, kus peame tegema mitu AJAX-kõnet back-end-teenusele ja seejärel sooritama muid AJAX-kõnesid sõltuvalt tulemusest. Peaksite AJAX-kõned üksteise sisse pesitsema, sattudes võib-olla olukorda, mida nimetatakse tagasihelistamise põrguks. Kirjastaja / tellija mustri kasutamine on palju elegantsem lahendus.
Selle mustri kasutamise negatiivne külg on meie süsteemi erinevate osade keeruline testimine. Meil pole elegantset viisi, kuidas teada saada, kas süsteemi liituvad osad käituvad ootuspäraselt või mitte.
Käsitleme lühidalt mustrit, mis on ka lahtisidunud süsteemidest rääkides väga kasulik. Kui meil on stsenaarium, kus süsteemi mitu osa peavad suhtlema ja kooskõlastama, oleks võib-olla hea lahendus vahendaja kasutuselevõtt.
Vahendaja on objekt, mida kasutatakse süsteemi erinevate osade vahelise suhtluse keskpunktina ja kes haldab nende vahelist töövoogu. Nüüd on oluline rõhutada, et see haldab töövoogu. Miks see oluline on?
Sest avaldaja / tellija mustriga on suur sarnasus. Võite endalt küsida, OK, nii et need kaks mustrit aitavad mõlemad objektide vahel paremat suhtlemist rakendada ... Mis vahe on?
Erinevus seisneb selles, et vahendaja tegeleb töövoogudega, samas kui kirjastaja / tellija kasutab nn tulekahju ja unusta tüüpi suhtlust. Kirjastaja / tellija on lihtsalt sündmuste koguja, see tähendab, et ta lihtsalt hoolitseb sündmuste vallandamise eest ja annab õigetele tellijatele teada, millised sündmused vallandati. Sündmuste koondajal pole vahet, mis juhtub pärast sündmuse vallandamist, mida vahendaja puhul ei tehta.
Vahva vahendaja näide on viisardi tüüpi liides. Oletame, et teil on töötamise jaoks ulatuslik registreerimisprotsess. Sageli, kui kasutajalt nõutakse palju teavet, on hea tava jagada see mitmeks etapiks.
Nii on kood palju puhtam (seda on lihtsam hooldada) ja kasutaja ei ole ülekoormatud teabe hulga eest, mida küsitakse ainult registreerimise lõpetamiseks. Vahendaja on objekt, mis tegeleks registreerimisetappidega, võttes arvesse erinevaid võimalikke töövooge, mis võivad juhtuda seetõttu, et igal kasutajal võib olla ainulaadne registreerimisprotsess.
Selle kujundusmudeli ilmselgeks eeliseks on parem suhtlus süsteemi erinevate osade vahel, mis nüüd suhtlevad kõik vahendaja ja puhtama koodibaasi kaudu.
Negatiivne külg oleks see, et nüüd oleme oma süsteemi sisestanud ühe ebaõnnestumispunkti, see tähendab, et kui meie vahendaja ebaõnnestub, võib kogu süsteem töötamise lõpetada.
Nagu me juba artiklis oleme maininud, ei toeta JavaScript oma emakeelset klassi. Objektide vahelist pärandit rakendatakse prototüüpipõhise programmeerimise abil.
See võimaldab meil luua objekte, mis võivad olla teiste loodavate objektide prototüübiks. Prototüüpi objekti kasutatakse iga konstruktori loodud objekti kavandina.
Kuna oleme sellest juba eelmistes lõikudes rääkinud, näitame lihtsat näidet selle mustri kasutamise kohta.
var personPrototype = { sayHi: function() { console.log('Hello, my name is ' + this.name + ', and I am ' + this.age); }, sayBye: function() { console.log('Bye Bye!'); } }; function Person(name, age) { name = name || 'John Doe'; age = age || 26; function constructorFunction(name, age) { this.name = name; this.age = age; }; constructorFunction.prototype = personPrototype; var instance = new constructorFunction(name, age); return instance; } var person1 = Person(); var person2 = Person('Bob', 38); // prints out Hello, my name is John Doe, and I am 26 person1.sayHi(); // prints out Hello, my name is Bob, and I am 38 person2.sayHi();
Pange tähele, kuidas prototüübi pärimine suurendab ka jõudlust, kuna mõlemad objektid sisaldavad viidet funktsioonidele, mida rakendatakse prototüübis endas, mitte igas objektis.
Käsumuster on kasulik juhtudel, kui soovime käske täitvad objektid lahutada käske väljastavatest objektidest. Kujutage näiteks ette stsenaariumi, kus meie rakendus kasutab suurt hulka API-teenuse kõnesid. Oletame, et API-teenused muutuvad. Peaksime koodi muutma kõikjal, kus muudetud API-sid kutsutakse.
See oleks suurepärane koht abstraktsioonikihi juurutamiseks, mis eraldaks API-teenust kutsuvad objektid neile rääkivatest objektidest millal API-teenuse helistamiseks. Nii väldime modifitseerimist kõigis kohtades, kus meil on vaja teenusele helistada, vaid peame muutma ainult neid objekte, mis ise helistavad, mis on ainult üks koht.
Nagu iga teise mustri puhul, peame ka teadma, millal sellise mustri järele tegelikult vajadus on. Peame olema teadlikud tehtud kompromissist, kuna lisame API-kõnedele täiendava abstraktsioonikihi, mis vähendab jõudlust, kuid säästab potentsiaalselt palju aega, kui peame käske täitvaid objekte muutma.
// the object which knows how to execute the command var invoker = { add: function(x, y) { return x + y; }, subtract: function(x, y) { return x - y; } } // the object which is used as an abstraction layer when // executing commands; it represents an interface // toward the invoker object var manager = { execute: function(name, args) { if (name in invoker) { return invoker[name].apply(invoker, [].slice.call(arguments, 1)); } return false; } } // prints 8 console.log(manager.execute('add', 3, 5)); // prints 2 console.log(manager.execute('subtract', 5, 3));
Fassaadimustrit kasutatakse siis, kui soovime luua abstraktsioonikihi avalikult näidatava ja kardina taga rakendatu vahele. Seda kasutatakse siis, kui soovitakse hõlpsamat või lihtsamat liidest alusobjektiga.
Selle mustri suurepärane näide oleks selektorid DOM-i manipuleerimisraamatukogudest nagu jQuery, Dojo või D3. Võib-olla olete märganud nende teekide kasutamisel, et neil on väga võimsad valijafunktsioonid; saate kirjutada keerulistes päringutes, näiteks:
jQuery('.parent .child div.span')
See lihtsustab valiku funktsioone palju ja kuigi see tundub pealtnäha lihtne, on selle toimimiseks kapoti all rakendatud terve keeruline loogika.
Samuti peame olema teadlikud jõudluse ja lihtsuse kompromissist. Soovitav on vältida täiendavat keerukust, kui see pole piisavalt kasulik. Eelnimetatud raamatukogude puhul oli kompromiss seda väärt, kuna need kõik on väga edukad raamatukogud.
Kujundusmustrid on väga kasulik tööriist vanem JavaScripti arendaja peaks olema teadlik. Disainimustrite spetsiifika tundmine võib osutuda uskumatult kasulikuks ja säästa palju aega projekti mis tahes elutsüklis, eriti hoolduse osas. Süsteemivajadustele hästi sobivate kujundusmudelite abil kirjutatud süsteemide muutmine ja hooldamine võib osutuda hindamatuks.
Selleks, et artikkel oleks suhteliselt lühike, ei näita me rohkem näiteid. Huviliste jaoks sai selle artikli jaoks suurepärase inspiratsiooni raamat 'Nelja jõuk' Kujundusmustrid: korduvkasutatava objektile orienteeritud tarkvara elemendid ja Addy Osmani oma JavaScripti kujundusmustrite õppimine . Soovitan soojalt mõlemat raamatut.
Seotud: JSi arendajana hoiab see mind öösiti üleval / mõistab ES6 klassi segadustJavaScript on asünkroonne, toetab esmaklassilisi funktsioone ja on prototüüpipõhine.
Kujundusmustrid on korduvkasutatavad lahendused tarkvara kujundamisel sageli esinevatele probleemidele. Need on tõestatud lahendused, kergesti korduvkasutatavad ja väljendusrikkad. Need vähendavad teie koodibaasi suurust, takistavad edasist ümbertegemist ja muudavad teie koodi kergemini teistele arendajatele arusaadavaks.
JavaScript on kliendipoolne skriptikeel brauseritele, mille Brendan Eich lõi nüüdsest Mozillast Netscape Navigatorile.
ECMAScript on standardiseeritud skriptikeele spetsifikatsioon, mida kõik kaasaegsed brauserid üritavad toetada. ECMAScript on mitu rakendust, neist kõige populaarsem on JavaScripti.
Protomuster on tulevane muster, kui see läbib teatud arendusperioodi teatud stsenaariumi ja stsenaariumid, kus muster osutub kasulikuks ja annab õigeid tulemusi.
Kui kujundusmuster esindab head tava, siis anti-muster tähistab halba tava. Anti-mustri näide oleks Object klassi prototüübi muutmine. Object prototüübi muudatusi on näha kõigis objektides, mis päranduvad sellest prototüübist (see tähendab peaaegu kõigis JavaScripti objektides).
Kujundusmustrid võivad olla loomingulised, struktuursed, käitumuslikud, samaaegsed või arhitektuursed.
Mõned ülaltoodud artiklis käsitletud näited hõlmavad konstruktori mustrit, mooduli mustrit, paljastavat mooduli mustrit, üksikut mustrit, vaatleja mustrit, vahendaja mustrit, prototüübi mustrit, käsumustrit ja fassaadimustrit.