JavaScript ist insofern interessant, als dass es keine bestimmte Struktur vorgibt. „Bring Your Own Organization“, wenn Sie so wollen. Da ich immer mehr JavaScript in Web-App-Websites schreibe, wird dies immer interessanter. Wie Sie Ihr JavaScript strukturieren, ist sehr wichtig, denn
- Richtig gemacht, macht es den Code für andere und für Sie selbst einfacher zu verstehen, wenn Sie Ihren eigenen Code wieder aufrufen.
- Eine festgelegte Struktur hilft, zukünftigen Code sauber zu halten und Ihre erklärten Best Practices zu fördern.
- Es macht Ihren Code testbar.
Bevor ich weitermache, sei gesagt, dass ich weit davon entfernt bin, ein Meister von JavaScript zu sein. Es gibt viele kluge Leute in der JavaScript-Welt, die das schon länger tun und darüber reden, als ich wusste, was ein div ist.
Dieses Thema beschäftigt mich in letzter Zeit, da ich viel mehr JavaScript für CodePen schreibe, das sehr JavaScript-lastig ist. Wir verwenden seit Beginn das „Module Pattern“ und es hat uns bisher recht gut gedient, denke ich.
Dann habe ich diesen Artikel über die Entstehung der Treehouse-Werbung veröffentlicht und darin auch das Module Pattern für das dortige JavaScript verwendet. Louis Lazaris hat darauf hingewiesen, dass er auch so gerne JavaScript schreibt, und eine kleine Diskussion begonnen. Louis folgte dem mit einem Artikel, der sein bevorzugtes Strukturmuster erklärt.
Das Konzept
Es ist einfacher, solche Konzepte zu verstehen, wenn ein Beispiel zum Aufbau vorhanden ist. Nehmen wir an, wir entwickeln ein Nachrichten-Widget. Es listet einige Nachrichtenartikel auf und hat einen Button, mit dem Sie weitere Nachrichtenartikel laden können.
Das Modul
Bescheidene Anfänge
var NewsWidget = {
};
Nur ein Objekt. Ich mag meine JavaScript-Variablen, die mit einem Kleinbuchstaben beginnen, aber Module haben einen Großbuchstaben am Anfang. Nur eine Konvention, die die Lesbarkeit des Codes erleichtert.
Einstellungen
Das Nachrichten-Widget wird wahrscheinlich einige wichtige Zahlenwerte damit assoziiert haben (z. B. wie viele Artikel darin enthalten sind). Auch einige wichtige Elemente (DOM-Knoten), auf die regelmäßig zugegriffen werden muss.
Es wird eine Reihe von Unterfunktionen dieses Moduls geben, die kleine, spezifische Dinge tun. Viele davon müssen möglicherweise auf diese Einstellungen und „gecachten“ Elemente zugreifen können. Daher werden wir die Einstellungen für jede von ihnen verfügbar machen.
var s,
NewsWidget = {
settings: {
numArticles: 5,
articleList: $("#article-list")
}
};
Den Zugriff auf die Unterfunktion werden wir gleich besprechen.
Init-Funktion
Um „loszulegen“, wird nur **eine** Funktion aufgerufen. Dies ist bei allen Modulen konsistent, sodass Sie genau wissen, wo Sie nachschauen müssen, wenn Sie mit dem Lesen des Codes beginnen und herausfinden, was wann passiert.
var s,
NewsWidget = {
settings: {
numArticles: 5,
articleList: $("#article-list"),
moreButton: $("#more-button")
},
init: function() {
// kick things off
s = this.settings;
}
};
Das Erste, was die init-Funktion tut, ist, die Variable s (die wir auf derselben Ebene wie das Modul deklariert haben) zu einem Zeiger auf die Einstellungen zu machen. Aufgrund der Stelle, an der s deklariert wurde, bedeutet dies, dass alle Unterfunktionen des Moduls Zugriff auf die Einstellungen haben. Gut gemacht.
Binden von UI-Aktionen
Alex Vasquez hat für uns bei CodePen eine Konvention etabliert, bei der wir eine Funktion nur zum Binden der UI-Events haben. Sie schreiben nie Code, der beim Binden etwas „tut“, Sie binden nur und rufen dann eine andere, passend benannte Unterfunktion auf.
var s,
NewsWidget = {
settings: {
numArticles: 5,
articleList: $("#article-list"),
moreButton: $("#more-button")
},
init: function() {
s = this.settings;
this.bindUIActions();
},
bindUIActions: function() {
s.moreButton.on("click", function() {
NewsWidget.getMoreArticles(s.numArticles);
});
},
getMoreArticles: function(numToGet) {
// $.ajax or something
// using numToGet as param
}
};
Kombinieren von Dateien
Wahrscheinlich ist das Nachrichten-Widget nicht das einzige JavaScript, das Sie auf einer Seite haben werden. Es wird viele Module geben, die Sie laden müssen. Vielleicht befindet sich das Nachrichten-Widget auf jeder Seite Ihrer Website und gehört daher in eine global.js-Datei. Diese global.js-Datei wird letztendlich eine Menge von Modulen sein, die zusammengeführt werden, und dann eine große Auftaktparty.
Es gibt alle möglichen Arten, diese Verkettungsangelegenheiten zu handhaben. Es könnte ein Entwickler-Tool wie CodeKit und seine Fähigkeit, Anhänge/Vorbereitungen durchzuführen, sein. Es könnte ein schickes Grunt.js-Build-Skript-Ding sein.
Bei CodePen verwenden wir Ruby on Rails und seine Asset-Pipeline. Für uns wäre die global.js-Datei also so etwas wie
//= require common/library.js
//= require module/news-widget.js
//= require module/some-other-widget.js
(function() {
NewsWidget.init();
SomeOtherModule.init();
})();
Das ist alles, Leute
Ziemlich befriedigende Art, JavaScript zu schreiben, finde ich. Es erfüllt die drei Punkte, die ich zur Bedeutung von Struktur gemacht habe.
Bezüglich des Testpunkts weiß ich, dass er nicht viel diskutiert wurde, aber ich bin sicher, Sie können sich vorstellen, wie einfach es ist, für kleinere, spezifische Funktionen, die für spezifische Aufgaben zuständig sind, Assertionen zu schreiben. So wird die meiste JavaScript-Tests durchgeführt (z. B. Jasmine). Zum Beispiel in irgendeiner Form von Code oder einer anderen: „Ich behaupte, dass, wenn diese Funktion diesen Wert erhält, etwas anderes passiert und gleich einem anderen Wert ist.“
Das ist nur die Spitze des Eisbergs. Ich habe gerade erst damit angefangen, aber Learning JavaScript Design Patterns (kostenlos online zu lesen!) von Addy Osmani scheint viel tiefer in dieses Kaninchenloch zu gehen.
Sie sollten wahrscheinlich „function()“ aus der zweiten Zeile jedes Codebeispiels entfernen (außer dem ersten und letzten). :-)
Doh! Behoben.
Tolle Erklärung, Mann…
Super Sauce. Ziemlich ähnlich dem, was ich getan habe, aber sauberer. Ich mag wirklich, wie man die Einstellungen macht. Danke
Ich empfehle, sich Marionette anzusehen (http://marionettejs.com/). Ich verwende ihre Module/Views/Router/Controller gerne, auch wenn ich Backbone nicht zur Interaktion mit einem REST-Backend verwende. Marionette bietet mir eine sinnvolle Möglichkeit, Code zu strukturieren. Es kümmert sich auch um einige der Boilerplate-Codes und Speicherverwaltungsprobleme.
Der Entwickler Derick Bailey bietet auch ein schönes JSFiddle, um Ihnen den Einstieg zu erleichtern (http://jsfiddle.net/derickbailey/M5J8Q/). Es ist sehr vereinfacht, aber ein guter Ausgangspunkt, wenn Sie lernen oder herumprobieren möchten.
Wenn Sie dieselbe Variable „s“ für die Einstellungen jedes Moduls verwenden und sie dann alle in dieselbe global.js importieren, würden sie sich dann nicht gegenseitig stören?
Das ist ein guter Fang meiner Meinung nach, sie sollten besser einen Modullader verwenden (require.js oder Ähnliches) oder eine automatisch ausgeführte Funktion verwenden (die
um lokale Variablen einzuschließen.
Sie sollten lieber ein bestehendes Modulmuster verwenden (nochmal, im AMD-Stil), anstatt es neu zu kodieren.
Nein, weil die Module sequenziell initialisiert werden. Aber ich würde ‚this.settings‘ (oder eine lokale Kurzschreibweise, wenn ich ‚this.settings‘ oft brauche) anstelle von ‚s‘ verwenden.
@atmin Das hat mich auch gestört. Warum das globale Namensraum für einen Kurzschluss verunreinigen? Warum nicht die Variable s lokal setzen und als Kurzschluss verwenden, oder einfach die Semantik überspringen und s als Einstellungs-Objekt verwenden?
Gut, sie werden sequenziell initialisiert. Aber ich dachte, der Grund, warum
saußerhalb des Moduls und nicht als lokale Variable innerhalb derinit-Funktion deklariert wurde, war, damit andere Methoden desselben Moduls zukünftig leichter auf die Einstellungen zugreifen können. WennNewsWidget.getMoreArticlesaufsverweist, wird es auf die falschen Einstellungen verweisen.Tatsächlich glaube ich, dass es im Beispielcode, innerhalb des Event-Handlers, wahrscheinlich zu einem Fehler kommen würde, da er
s.numArticlesreferenziert und nach der Initialisierung anderer Module ausgelöst würde.Hier ist ein Beispiel dafür, was ich meine: http://codepen.io/jetpacmonkey/pen/gkmei
Ich stimme Michael im Allgemeinen zu. Früher habe ich meine Module so geschrieben, wie im Beitrag gezeigt, und bin schnell auf Namenskollisionen gestoßen. Jetzt schreibe ich meine Module mit dem Pattern der selbstausführenden Funktion, wodurch eine „Kapselung“ für private Variablen/Methoden usw. für das Modul bereitgestellt wird. Probieren Sie etwas Ähnliches
var NewsWidget = (function(){ var s = { 'internal' : 'settings' }, me = {}; function setupBindings() { //do UI bindings } me.init = function() { setupBindings(); //do other important setup things } return me; }());Übrigens scheint die Einrückung nicht richtig zu funktionieren … oder ich übersehe etwas, wie man Code in einem <pre> formatiert
Was Brendan gesagt hat :)
Die Einrückung ist in Ordnung. Die Vorschau scheint kaputt zu sein.
Globale Variablen sind eines der schlimmsten Anti-Muster in jeder Programmiersprache. Das ist fast immer das Erste, was man in einem CS-101-Kurs an jeder Universität lernt. JavaScript macht die Verwendung dieses Anti-Musters einfach und verleitet fast dazu. Warum? Weil man sich oft inmitten eines Closure-Scopes befindet, ohne auf eine benötigte Variable zugreifen zu können, also erstellt man eine Variable in einem übergeordneten Scope. Chris sagte es selbst: „Wir werden die Einstellungen für jede [Unterfunktion] verfügbar machen.“ Das Beste, was Sie mit Ihren globalen Variablen machen können, ist, sie als „const“ zu deklarieren und sie unveränderlich zu machen. Wenn JS Sie wegen illegalen Zugriffs anschreit, wissen Sie, dass Sie es falsch verwenden. Die anderen Lösungen sind in einigen der anderen Kommentare aufgeführt. Verwenden Sie Namespacing.
Irgendetwas scheint seltsam mit Ihrem Modulmuster zu sein: Sie deklarieren das Einstellungs-Objekt und weisen articleList einem jQuery-Objekt zu. Das bedeutet, dass Ihr DOM bereit sein muss, wenn Sie diese JavaScript-Datei einbinden. Wenn das stimmt, was ist dann der Sinn einer Init-Funktion, die Events an den DOM bindet?
Laden Sie alle Skripte am Ende, der DOM ist bereit, wenn Sie das Skript aufrufen.
Es wäre wahrscheinlich flexibler, wenn anstatt einer festen ID für die Liste der Artikel, diese als Parameter an init() übergeben werden könnte.
Zustimmung. Genau das mache ich, aber ich mag Standardwerte, die ich überschreiben kann.
Jeremy: Ich glaube, das würde nicht passieren. Das ‚s‘ verweist auch auf ‚this‘, was meiner Meinung nach verhindert, dass sich Dinge dank des lexikalischen Geltungsbereichs von Funktionen überschreiben. Wenn ich falsch liege, korrigiere ich mich natürlich.
Das
thissteht auf der rechten Seite des Gleichheitszeichens. Die Variables, der tatsächlich etwas zugewiesen wird, hat globalen Geltungsbereich (was der Grund dafür war, dass sie außerhalb des Moduls deklariert wurde), was bedeutet, dass, wenn Ruby on Rails nichts Besonderes tut, dieinitdes zweiten Moduls die Einstellungen des ersten überschreiben würde, und alle späteren Aufrufe einer Funktion innerhalb des ersten Moduls, diesverwendet, würden sich auf die Einstellungen des zweiten Moduls beziehen.Könnte das auch so geschrieben werden? `
Mehr OOP, schätze ich?
Ich denke, ja, das könnte so geschrieben werden. Chris‘ Beispiel konzentriert sich auf strengeres Namespacing. Wo ‚NewsWidget‘ eine Master-Klasse mit daran angehängten Unterklassen ist: ‚settings‘, ‚init‘ usw.
Tatsächlich entspricht die von Chris verwendete Methodik dem Singleton-Designmuster und ist in OOP-Sprachen sehr verbreitet.
Sie können im Internet nach Situationen suchen, in denen es genau richtig ist, es zu verwenden.
Danke, dass Sie meinen Artikel erwähnen, Chris.
Für Interessierte empfehle ich besonders einige der Kommentare, die in meinem Artikel gepostet wurden und einige großartige Vorschläge enthielten. Besonders gut hat mir der von Maciej Baron gefallen
http://www.impressivewebs.com/my-current-javascript-design-pattern/#comment-32835
Wie wäre es damit –> http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth
Das ist kein Modulmuster, das ist ein einfaches Objekt-Literal. Das Modulmuster ist etwas mehr als das, und diese Bindungsfunktion ist keine wirkliche gute Entwicklungsweise, etwas wie das: https://gist.github.com/4504091
Ich habe ein Modulmuster-Bootstrap erstellt, schauen Sie sich das an: https://github.com/guilhermepontes/module-pattern-bootstrap
Es gibt viele Möglichkeiten, das Modulmuster zu realisieren, das Objekt-Literal ist eine davon. Ihr Text, um hier zu verlinken… siehe dies
Dieser Gist repräsentiert eine Modul-App: https://gist.github.com/4504159
Ich verwende einen ähnlichen Ansatz, aber mit einer Funktion (wegen der automatischen Initialisierung). Sie kann leicht als Konstruktor angepasst werden
Die Tatsache, dass Sie das Schlüsselwort
varvor _somePrivateMethod nicht verwenden, bedeutet, dass es im globalen Geltungsbereich platziert wird. Ihr Beispiel ist ziemlich gut, aber damit Ihre Methode wirklich privat ist, benötigen Sievar, um den Geltungsbereich lokal zu halten.var _somePrivateMethod = function() { ... }Guter Hinweis, danke.
Hallo, werfen Sie einen Blick auf das aktualisierte Beispiel – im vorherigen habe ich ein wenig geschludert und in einzelnen Instanzen (kein ‚new‘) zeigt ‚this‘ auf das ‚window‘-Objekt. Hier sind aktualisierte Beispiele
Sie können es auf jsFiddle sehen
* Standardbeispiel (einzeln, Auto-Init) – http://jsfiddle.net/nightman/jxZ5n/1/
* Beispiel mit mehreren Instanzen, kein Auto-Init – http://jsfiddle.net/nightman/B8ECk/1/
Grüße
Karol – Ihre Implementierung gefällt mir am besten.
Hier ist Ihr Beispiel für die Version mit mehreren Instanzen, aber mit Auto-Init.
http://jsfiddle.net/B8ECk/2/
Sieht wirklich gut aus! Danke.
Hallo Cooper, werfen Sie einen Blick auf das aktualisierte Beispiel – im vorherigen habe ich ein wenig geschludert und in einzelnen Instanzen (kein ‚new‘) zeigt ‚this‘ auf das ‚window‘-Objekt. Hier sind aktualisierte Beispiele
Grüße
Danke fürs Teilen, ich benutze die gleiche Methode, aber als Singleton, schauen Sie sich das an
http://robdodson.me/blog/2012/08/08/javascript-design-patterns-singleton/
Ich bin bei weitem kein JS-Profi. Ich habe von diesem Muster von ein paar Kollegen gelernt, als wir vor etwa 6 Monaten für einen großen Kunden gearbeitet haben. Seitdem architekiere ich mein gesamtes JS so. Es hat eine ganze Reihe von Vorteilen: 1) Sie können jedes Modul separat in Ihrem CMS speichern und beim Erstellen verketten. 2) Wenn Sie modulares HTML und CSS geschrieben haben, können Sie funktionale Module mit Präsentationsmodulen kombinieren. 3) Wenn Sie das Muster konsequent anwenden, ist es einfach, JSON damit zu erstellen.
Der „erweiterte“ Ansatz, den ich gelernt habe, ist, dass Sie ein Objekt erstellen können, das alle Ihre Module darstellt, es dem Fenster zuweisen und dann einen „Master“-Init erstellen, um Ihre Module beim Laden des Dokuments auszuführen.
window.paceaux = {
this.init = function(){
NewsWidget.init();
ArticleWidget.init();
},
NewsWidget ={
//blah code
this.init= function (){
}
},
ArticleWidget= {
this.init = function (){
}
}
};
//———–
//Document ready
//
$(function () {
window.paceaux.init();
});
Das Hauptproblem dabei ist, dass es sehr monolithisch ist, da es hartkodierte Abhängigkeiten vom DOM hat, es ist ein Singleton, so dass Sie nicht mehrere Widgets pro Seite haben können, und es wäre schwierig zu testen.
Der Stil, den ich persönlich für diese Art von Dingen bevorzuge, ist der Stil, den man in den *Bootstrap-Plugins* sieht. Das heißt
man verpackt alles in eine selbstaufrufende Funktion
in der ein konstruierbares Objekt ist, das die gesamte Widget-Logik enthält.
Abhängigkeiten werden dem Konstruktor übergeben, was Unit-Tests erleichtert und Sie können mehrere Widgets pro Seite haben
Sie können auch sehr einfach ein jQuery-Plugin und eine deklarative data-* API daraus erstellen, da Ihre gesamte Logik in einem sauberen Objekt verpackt ist. Es ist im Grunde Boilerplate-Code und macht die Dinge sehr flexibel.
Ich habe ein Beispiel für ein voll funktionsfähiges Widget vorbereitet, das genau zeigt, was ich meine, auf Codepen. Es ist *stark* im JS kommentiert, damit jeder es nachvollziehen kann. Es demonstriert auch mehrere Widgets auf einer Seite :)
Großartig, ich hoffe, es wird mehr von dieser Diskussion geben..
Es gibt viele JS-Muster, die verwendet werden können, obwohl wir manchmal wissen müssen, ob es sinnvoll ist, ein Muster zu verwenden
Ihre Struktur ist korrekt, aber sie benötigt noch ein paar Dinge, bevor sie wirklich ein Modul ist, d.h. private Member kapseln, eine öffentliche API bereitstellen und Namespacing. Ich empfehle Ihnen dringend, den ursprünglichen Post zum Modulmuster von Douglas Crockford zu lesen.
Im Wesentlichen haben Sie eine sich selbst ausführende Funktion mit privaten Membern
News = function() { var foo = 'bar'; var _init = function() { // do init stuff here } function setupBindings() { // do jQuery bindings } }();…die mit einer öffentlichen API freigibt
News = function() { var foo = 'bar'; var _init = function() { // do init stuff here } function setupBindings() { // do jQuery bindings } return { init: _init } }();…die wie folgt beim DOM-Laden aktiviert werden kann
oder so, wenn
_initParameter akzeptiert$(document).ready(function(){ News.init('#news_sidebar'); });Das scheint ein einfacher Weg zu sein, um mich in das Schreiben von modularisiertem Code einzuarbeiten.
Außerdem hatte ich Addys Buch nicht gesehen, danke für den Hinweis!
kleiner Tippfehler
Sie haben ein „S“ in Addys Nachnamen vergessen.
Darf ich Ihnen vorschlagen, sich die BEM-Methodik anzusehen.
BEM ist eine Methode, Ihren Code zu organisieren, um den Prozess der Entwicklung und zukünftigen Wartung zu vereinfachen.
Es gab auch eine Reihe von Artikeln über BEM bei SmashingMagazine. Und Projektstruktur wurde in Teil 3 beschrieben.
Das sieht nach einer guten Art aus, Code zu schreiben.
Das
{ spam: eggs }sieht für mich wie ein Wörterbuch aus.Funktioniert das mit reinem JS?
Das war exzellent! Wie immer…
Ich habe ein paar ähnliche Dinge, aber so mache ich es im Moment, wenn es schlecht ist, bessere Wege usw. bitte zögern Sie nicht, Ratschläge zu geben.
/******** * * Page Specific / Site Specific JS File * File Saved as: CustomJS.js * ********/ ;(function(window){ "use strict"; var varName = function(methodName){ this.data = { reqMethod : (typeof methodName !=='undefined') ? methodName : false } this.GenericJS = new GenericJS(); this.init(); }; var o, gen; varName.prototype.init = function(){ o = this.data, gen = this.GenericJS; //-- dynamically call the required method on page call if(o.reqMethod!==''){this[o.reqMethod]()}; }; //-- method desc varName.prototype.publicMethodName = function(){ gen.CheckEmailAddress(newEmail); } //-- method desc varName.prototype.publicMethodNameTwo = function(){ //-- call to my private method APrivateMethod(); } function APrivateMethod(){ //-- private stuff done here } window.varName = varName; }(window)); /******** * * Generic JS File * File saved as: GenericJS.js * *******/ ;(function(window){ "use strict"; var GenericJS = function(methodName){ this.data = {} this.init(); }; // var o, gen; varName.prototype.init = function(){ o = this.data; }; GenericJS.prototype.CheckEmailAddress = function(){return _checkEmail(email);} GenericJS.prototype.GenericPublicMethodTwo = function(){return _AnotherPrivateMethod();} function _checkEmail(em){ // do stuff here } function _AnotherPrivateMethod(){ // do stuff here } window.GenericJS = GenericJS; }(window)) /*--- USE //-- include -- path/to/GenericJS.js //-- include -- path/to/CustomJS.js //--script tag ((typeof varName) !== 'undefined') ? new varName("publicMethodName") : null; By sending the method name within the set up 'new varName("publicMethodName")' then i can use CustomJS.js for all my site pages and simply call the page method: eg: new varName("homeDetails"); for say index.php or new varName("signup"); for the signup.php page, or if each page has its own file HomePage.js then I would not be required to call it in this way but would call it within the main init method: EG: new varName() then in the HomePage.js init method i would do all the work there and the if(o.reqMethod!==''){this[o.reqMethod]()}; is not required. */Chris, ich gebe Ihnen eine „A“ für Bemühung und ein „B-“ für Ausführung.
Wie andere bereits angemerkt haben, ist die Variable „s“ global und birgt somit die Gefahr, von anderen Modulen überschrieben zu werden. Es ist auch kein echtes Modulmuster. Wenn Sie möchten, dass es ein Singleton ist, könnten Sie Ihr Beispiel leicht wie folgt modifizieren (was anderen Beiträgen in den Kommentaren ähnelt)
Ich denke, solange er das Ganze in eine sich selbst ausführende anonyme Funktion verpackt, gibt es keine Bedenken wegen des globalen „s“. Und das wäre ähnlich Ihrem obigen Vorschlag (den ich übrigens mag).
Ausgezeichnet, Peter. Sauber und funktioniert gut. Ich werde diesem Muster bei zukünftigem Code folgen!
Wenn das Ganze in eine sich selbst ausführende anonyme Funktion verpackt ist, dann wäre „s“ nicht global, aber dann wäre auch NewsWidget nicht global, was bedeutet, dass er NewsWidget.init() außerhalb der sich selbst ausführenden Funktion nicht aufrufen könnte. Die Umgehung wäre, NewsWidget ohne „var“ zu deklarieren, was es implizit zu einem globalen Objekt machen würde, aber ich würde das generell abraten.
Anstatt das Ganze in eine sich selbst ausführende anonyme Funktion zu verpacken, habe ich im Grunde die anonyme Funktion beibehalten, aber sie gibt ein Objekt zurück (und das weise ich dann NewsWidget zu). Es ist ähnlich, aber diese subtile Änderung macht einen großen Unterschied.
Ich denke, so wie ich es sehe, wäre das die Struktur für das gesamte JS einer einzelnen Website/App. Daher wäre es egal, ob „s“ global für die Anwendung ist, oder ob das Hauptobjekt außerhalb der Funktion nicht zugänglich ist. Insbesondere wenn es sich um eine kleine App handelt, die keine potenziellen Konflikte und Ähnliches hat.
Das gefällt mir, weil es die globale Einstellungsvariable behebt, aber wie würde ich das mit dem Modulmuster unter Verwendung von Closures verwenden, um Funktionen privat/öffentlich zu machen (Kapselung)? Wenn jede Funktion im Rückgabewert steht, ist sie öffentlich. Wenn Sie Funktionen aus dem Rückgabewert entfernen, machen Sie sie privat, verlieren aber den Zugriff auf die Einstellungs variable.
@Jerry, wenn Sie Funktionen aus dem Rückgabewert entfernen, machen Sie sie privat. Sie haben immer noch Zugriff auf die Einstellungen über die private Variable „s“. Zum Beispiel
In diesem Beispiel habe ich eine private Methode, „somePrivateFunction“, hinzugefügt, die über die private Variable „s“ Zugriff auf die Einstellungen hat. Alternativ hätte ich etwas tun können, wie z. B. eine Referenz auf „this“ als einen der Parameter nehmen
Und dann, wo ich die private Funktion aufrufe
Sehr schön, Peter. Ich experimentiere gerade auch mit diesem modularen Muster!
Eine andere Möglichkeit, dies zu erreichen, ist.
Toller Artikel! Ich habe mit Codes gearbeitet, die diesen Mustern folgen, aber ich hatte einige Zweifel. Danke.
Tolle Erklärung, Mann. Gerne gelesen :)
Ein toller Beitrag zur Erstellung von Skripten auf Basis von Mustern. Viel benötigte Richtlinien, um Fehler bei der Skripterstellung zu vermeiden.
Hi, ich glaube, Sie meinten Addy Osmani!
Großartiger Artikel!
Beste Grüße
Gute Lektüre, ich stimme auch den Kommentaren von Peter Foti oben zu. Ich denke, Sie würden sich in Sparky.js verlieben, wenn Sie es sich ansehen würden – http://sparkyjs.com/
Ich bin kein Profi in JS, aber mein „Aha!“-Moment mit JavaScript kam mit dem „Revealing Module Pattern“
http://addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript
Ich stimme dem Autor zu, dass es eine Verbesserung ist. Ich hatte bereits etwas OOP (soweit es ging) in PHP betrieben, daher hat mir ein Objekt mit öffentlichen und privaten Methoden gefallen.
Ein toller kleiner Artikel über das Modul-Pattern! Ich habe mich in letzter Zeit wirklich damit beschäftigt, mit der Rails Asset Pipeline und dem Versuch, unser JavaScript-Chaos bei der Arbeit mit require.js zu organisieren. Es macht meiner Meinung nach alles ordentlicher, und ich liebe es, Dateien mit nur ein paar hundert Zeilen JS anstatt ein paar tausend zu haben.
Danke Chris…
Ehrlich gesagt, Sie haben gerade die Oberfläche der Closure-Logik angekratzt. Es hat einige Zeit gedauert, bis ich diese Geheimnisse verstanden habe, aber sobald das gesamte Closure-Thema verstanden ist, offenbart es die Vorteile der JS-Programmierung.
Erweitern Sie einfach den Begriff „Modul“ und denken Sie in Begriffen von „Frameworks“. Bauen Sie Ihre eigenen Bibliotheken und verwenden Sie sie konsequent.
Ein Beispiel
Sie verwenden eine Variable außerhalb Ihres Moduls. Wie schön wäre es, reine „private“ Variablen nutzen zu können, die nur von innerhalb des Moduls zugänglich sind?
Die Meisterwissenschaft besteht darin, eine Referenz innerhalb des Moduls zu behalten, die in jedem Kontext auf sich selbst zeigt. Nein, „this“ hilft nicht, da es auf andere Objekte zeigt, wenn eine Modulfunktion aus einem Event-Handler oder einem Timer aufgerufen wird.
Es gibt viele Dokumentationen und Beispiele, und ich kann jeden nur ermutigen, sein Wissen zu erweitern – es lohnt sich, und es wird aus Ihrem ACME-JavaScript eine vielseitige Programmiersprache machen.
Soweit ich Muster verstehe, ist dies kein Modul-Pattern, sondern ein einfaches Singleton-Pattern. Singletons werden durch ihre einfache Konstruktion über die Objekt-Literal-Syntax definiert. Ihnen fehlt häufig die Kapselung, da alles (Eigenschaften und Methoden) normalerweise öffentlich zugänglich ist.
Aufgrund dieses „alles ist öffentlich“-Ansatzes wird das Objekt anfällig für Mutationen. In Multi-Entwickler-Umgebungen oder öffentlichen APIs ist dies normalerweise keine gute Form und Kapselung ist wünschenswerter.
Kapselung, die einem Singleton normalerweise fehlt, bietet eine kontrollierte API, bei der Sie nur privilegierte Methoden verfügbar machen, die Zugriff auf private Variablen haben. Dies verhindert versehentliches Überschreiben und Mutationen.
Module hingegen sind mit dem Ziel konstruiert, Kapselung zu bieten. Das grundlegende Modul ist eine Funktion, die eine privilegierte Funktion oder ein Objekt mit privilegierten Methoden zurückgibt, die Zugriff auf private Variablen haben, aber der Haken ist, dass diese Variablen, die die Daten enthalten, nicht Teil des zurückgegebenen Objekts sind, sodass sie nicht öffentlich außerhalb der Modulfunktion verfügbar sind.
Wenn Sie denken: „Das klingt nach einer Closure“, dann ist es das! Diese Closure ermöglicht es dem Entwickler zu steuern, was und wie auf die Daten zugegriffen wird, z. B. Kapselung. Dies ist die beste Form für öffentliche APIs und mehrere Entwicklungsteams.
TL;DR
Singletons werden durch eine Objekt-Literal-Syntax definiert und haben normalerweise keine Kapselung mit privaten und privilegierten Entitäten
var mySingleton = { name: 'Justin', // I'm public. getName: function () { // I'm public too! return name; } }Module hingegen werden durch eine Funktion definiert, die ein Objekt mit privilegierten Methoden zurückgibt, die Zugriff auf private Variablen haben
var myModule = function () { var name = 'Justin', // I am private! obj = {}; // I will be exposed! obj.getName = function () { return name; }; obj.setName = function (newName) { name = newName; }; return obj; };Zumindest verstehe ich Muster in JavaScript so. Lassen Sie mich wissen, wenn ich etwas missverstehe.
Ich wollte gerade erwähnen, dass dies nicht wirklich ein Modul-Pattern ist, sondern ein Singleton, aber Justin Lowry kam mir zuvor. Ich mag es nicht, bei Mustern und Programmiertechniken religiös zu werden, da es oft nur um Semantik oder unterschiedliche Hintergründe geht. Es kann Singleton-Module, instanziierbare Module, Module mit Factories darin usw. geben.
In diesem Fall sind die Codebeispiele definitiv Singletons. An Singletons ist nichts auszusetzen, aber für diejenigen, die nicht aufpassen, was vor sich geht… könnten Speicherlecks entstehen, wenn Sie nicht manuell verwalten, worauf das Objekt verweist.
Bis zur Mitte der Kommentarliste befürchtete ich, dass niemand AMD (Asynchronous Module Definition) erwähnt hätte.
Natürlich passt das im Artikel gezeigte Singleton-Pattern normalerweise zu den Bedürfnissen der meisten Entwickler. Aber etwas komplexere Web-Apps, die aus vielen Untermodulen mit verschiedenen Abhängigkeiten bestehen, sind eine Qual…
Ich bin von der PHP-Welt zu JavaScript-gesteuerten Apps gekommen und einige Dinge kamen mir ziemlich seltsam vor. Aber als ich anfing, den AMD-Ansatz zu lernen, begann die Entwicklung einer App mir wegen des gesamten Prozesses Freude zu bereiten.
Jemand schrieb früher über private Variablen. Huh, da gibt es tatsächlich einige Probleme. Ich habe diese Situation mit Build-Skripten gelöst, die den Inhalt meines Quellcodes analysieren, z. B. wird this._somePrivateStuff() in eine einfache Variable _somePrivateStuff() umgeschrieben und weiter analysiert. Warum so seltsam? Huh, ich habe viele Dokumentationen über TDD in JS recherchiert und gelesen, und eines wurde gesagt – absolut keine Reflexionsmechanismen. Also – in der Entwicklungsversion sollten Sie Ihre privaten/geschützten Variablen… öffentlich machen, aber mit einem Präfix versehen, und kurz vor der Veröffentlichung – die Deklarationen durch entsprechende Modifikatoren ersetzen.
Wenn jemand bessere Erfahrungen mit dem Design solcher Module in Bezug auf den TDD-Ansatz hat, können Sie gerne auf diesen Kommentar antworten.
Ich bin mir nicht sicher, wie tief Ihr Wissen über JS geht, aber Privatsphäre kann nur durch die Verwendung von Funktionsbereichen zur Umschließung von Variablen (d. h. Closure) erstellt werden, wodurch sie vom äußeren Bereich verborgen werden. Das Präfixen von Variablen mit einem Unterstrich tut funktional überhaupt nichts.
Wenn Sie eine komplexe JS-App entwickeln und sie testen möchten, ist der beste Weg, ein echtes Modul- oder Factory-Pattern zu verwenden und die gewünschten Methoden zurückzugeben, ohne die privaten Variablen zurückzugeben. Dann kann das Test-Framework die API testen und sicherstellen, dass sie das Erwartete zurückgibt.
Eines der besten JS-Frameworks mit einer großartigen integrierten Einheitentestfunktion ist AngularJS. Sie haben das gesamte Framework mit Blick auf Tests entwickelt. Ich empfehle dringend, sich damit zu befassen und zu entdecken, wie sie Probleme gelöst haben.
Sie haben sogar Dependency Injection (DI) entwickelt, die das Testen nahezu mühelos macht. DI ist selten zu finden, aber eine sehr leistungsfähige Methode zur Verwaltung von Abhängigkeiten.
Wie auch immer, wenn Sie wahre Privatsphäre wünschen, können Sie nicht das verwenden, was Sie über andere „klassische“ Sprachen wissen, und Sie müssen die prototypische Natur von JS und seinen lexikalischen Geltungsbereich wirklich verstehen. Wenn Sie versuchen, mehr klassische Stile auf JS anzuwenden, werden Sie am Ende gebissen.
Ähm, mein Fehler – natürlich sind diese Variablen durch Closure eingeschlossen. Ich meinte den Zugriff auf die privaten Felder von innen während des Testens, weil – soweit ich weiß – keine JS-VM über Reflexionsfähigkeiten verfügt.
Ich werde mir AngularJS ansehen, danke für das Feedback.
Wie andere bereits sagten, ist dies eigentlich *nicht* das Modul-Pattern, sondern das Singleton (Objekt-Literal-Pattern). Rebecca Murphey hat einen wirklich großartigen Beitrag zu diesem Muster, falls jemand mehr darüber erfahren möchte: http://rmurphey.com/blog/2009/10/15/using-objects-to-organize-your-code/
Fantastisches Zeug, danke Chris.
Erstaunliche Erklärung des Modul-Pattern-Designs, nur wenn wir ein 20-seitiges Buch hätten, das sich auf das Modul-Design-Pattern und verschiedene Ansätze konzentriert. Andy Osamis Bücher sind großartig, aber er erklärt alle Muster und das Modul-Pattern ist das beliebteste.
Ich wollte fragen, ob ich Lodge abonniere, ob ich serverseitige Techniken lernen kann, wie z. B. das automatische Hinzufügen neuer Feeds zur Homepage, genau wie die Homepage dieser Website, und wie man Foren usw. behandelt??? Ich bin ein Anfänger in all diesen Dingen (verwirrt).
Ich liebe dieses Muster und verwende seit einiger Zeit eine Variante davon.
Schneller Tipp: In der `bind_events`-Methode kann `.on()` `event.data` übergeben. Das bedeutet, dass Sie nicht unbedingt einen Funktionsaufruf in eine andere Funktion einpacken müssen, nur um Argumente zu übergeben.
Ein Ausschnitt aus einer meiner `bind_events`-Methoden.
... bind_events: function() { this.$the_window.on('resize', App.resize_img); this.$the_window.on('resize', App.recenter_carousel); this.$the_window.on('popstate', App.handle_state); this.$doc.on('swiped_left', { index: 1 }, this.swap_studio); this.$doc.on('swiped_right', { index: -1 }, this.swap_studio); this.$doc.on('click touchend', '.control.next, .pad.right', { index: 1 }, this.swap_studio); this.$doc.on('click touchend', '.control.prev, .pad.left', { index: -1 }, this.swap_studio); this.$doc.on('click touchend', '.artist-list a',{ type: 'list-link', index: $(this).data('index') }, this.swap_studio); this.$doc.on('click', '.btn-list, .controls .index', this.toggle_list); this.$doc.on('click touchend', '.btn.toggle-box, .artist-name', this.toggle_box); }, ....Beachten Sie den JSON-Objektparameter bei einigen dieser Bindungen. { index: 1 } kann später innerhalb der betreffenden Funktion über etwas wie… zugegriffen werden.
If (event.data.index === 1) { // do some cool stuff }Besser? Nicht unbedingt. Nur ein weiteres Werkzeug im Werkzeuggürtel.
Der Tipp mit `event.data` ist sehr cool. Ich benutze `.on()` ständig und kannte diesen Trick nie. Sehr cool. Danke fürs Teilen.
Das ist ein cooles Muster, aber ich würde es nur für die Übergabe von Objekten verwenden, die im laufenden Betrieb generiert wurden. Wenn Sie nur eine Methode eines Objekts als Callback ausführen möchten, bevorzuge ich persönlich `$.proxy`.
z. B. mit Chris‘ Code oben
Spart uns die zusätzliche `s`-Variable.
Hallo, ich habe mich in früheren Codebeispielen etwas verschlechtert – in meinem Beispiel für eine einzelne Instanz zeigt „this“ auf das „window“-Objekt. Hier ist die aktualisierte Version
jsFiddle-Code
* http://jsfiddle.net/nightman/ynUZf/ – Einzelinstanz, einfaches Beispiel
* http://jsfiddle.net/nightman/3kzhj/ – Einzelinstanz, Beispiel mit zusätzlichen Optionen
* http://jsfiddle.net/nightman/TBKvB/1/ – Mehrere Instanzen (Konstruktorbeispiel)
* http://jsfiddle.net/nightman/prpU2/2/ – Mehrere Instanzen (Konstruktorbeispiel) mit Auto-Init
Mit freundlichen Grüßen.
Seien Sie vorsichtig. Es gibt viele Leute, die über JavaScript schreiben, die keine Experten sind (ich spreche nicht von Ihnen, sondern von denen, von denen wir unsere Informationen beziehen). Ich wette, es gibt mehr unerfahrene Programmierer, die JavaScript ausspucken, als jede andere Sprache, was zu einer Verbreitung von Plug and Pray Widgets und Bibliotheken führt.
Programmieren bedeutet nicht nur, die Syntax einer Sprache zu lernen und voranzukommen, das ist Experimentieren (nichts falsch daran, aber verwenden Sie es nicht als Beispiel für die beste Vorgehensweise). Programmieren bedeutet auch nicht nur, mit einem Computer zu kommunizieren oder Algorithmen und Muster zu lernen. Was auch wichtig ist, besonders heutzutage, ist die Kommunikation mit Menschen und die Darstellung eines Mitentwicklers, was Ihr Programm tut (sogar sich selbst, besonders wenn Ihr Gedächtnis sein mentales Modell Ihres Programms verloren hat).
Es gibt viele JavaScript-„Best Practice“-Artikel und Codierungsstandards, die sich auf das Schreiben für den Compiler konzentrieren, nicht für den Autor. Programmieren selbst (im abstrakten Sinne, nehme ich an) ist eine Fähigkeit, die sehr schwer auf andere zu übertragen ist, das Erlernen von Syntax, Algorithmen und Mustern ist der einfache Teil.
Bret Victor berührt dies und vieles mehr in seinem wunderbaren Essay: http://worrydream.com/#!/LearnableProgramming, wenn Sie Zeit zum Lesen haben, ist er ziemlich lang, aber eine absolute Freude.
Ein gutes Modul würde es nicht zulassen, dass das `settings`-Objekt direkt von außerhalb des Moduls manipuliert wird; es wäre privat für das Modul und könnte nur von außen modifiziert werden, wenn eine öffentliche Funktion existiert, die dies erlaubt. Auf diese Weise kann es validiert werden, was ist, wenn jemand
s.numArticles = document.location.search.substring(1)tut?Ein *großartiges* Modul hätte nicht nur programmatischen Zugriff auf Einstellungen, es gäbe eine Möglichkeit, alle benötigten Parameter aus dem Dokument abzurufen. Sie sollten keine JavaScript-Module für JavaScript-Programmierer schreiben, Sie sollten für HTML-Autoren schreiben, besonders wenn Sie möchten, dass mehr Leute sie benutzen, sie wollen nur das HTML schreiben, um Ihr Skript einzubinden, und damit ist die Sache erledigt.
Darüber hinaus hätte ein *fantastisches* Modul keinerlei fest codierte HTML-IDs. Es würde das Dokument inspizieren und feststellen, ob es selbst benötigt wird, und entsprechend handeln, ziemlich so, wie Polyfills funktionieren, indem es Elemente, Klassen, Microdaten verwendet, alles außer HTML-IDs.
Ihr „Modul“ ist lediglich ein Objekt, das mit der Objekt-Literal-Syntax erstellt wurde. Der Grund, warum Sie Ihre Referenz auf `settings` exportieren müssen, ist, dass Sie das Modul-Pattern aus diesem Buch nicht tatsächlich verwendet haben: http://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript – das Buch beginnt mit dem Modul-Pattern, indem es die Objekt-Literal-Syntax als Voraussetzung beschreibt, und beschreibt dann das Modul-Pattern selbst. Das Objekt-Literal ist eine Möglichkeit, ein Objekt zu erstellen, ohne `new Object()` zu verwenden, so wie das String-Literal einen String erstellt, ohne `new String()` zu verwenden.
Dieses Buch sieht ziemlich gut aus. Zumindest verwendet es die korrekte Terminologie für Immediately Invoked Functions, im Gegensatz zu einigen der vorherigen Kommentare, die sie immer noch als selbstausführend bezeichnen. Diese Funktion ist selbstausführend
Selbstausführende Funktionen gehören zu Diskussionen über Rekursion. Diese Funktion wird sofort aufgerufen
Obwohl ich persönlich das `new`-Schlüsselwort bevorzuge
Keine abschließenden Klammern sind erforderlich, und Sie müssen die Schnittstelle des Moduls nicht `returnen`, sondern einfach das neue Objekt (`this`) verwenden, um sie zu definieren.
Warum Sie `NewsWidget.init(); SomeOtherModule.init();` innerhalb eines Immediately Invoked Function Expression haben, werde ich nie verstehen. Da Sie dort nichts zu schützen oder privat zu machen haben. Dennoch, wenn die Module selbst sofort aufgerufen würden, müssten Sie `init` überhaupt nicht aufrufen.
Gute Zusammenfassung und wirklich nachdenklich stimmendes Thema. Danke!
@Lee, tolle Antwort, danke. Ich verlor den Glauben beim Lesen dieses Beitrags und der Kommentare. +1 dafür, dass Sie wissen, was Sie tun.
Das ist großartig. Vielen Dank, dass Sie Ihre Expertise teilen.
Ich habe dies übernommen und mit Optionen erweitert. Dank @karol mit dem Beispiel aus seiner Methode.
CodePen: Modul-Pattern mit Objekt
Der Klick-Event von `s.moreButton.on` wird nie ausgelöst. Kann mir jemand sagen warum?
Wenn ich stattdessen `$(“#more-button”)` verwende, funktioniert es.
Ich verwende seit kurzem eine Modifikation dieses Musters. Ich mag die Verwendung von Objekten auf diese Weise, aber ich mag nicht, wie schwer es wird, Unterobjekte zu verstehen, wenn der Code immer komplexer wird. Außerdem bin ich der festen Überzeugung, in jedem Fall, in dem es möglich ist, vollständige Wörter als Variablennamen zu verwenden. Ich finde, es ist viel selbstdokumentierender. Mein Code sieht dann eher so aus
Ich schätze, ich könnte auch den „Einstellungen“-Teil als Standardeinstellungen verwenden und dann ein „Einstellungen“-Objekt an die `initialize`-Funktion übergeben, um bestimmte Standardwerte zu überschreiben. Ich bin mir sicher, dass es viele andere Dinge gibt, die besser sein könnten, aber so sieht mein Muster im Moment aus. Ich mag, dass ich, obwohl ich mit einem Objekt arbeite, nicht so viel darüber nachdenken muss, wo Unterobjekte enden und ob sie richtig kommentiert sind.
Ich liebe diesen Programmierstil in Ihrem Beispiel. Er ist für mich viel sauberer. Aber wenn ich mir die anderen Stile in den Kommentaren ansehe, habe ich mir ein kleines Vollbildmodul angesehen, das ich vor ein paar Tagen geschrieben habe, und mir wurde klar, dass jede Methode im Modul öffentlich ist. Ich würde diesen Stil gerne beibehalten, wenn es möglich ist, Methoden privat zu machen.
Also ist der Weg, wie andere es gemacht haben, besser. Ich habe mich jedoch gefragt: Ist es notwendig oder gute Praxis, `window` in eine selbst aufrufende Funktion wie dieses Beispiel zu übergeben? Ich weiß, dass jQuery-Plugins dies tun, falls `$` von einem anderen Skript überschrieben wird, aber wird `window` jemals überschrieben?
Nachdem ich weitere Kommentare gelesen und diesen Link von Oriely gefunden habe, ergibt das mehr Sinn für mich. Der Teil über Module mit Konstruktoren beantwortet auch eine weitere Frage, die ich zum Übergeben von Optionen an ein Modul hatte.
Wie man das Modul-Pattern verwendet
Schöner Artikel, es gibt nur eine Sache, die ich ändern würde…
var s;
Sie sollten die Variable „s“ im privaten Geltungsbereich von `init` deklarieren, da Sie mit „undefined“ eine globale Variable `s` erstellen. Es ist immer besser, globale Variablen auf ein Minimum zu beschränken.
Schöner Artikel!
Ich benutze eine Closure, über die Christian Heilmann in seinem Artikel spricht, Sieben JavaScript-Dinge, die ich viel früher in meiner Karriere gewusst hätte, genannt „the revealing module pattern“, und fand, dass es gut funktioniert. Ich dachte tatsächlich über Closure in Ihrem allerersten Code-Snippet dieses Artikels nach und habe nicht versucht, ein Objekt für Closure zu verwenden. Ich denke, jeder hat seinen eigenen Stil, aber welcher Weg ist im Allgemeinen der beste?
Wow, danke für diesen Link :)
Sie sollten sich das ansehen
Terrific JS
Schöner Artikel. Ich schreibe seit einiger Zeit JavaScript-Apps und war frustriert darüber, wie schwierig es ist, Apps zu strukturieren. Ich habe mein eigenes Programm zur Strukturierung von JavaScript-Apps namens structureJS entwickelt: https://github.com/ShamariFeaster/structureJS
Mehr Modulansatz ist Ihr Verwendung von sauberem JS
}
;(function(){
})();
Dies ist kein tatsächliches Beispiel für ein Modul-Pattern-Design? Sie weisen einem Variablennamen einfach ein Objekt-Literal zu… Das Modul-Pattern muss eine anonyme Funktion verwenden, die inline ausgeführt wird, um eine Closure für den von Ihnen erstellten Namespace zu erzeugen. Dies ermöglicht öffentliche/private Mitglieder, die OOP nachahmen.