1. Code
  2. JavaScript

Modulares JavaScript schreiben

Beim Schreiben einer kompletten Webanwendung in JavaScript ist es sehr wichtig, dass sie gut organisiert ist. Die Wartung eines Spaghetti-Code-Projekts verursacht nur Kopfschmerzen und Alpträume. In diesem Tutorial zeige ich Ihnen, wie Sie Ihren Code modularisieren, um die Verwaltung großer JavaScript-Projekte zu vereinfachen.
Scroll to top

German (Deutsch) translation by Valentina (you can also view the original English article)

Beim Schreiben einer kompletten Webanwendung in JavaScript ist es sehr wichtig, dass sie gut organisiert ist. Die Wartung eines Spaghetti-Code-Projekts verursacht nur Kopfschmerzen und Alpträume. In diesem Tutorial zeige ich Ihnen, wie Sie Ihren Code modularisieren, um die Verwaltung großer JavaScript-Projekte zu vereinfachen.


Einführung

Vor kurzem habe ich mir eine meiner Lieblings-JavaScript-Präsentationen noch einmal angesehen: Scalable JavaScript Application Architecture von Nicolas C. Zakas. Ich fand die von ihm empfohlenen modularen JavaScript-Muster besonders faszinierend und beschloss, es auszuprobieren. Ich habe die theoretischen Code-Schnipsel aus seinen Folien genommen, sie erweitert und angepasst und bin zu dem gekommen, was ich Ihnen heute zeigen werde. Ich kann kaum behaupten, darin Experte zu sein: Über diese Methode, JavaScript zu schreiben, denke ich erst seit etwas mehr als einer Woche nach. Aber hoffentlich kann ich Ihnen zeigen, wie Sie das, was Zakas beworben hat, in die Praxis umsetzen und Sie dazu anregen, auf diese Weise zu programmieren.


Ein Wort zum begleitenden Screencast

Zu diesem Tutorial gibt er einen Screencast; es ist ziemlich lang (eigentlich fast zweieinhalb Stunden), daher ist hier das Inhaltsverzeichnis, das Ihnen beim Navigieren hilft; Ich habe jedem Abschnitt einen Titel "Folie" gegeben, um das Auffinden des Anfangs jedes Abschnitts zu erleichtern.

  • 0:00:00 - Anfang
  • 0:05:15 - Erstellung der Module
  • 0:40:05 - Erstellung der Sandbox
  • 1:04:39 - Erstellung des Kerns
  • 1:36:55 - Erstellung der Schnittstelle (HTML & CSS)
  • 1:51:08 - Fehlersuche!
  • 2:02:27 – Erstellung des Kerns (Dojo-Edition)
  • 2:18:47 - Die Vorteile besprechen

Präsentation von Nicolas Zakas

Wenn Sie sie in letzter Zeit nicht gesehen haben, sollten Sie sich Zakas' Präsentation ansehen, bevor Sie fortfahren; Das meiste von dem, was ich Ihnen zeigen werde, wird mit diesem unter Ihrem Gürtel leichter zu verstehen sein:

Die Präsentation

Hinweis: Dies ist nicht die Originalpräsentation, auf der dieses Tutorial basiert, sondern eine neuere Version desselben Vortrags.

Die Folien


Die Zusammenfassung

Zusammenfassend hat eine gute JavaScript-Anwendung also vier Schichten. Von unten nach oben sind dies wie folgt:

  • Die Basis: Dies wäre Ihr JavaScript-Framework, wie jQuery, wenn Sie eines verwenden. Oder Sie können Ihre eigene schreiben.
  • Der App-Kern: Diese Schicht ist dafür verantwortlich, alles miteinander zu verbinden und Ihre Web-App auszuführen. Es bietet auch eine Ausgleichsschicht für die nächste Schicht (die Sandbox); das mag überflüssig erscheinen, sobald Sie sich darauf eingelassen haben, und Sie werden sich fragen, warum wir die Sandbox nicht einfach direkt mit der Basis sprechen lassen. Wenn wir jedoch zuerst alles durch den Kern laufen lassen, können wir die Basis einfach gegen ein anderes Framework austauschen. Dann müssen wir nur noch die Mittelsmann-Funktionen im Kern ändern.
  • Die Sandbox: Dies ist die Mini-API – und die einzige API – auf die jeder Teil unserer Web-App Zugriff hat. Sobald Sie eine Sandbox erstellt haben, mit der Sie zufrieden sind, ist es sehr wichtig, dass sich die äußere Schnittstelle in keiner Weise ändert. Dies liegt daran, dass Sie viele Module davon abhängig haben und das Ändern der API bedeuten würde, jedes Modul durchzugehen und es zu aktualisieren - nicht etwas, das Sie tun möchten. Natürlich können Sie den Code in den Methoden der Sandbox ändern (solange sie immer noch dasselbe tun) oder Funktionen hinzufügen.
  • Die Module: Das sind die wahren Funktionen unserer App. Jedes Modul ist ein eigenständiger Code, der einen einzelnen Aspekt der Web-App ausführt. So wie ich zuvor Web-Apps geschrieben habe, ist der gesamte Modulcode in einem spaghettiartigen Durcheinander verflochten, und wenn ein Teil fehlt, stürzt die gesamte App ab. Wenn alles sauber in Module eingefügt wird (mit Interaktionen durch die Sandbox), verursacht ein fehlendes Teil keine Fehler.

Um dieses Muster zu erklären, erstellen wir einen Mini-Online-Shop (na ja, nur das Frontend). Dies zeigt wirklich den Nutzen von Modulen, denn ein Online-Shop besteht aus vielen unterschiedlichen Teilen:

  • ein Produktpanel
  • ein Einkaufswagen
  • ein Suchfeld
  • eine Möglichkeit, Produkte zu filtern

Jedes dieser Teile ist eindeutig ein separater Teil der Seite, aber alle müssen mit anderen Modulen interagieren – entweder als Initiator oder als Empfänger. Wir werden sehen, wie das alles funktioniert!

Alles klar, genug Theorie! Beginnen wir mit der Codierung!


Die Bausteine: Module

Als ich mit diesem Projekt begann, hatte ich weder einen Kern noch eine Sandbox, um daran zu arbeiten. Also beschloss ich, mit der Codierung der Module zu beginnen, da mir dies eine Vorstellung davon geben würde, was die Module benötigen, um über die Sandbox darauf zugreifen zu können.

Beim Erstellen eines Moduls habe ich ein Muster verwendet, das dem Beispielcode, den Zakas gezeigt hat, sehr nahe kommt:

1
CORE.create_module("search-box", function (sb) { 
2
    return { 
3
        init : function () {}, 
4
        destroy : function () {} 
5
    }; 
6
});

Wie Sie sehen, verwenden wir die Methode create_module auf dem CORE, um dieses Modul bei der Web-App zu registrieren (natürlich haben wir den Core noch nicht erstellt, aber wir werden es schaffen). Diese Funktion benötigt zwei Parameter: den Namen des Moduls (in diesem Fall „Suchfeld“) und eine Funktion, die das Modulobjekt zurückgibt. Was ich hier gezeigt habe, ist das einfachste Modul, das möglich ist. Beachten Sie einige Dinge über die Creator-Funktion: Erstens hat sie einen einzigen Parameter, der eine Instanz unserer Sandbox ist (wenn wir uns die Sandbox ansehen, werden wir sehen, warum eine Instanz besser ist, als wenn alle Module auf dieselbe Sandbox zugreifen Objekt). Dieses Sandbox-Objekt ist die einzige Verbindung, die das Modul zur „Außenwelt“ hat. (Natürlich gibt es, wie Zakas sagte, wirklich keine technischen Einschränkungen, die Sie daran hindern, direkt aus dem Modul auf die Basis oder den Kern zuzugreifen; Sie sollten es nur nicht tun.) Wie Sie sehen, gibt diese Funktion ein Objekt zurück, das ist unser Modulobjekt. Zumindest hat dieses Modul nur eine init-Methode (wird verwendet, wenn wir das Modul starten) und eine destroy-Methode (wird verwendet, wenn wir das Modul herunterfahren).

Lassen Sie uns also ein echtes Modul bauen.


Voller Screencast


Das Suchfeldmodul

1
CORE.create_module("search-box", function (sb) { 
2
    var input, button, reset; 
3
 
4
    return { 
5
        init : function () {}, 
6
        destroy : function () {}, 
7
        handleSearch : function () {}, 
8
        quitSearch : function () {} 
9
    }; 
10
});

Sie sollten verstehen, was hier vor sich geht: Unser Modul verwendet drei Variablen: inputbutton, und reset. Neben den beiden erforderlichen Modulfunktionen im Rückgabeobjekt erstellen wir zwei suchbezogene Funktionen. Ich glaube nicht, dass es einen Grund gibt, warum sie Teil des zurückgegebenen Modulobjekts sein müssen; sie könnten genauso gut über der return-Anweisung deklariert und per Closure referenziert werden. Lassen Sie uns in jede dieser Funktionen eintauchen:

1
init : function () { 
2
    input = sb.find('#search_input')[0]; 
3
    button = sb.find('#search_button')[0]; 
4
    reset  = sb.find("#quit_search")[0]; 
5
 
6
    sb.addEvent(button, 'click', this.handleSearch); 
7
    sb.addEvent(reset, 'click', this.quitSearch); 
8
},

Zuerst weisen wir die drei benötigten Variablen zu. Da wir in der Lage sein müssen, DOM-Elemente aus unseren Modulen zu finden, benötigen wir eine Find-Methode in unserer Sandbox. Aber was ist mit der [0] am Ende? Nun, unsere Methode sb.find gibt etwas zurück, das einem jQuery-Objekt ähnelt: Den Elementen, die dem Selektor entsprechen, werden nummerierte Schlüssel gegeben, und dann gibt es einige Methoden und Eigenschaften. Wir werden jedoch nur die rohen DOM-Elemente benötigen, die wir greifen (da nur ein Element eine ID hat, können wir sicher sein, dass wir nur ein Element zurückgeben).

Zwei dieser Elemente (button und reset) sind Schaltflächen, und daher müssen wir einige Ereignishandler anschließen. Das müssen wir auch der Sandbox hinzufügen! Wie Sie sehen, ist dies Ihre Standardfunktion zum Hinzufügen von Ereignissen: Sie nimmt das Element, das Ereignis und die Funktion an.

Wie wäre es mit der Zerstörung des Moduls:

1
destroy : function () { 
2
    sb.removeEvent(button, 'click', this.handleSearch); 
3
    sb.removeEvent(reset, 'click', this.quitSearch); 
4
    input = null; 
5
    button = null; 
6
    reset = null; 
7
},

Es ist ziemlich einfach: Es muss eine removeEvent-Funktion geben, die die Arbeit von addEvent rückgängig macht. Dann setzen wir einfach die drei Modulvariablen auf null.

Diese beiden Ereignis-Listener verweisen auf die Suchfunktionen. Schauen wir uns den ersten an:

1
handleSearch : function () { 
2
    var query = input.value; 
3
    if (query) { 
4
        sb.notify({ 
5
            type : 'perform-search', 
6
            data : query 
7
        }); 
8
    } 
9
},

Zuerst erhalten wir den Wert des Suchfelds. Wenn etwas drin ist, machen wir weiter. Aber was sollen wir tun? Normalerweise hätten wir bei einer dynamischen Suche (die keine Seitenaktualisierung der Ajax-Anfrage erfordert) Zugriff auf das Produktpanel und könnten sie entsprechend filtern. Aber unser Modul muss mit oder ohne Produktpanel existieren können; Außerdem ist die Verbindung zur Außenwelt nur über die Sandbox möglich. Hier ist also, was Zakas vorgeschlagen hat: Wir teilen der Sandbox (die wiederum dem Kern mitteilt) einfach mit, dass der Benutzer eine Suche durchgeführt hat. Dann wird der Kern diese Informationen den anderen Modulen anbieten. Wenn eines antwortet, nimmt er die Daten und läuft damit. Wir tun dies über die Methode sb.notify; sie braucht ein Objekt mit zwei Eigenschaften: die Art des Ereignisses, das wir durchführen, und die Daten, die sich auf das Ereignis beziehen. In diesem Fall führen wir ein „Perform-Search“-Ereignis durch und die relevanten Daten sind die Suchanfrage. Das ist alles, was das Suchfeldmodul tun muss. Wenn es ein anderes Modul gibt, das die Durchsuchbarkeit offengelegt hat, gibt der Kern ihm die Daten.

Das Schöne daran ist, dass diese Methode absolut vielseitig ist. Das Modul, das dieses Ereignis in unserem Beispiel verwendet, macht nichts Ajax-y, aber es gibt keinen Grund, warum ein anderes Modul dies nicht tun oder auf eine völlig andere Weise suchen könnte.

Die Methode quitSearch ist nicht viel komplizierter:

1
quitSearch : function () { 
2
    input.value = ""; 
3
    sb.notify({ 
4
        type : 'quit-search', 
5
        data : null 
6
    });                 
7
}

Zuerst leeren wir das Suchfeld; dann teilen wir der Sandbox mit, dass wir eine „quit-search“ ausführen; In diesem Fall liegen keine relevanten Daten vor.

Ob Sie es glauben oder nicht, das ist das gesamte Suchmodul. Ziemlich einfach, oder? Kommen wir zum nächsten.

Das Filterleistenmodul

Wir möchten, dass unser Online-Shop Benutzern die Möglichkeit gibt, Produkte nach Kategorie anzuzeigen. Lassen Sie uns also eine Filterleiste implementieren, in der der Benutzer auf die Kategorienamen klickt, um nur die Elemente in der angegebenen Kategorie anzuzeigen.

1
CORE.create_module("filters-bar", function (sb) { 
2
    var filters; 
3
    return { 
4
        init : function () { 
5
            filters = sb.find('a'); 
6
            sb.addEvent(filters, 'click', this.filterProducts); 
7
        }, 
8
        destroy : function () { 
9
            sb.removeEvent(filters, 'click', this.filterProducts); 
10
            filters = null; 
11
        }, 
12
        filterProducts : function (e) { 
13
            sb.notify({ 
14
                type : 'change-filter', 
15
                data : e.currentTarget.innerHTML 
16
            }); 
17
        } 
18
    };         
19
});

Dieser ist nicht sehr kompliziert. Wir benötigen eine Variable, um die Filter zu speichern. Wie Sie sehen können, verwenden wir sb.find, um alle Anker zu erhalten. Aber wie stehen die Chancen, dass alle Anker auf der Seite Filter sind? Nicht sehr gut. Sobald wir die find-Methode schreiben, werden Sie sehen, wie sie nur die Elemente innerhalb des DOM-Elements zurückgibt, das unserem Modul entspricht. Anschließend fügen wir den Filtern ein Click-Ereignis hinzu, das die Methode filterProduct aufruft. Wie Sie sehen können, teilt diese Methode der Sandbox einfach unser „change-filter“-Ereignis mit und gibt ihr den Text des Links an, auf den auf die Daten geklickt wurde (e ist das Ereignisobjekt und currentTarget ist das Element, auf das geklickt wurde.

Mit destroy werden natürlich die Ereignis-Listener einfach gelöscht.

Das Produktpanel-Modul

Dieses wird ziemlich langwierig und möglicherweise kompliziert sein, also halte dich fest! Wir beginnen mit einer Shell:

1
CORE.create_module(“product-panel”, function (sb) {  
2
    var products; 
3
         
4
    function eachProduct(fn) { 
5
        var i = 0, product; 
6
        for ( ; product = products[i++]; ) { 
7
            fn(product); 
8
        } 
9
    } 
10
    function reset () { 
11
        eachProduct(function (product) { 
12
            product.style.opacity = '1';     
13
        }); 
14
    } 
15
    return { 
16
        init : function () {}, 
17
        reset : reset, 
18
        destroy : function () {}, 
19
        search : function (query) {}, 
20
        change_filter : function (filter) {}, 
21
        addToCart : function (e) {} 
22
 
23
    };       
24
});

Nehmen Sie sich einen Moment Zeit, um darüber nachzudenken; es gibt nicht viel, das sich von unseren anderen Modulen unterscheidet; es gibt einfach mehr davon. Der Hauptunterschied besteht darin, dass ich zwei Hilfsfunktionen verwendet habe, die außerhalb des zurückgegebenen Objekts erstellt wurden. Die erste heißt eachProduct, und wie Sie sehen, nimmt sie einfach eine Funktion und führt sie für jedes Element in der Produktliste aus. Die andere ist eine Reset-Funktion, die wir gleich verstehen werden.

Schauen wir uns nun die init- und die destroy-Funktionen an.

1
init : function () { 
2
    var that = this; 
3
    products = sb.find('li'); 
4
    sb.listen({ 
5
            'change-filter'  : this.change_filter, 
6
            'reset-fitlers' : this.reset, 
7
            'perform-search' : this.search, 
8
            'quit-search'    : this.reset 
9
        }); 
10
    eachProduct(function (product) { 
11
        sb.addEvent(product, 'click', that.addToCart);        
12
    });  
13
}, 
14
destroy : function () { 
15
    var that = this; 
16
    eachProduct(function (product) { 
17
        sb.removeEvent(product, 'click', that.addToCart);         
18
    }); 
19
    sb.ignore(['change-filter', 'reset-filters', 'perform-search', 'quit-search']); 
20
},

Innerhalb von init sammeln wir alle Produkte (die in Listenelementen dargestellt werden). Dann müssen wir der Sandbox mitteilen, dass wir an mehreren Events interessiert sind. Wir übergeben ein Objekt an die Methode sb.listen; dieses Objekt verwendet den Ereignisnamen als Schlüssel und die Ereignisfunktion als Wert für jede Eigenschaft. Zum Beispiel teilen wir der Sandbox mit, dass wir, wenn jemand anderes ein „Perform-Search“-Ereignis ausführt, darauf reagieren möchten, indem wir unsere search-Funktion ausführen. Hoffentlich beginnen Sie zu sehen, wie das funktionieren wird!

Dann verwenden wir unsere eachProduct-Hilfsfunktion, um jedem Produkt eine On-Click-Funktion zuzuweisen. Wenn ein Produkt angeklickt wurde, führen wir addToCart aus. Wir müssen this zwischenspeichern, da sich sein Wert auf das globale Objekt innerhalb der Funktion ändert.

Bei Destroy entfernen wir einfach die Ereignishandler aus den Produkten und teilen der Sandbox mit, dass wir nicht mehr an den Ereignissen interessiert sind (eigentlich glaube ich nicht, dass dies aufgrund der Art und Weise, wie wir die Dinge im Kern handhaben, notwendig ist, aber Ich habe es nur für den Fall eingeworfen, dass sich etwas am "Backend" ändert).

Jetzt sehen wir uns die Funktionen an, die aufgerufen werden, wenn andere Module Ereignisse auslösen:

1
search : function (query) { 
2
    reset(); 
3
    query = query.toLowerCase(); 
4
    eachProduct(function (product) { 
5
        if (product.getElementsByTagName('p')[0].innerHTML.toLowerCase().indexOf(query) < 0) { 
6
            product.style.opacity = '0.2'; 
7
        }  
8
    }); 
9
}, 
10
change_filter : function (filter) { 
11
    reset(); 
12
    eachProduct(function (product) { 
13
        if (product.getAttribute('data-8088-keyword').toLowerCase().indexOf(filter.toLowerCase()) < 0) { 
14
            product.style.opacity = '0.2'; 
15
        } 
16
    }); 
17
}, 
18
addToCart : function (e) { 
19
    var li = e.currentTarget; 
20
    sb.notify({ 
21
        type : 'add-item', 
22
        data : { id : li.id, name : li.getElementsByTagName('p')[0].innerHTML, price : parseInt(li.id, 10) } 
23
    });  
24
}

Wir beginnen mit search; Diese Funktion wird aufgerufen, wenn die Aktion „Suche durchführen“ stattfindet. Wie Sie sehen, verwendet es die Suchanfrage als Parameter. Zuerst setzen wir den Produktbereich zurück (nur für den Fall, dass die Ergebnisse einer vorherigen Suche oder Filterung da sind). Dann durchlaufen wir jedes Produkt mit unserer Hilfsfunktion. Denken Sie daran, dass das Produkt ein Listenelement ist; Darin befindet sich ein Absatz mit der Produktbeschreibung, und danach werden wir suchen (in einem realen Beispiel wäre dies wahrscheinlich nicht der Fall). Wir erhalten den Text des Absatzes und vergleichen ihn mit dem Text der Abfrage (beachten Sie, dass beide durch toLowerCase() ausgeführt wurden). Wenn das Ergebnis kleiner als 0 ist, was bedeutet, dass keine Übereinstimmung gefunden wurde, setzen wir die Deckkraft des Produkts auf 0,2. So verstecken wir Produkte in diesem Beispiel. Das ist es!

Jetzt ist ein guter Zeitpunkt, darauf hinzuweisen, dass die reset-Funktion lediglich die Deckkraft aller Produkte auf 1 zurücksetzt.

Die Methode changeFilter ist search ziemlich ähnlich; Anstatt die Produktbeschreibung zu durchsuchen, nutzen wir diesmal die HTML5-Daten-*-Attribute. Dadurch können wir unseren HTML-Elementen benutzerdefinierte Attribute hinzufügen, ohne die Spezifikationsregeln zu verletzen. Sie müssen jedoch mit „data-“ beginnen, und ich habe auch ein persönliches Präfix hinzugefügt, damit sie nicht mit Attributen in Konflikt stehen, die der Code von Drittanbietern verwenden könnte. Der an diese Funktion übergebene Filter wird mit dem Datenattribut verglichen, das die Namen der Kategorien der Elemente enthält. Wenn es keine Übereinstimmungen gibt, reduzieren wir die Deckkraft des Elements.

Die letzte Funktion ist addToCart, die ausgeführt wird, wenn eines der Produkte angeklickt wird. Wir erhalten das angeklickte Element und senden dann eine Benachrichtigung an das System, die es über unser Ereignis „Element hinzufügen“ informiert. Diesmal sind die von uns übergebenen Daten ein Objekt. Es enthält die Produkt-ID, den Produktnamen und den Produktpreis. In diesem Beispiel sind wir faul und verwenden die Element-ID als ID und Preis und die Produktbeschreibung als Namen.

Das Einkaufswagen-Modul

Wir haben noch ein Modul zum Anschauen. Es ist das Modul „Warenkorb“:

1
CORE.create_module(“shopping-cart”, function (sb) {  
2
    var cart, cartItems; 
3
 
4
    return { 
5
        init : function () { 
6
            cart = sb.find('ul')[0];  
7
            cartItems = {}; 
8
 
9
            sb.listen({ 
10
                'add-item' : this.addItem 
11
            }); 
12
        }, 
13
        destroy : function () { 
14
            cart = null; 
15
            cartItems = null; 
16
            sb.ignore(['add-item']); 
17
        }, 
18
        addItem : function (product) { 
19
        } 
20
    }; 
21
});

Ich denke, Sie haben jetzt den Dreh raus; Wir verwenden zwei Variablen: den Warenkorb und die Artikel im Warenkorb. Bei der Initialisierung des Moduls setzen wir diese auf die ul im Warenkorb bzw. ein leeres Objekt. Dann teilen wir der Sandbox mit, dass wir auf ein Ereignis reagieren möchten. Bei der Zerstörung werden wir das alles rückgängig machen.

Folgendes sollte passieren, wenn ein Artikel in den Warenkorb gelegt wird:

1
addItem : function (product) { 
2
    var entry = sb.find('#cart-' + product.id + ' .quantity')[0]; 
3
    if (entry) { 
4
        entry.innerHTML =  (parseInt(entry.innerHTML, 10) + 1); 
5
        cartItems[product.id]++; 
6
    } else { 
7
        entry = sb.create_element('li', { id : "cart-" + product.id, children : [ 
8
                sb.create_element('span', { 'class' : 'product_name', text : product.name }), 
9
                sb.create_element('span', { 'class' : 'quantity', text : '1'}), 
10
                sb.create_element('span', { 'class' : 'price', text : '$' + product.price.toFixed(2) }) 
11
                ], 
12
                'class' : 'cart_entry' }); 
13
 
14
        cart.appendChild(entry); 
15
        cartItems[product.id] = 1; 
16
    } 
17
 
18
}

Diese Funktion nimmt das Produktobjekt, das wir gerade im Produkt-Panel-Modul gesehen haben. Dann erhalten wir das Element mit dem Selektor ‘#cart-’ + product.id + ’ .quantity’; Dies sucht nach einem Element mit einer Klasse von ‚quantity‘ innerhalb eines Elements mit einer ID von „cart-id_number“. Wenn dieses Produkt zuvor in den Warenkorb gelegt wurde, wird es gefunden. Wenn es gefunden wird, erhöhen wir das innerHTML dieses Elements (die Menge dieses Produkts, das der Benutzer dem Auto hinzugefügt hat) um eins und aktualisieren den Eintrag im cartItems-Objekt, das den Kauf verfolgt.

Wenn das Element nicht gefunden wurde, ist dies das erste Mal, dass der Benutzer eines dieser Produkte in den Warenkorb legt. In diesem Fall verwenden wir die create_element-Methode der Sandbox; Wie Sie sehen, wird ein Attributobjekt ähnlich wie jQuery verwendet. Der Sonderfall hier ist die children-Eigenschaft, bei der es sich um ein Array von Elementen handelt, die in das von uns erstellte Element eingefügt werden. Wie Sie sehen, erstellen wir im Grunde ein Listenelement mit drei Bereichen: Produktname, Menge und Preis. Dann hängen wir diesen Listenartikel an den Warenkorb an und fügen das Produkt dem cartItems-Objekt hinzu.

Das ist der gesamte Code für unsere Module; Ich sollte beachten, dass ich dies alles in einer Datei namens modules.js abgelegt habe. Jetzt, da wir wissen, mit welcher Schnittstelle unsere Module arbeiten müssen, können wir das bauen … und das ist die Sandbox.


Die Unterstützung: Sandbox

Ich weiß, dass ich das bereits erwähnt habe, aber es ist ziemlich wichtig, dass sich die nach außen gerichtete Oberfläche der Sandbox nicht ändert. Dies liegt daran, dass alle Module davon abhängen. Sicher, Sie können Methoden hinzufügen oder den Code innerhalb von Methoden ändern, solange Sie die Methoden oder das, was die Funktion tut/zurückgibt, nicht ändern.

Wenn wir die Datei modules.js destillieren, sehen wir, dass dies die Methoden sind, die die Sandbox den Modulen geben muss:

  • find
  • addEvent 
  • removeEvent
  • notify
  • listen
  • ignore
  • create_element

Also machen wir uns an die Arbeit. Da ich mit dem Code Sandbox.create eine Sandbox-Instanz erstellen möchte, machen wir daraus ein Objekt mit(derzeit) nur einer Methode.

1
var Sandbox = { 
2
    create : function (core, module_selector) { 
3
            var CONTAINER = core.dom.query('#' + module_selector); 
4
            return { 
5
                 
6
            }; 
7
        }     
8
};

Hier ist unser Anfang. Wie Sie sehen, benötigt die create-Methode zwei Parameter: eine Referenz auf den Kern und den Namen des Moduls, an das sie übergeben wird. Dann erstellen wir eine Variable, CONTAINER, die auf das DOM-Element verweist, das dem Modulcode entspricht. Beginnen wir nun mit der Codierung der Funktionen, die wir aufgelistet haben.

1
find : function (selector) { 
2
    return CONTAINER.query(selector); 
3
},

Das ist ziemlich einfach. Tatsächlich sind die meisten Funktionen in der Sandbox ziemlich einfach, da es sich um einen dünnen Wrapper handeln soll, der dem Modul genau das richtige Maß an Zugriff auf den Kern bietet. Als die von uns aufgerufene Methode core.dom.query den Container zurückgab, gab sie dem Container eine Methode, die es ihm ermöglicht, per Selektor nach untergeordneten Elementen zu suchen; Wir verwenden dies, um die Fähigkeit eines Moduls, das DOM zu beeinflussen, einzuschränken, so dass es sowohl im HTML- als auch im JavaScript-Modul ein Modul bleibt.

1
addEvent : function (element, evt, fn) { 
2
    core.dom.bind(element, evt, fn); 
3
}, 
4
removeEvent : function (element, evt, fn) { 
5
    core.dom.unbind(element, evt, fn); 
6
},

Wie ich schon sagte, sind die meisten dieser Sandbox-Funktionen ziemlich klein; Wir werden die Ereignisdaten einfach zum Anschluss an den Kern übertragen.

1
notify : function (evt) { 
2
    if(core.is_obj(evt) && evt.type) { 
3
        core.triggerEvent(evt); 
4
    } 
5
}, 
6
listen : function (evts) { 
7
    if (core.is_obj(evts)) { 
8
        core.registerEvents(evts, module_selector); 
9
    }             
10
},  
11
ignore : function (evts) { 
12
    if (core.is_arr(evts)) { 
13
        core.removeEvents(evts, module_selector); 
14
    }          
15
},

Diese drei Funktionen sind, wie Sie sich erinnern werden, die Vehikel, mit denen die Module andere Module über ihre Aktionen informieren. Ich habe diesen eine kleine Fehlerprüfung hinzugefügt, um sicherzustellen, dass die Ereignisdaten alle in Ordnung sind, bevor wir sie an den Kern senden. Beachten Sie, dass wir auch den Namen des Moduls übergeben müssen, wenn wir dem Kern mitteilen, worauf wir hören oder was wir ignorieren.

1
create_element : function (el, config) { 
2
    var i, text; 
3
    el = core.dom.create(el); 
4
    if (config) { 
5
        if (config.children && core.is_arr(config.children)) { 
6
            i = 0; 
7
            while (config.children[i]) { 
8
                el.appendChild(config.children[i])); 
9
                i++; 
10
            } 
11
            delete config.children; 
12
        } else if (config.text) { 
13
           text = document.createTextNode(config.text); 
14
           delete config.text; 
15
           el.appendChild(text); 
16
        } 
17
        core.dom.apply_attrs(el, config); 
18
    } 
19
    return el; 
20
}

Dies ist offensichtlich die längste Funktion in der Sandbox (und um ehrlich zu sein, je mehr ich darüber nachdenke, desto mehr sollte sie meiner Meinung nach im Kern sein, aber trotzdem …). Wie wir wissen, nimmt sie den Namen eines Elements und eines Konfigurationsobjekts an. Wir beginnen mit der Erstellung des DOM-Elements (verwenden Sie eine Kernmethode). Wenn es dann ein Konfigurationsobjekt gibt und es ein Array namens children hat, werden wir jedes Kind durchlaufen und es an das Element anhängen. Dann löschen wir die children-Eigenschaft. Andernfalls, wenn wir eine text-Eigenschaft haben, setzen wir den Text des Elements darauf und löschen die text-Eigenschaft (in diesem Beispiel können wir nicht sowohl text- als auch untergeordnete Elemente haben). Schließlich verwenden wir eine weitere Kernfunktion, um die verbleibenden Attribute anzuwenden und das Element zurückzugeben.

Und das ist das Ende der Sandbox. Mir ist klar, dass dies eine ziemlich einfache Sandbox sein könnte, aber es sollte Ihnen eine Vorstellung davon geben, wie die Sandbox funktioniert. Außerdem können Sie bei der Verwendung andere Methoden hinzufügen, wenn Ihre Module dies erfordern.


Das Fundament: Basis und Kern

Jetzt sind wir bereit, zum Kern überzugehen. Hier ist, womit wir beginnen:

1
var CORE = (function () { 
2
    var moduleData = {}, debug = true; 
3
 
4
    return { 
5
        debug : function (on) { 
6
            debug = on ? true : false; 
7
        }, 
8
 
9
    }; 
10
 
11
}());

Wir werden das Modul data-Objekt verwenden, um alles zu speichern, was Sie über die Module wissen müssen; Die Debug-Variable steuert, ob Fehler in der Konsole protokolliert werden oder nicht. Wir haben eine einfache Debug-Funktion, um Fehler ein- und auszuschalten.

Beginnen wir mit der Funktion create_module, mit der wir unsere Module registriert haben:

1
create_module : function (moduleID, creator) { 
2
    var temp; 
3
    if (typeof moduleID === 'string' && typeof creator === 'function') { 
4
        temp = creator(Sandbox.create(this, moduleID)); 
5
        if (temp.init && temp.destroy && typeof temp.init === 'function' && typeof temp.destroy === 'function') { 
6
            moduleData[moduleID] = { 
7
                create : creator, 
8
                instance : null 
9
            }; 
10
            temp = null; 
11
        } else { 
12
            this.log(1, "Module \"" + moduleId + "\" Registration: FAILED: instance has no init or destroy functions"); 
13
        } 
14
    } else { 
15
        this.log(1, "Module \"" + moduleId +  "\" Registration: FAILED: one or more arguments are of incorrect type" ); 
16
 
17
    } 
18
},

Als erstes bestätigen wir, dass die an die Funktion übergebenen Parameter den richtigen Typ haben; Wenn dies nicht der Fall ist, rufen wir eine Protokollfunktion auf, die eine Schweregradnummer und eine Nachricht benötigt (wir werden die Protokollfunktion in einigen Minuten sehen).

Als Nächstes erstellen wir eine Kopie des fraglichen Moduls, um sicherzustellen, dass es die Funktionen init und Destroy hat; Wenn nicht, protokollieren wir erneut einen Fehler. Wenn jedoch alles in Ordnung ist, fügen wir moduleData ein Objekt hinzu; wir speichern die Creator-Funktion und einen leeren Platz für die Instanz, wenn wir das Modul starten. Dann löschen wir die temporäre Kopie des Moduls.

1
start : function (moduleID) { 
2
    var mod = moduleData[moduleID]; 
3
    if (mod) { 
4
        mod.instance = mod.create(Sandbox.create(this, moduleID)); 
5
        mod.instance.init(); 
6
    } 
7
}, 
8
start_all : function () { 
9
    var moduleID; 
10
    for (moduleID in moduleData) { 
11
        if (moduleData.hasOwnProperty(moduleID)) { 
12
            this.start(moduleID); 
13
        } 
14
    } 
15
},

Als nächstes fügen wir die Funktion zum Starten der Module hinzu; wie zu erwarten, akzeptiert es einen Modulnamen als einzelnen Parameter. Wenn in moduleData ein entsprechendes Modul vorhanden ist, führen wir seine create-Methode aus und übergeben ihm eine neue Sandbox-Instanz. Dann starten wir es, indem wir seine init-Methode ausführen.

Wir können eine Funktion erstellen, die es einfach macht, alle Module auf einmal zu starten, da dies wahrscheinlich etwas ist, was wir tun möchten. Wir müssen nur moduleData durchlaufen und jede moduleID an die Startmethode senden. Vergessen Sie nicht, den hasOwnProperty-Teil zu verwenden; Ich weiß, dass es unnötig und hässlich erscheint (zumindest für mich), aber es ist da, nur für den Fall, dass jemand dem Prototypobjekt des Objekts einen Gegenstand hinzugefügt hat.

1
stop : function (moduleID) { 
2
	var data; 
3
	if (data = moduleData[moduleId] && data.instance) { 
4
		data.instance.destroy(); 
5
		data.instance = null; 
6
	} else { 
7
		this.log(1, "Stop Module '" + moduleID + "': FAILED : module does not exist or has not been started"); 
8
	} 
9
}, 
10
stop_all : function () { 
11
	var moduleID; 
12
	for (moduleID in moduleData) { 
13
		if (moduleData.hasOwnProperty(moduleID)) { 
14
			this.stop(moduleID); 
15
		} 
16
	} 
17
},

Die nächsten beiden Funktionen sollten offensichtlich sein: stop und stop_all. Die stop-Funktion nimmt einen Modulnamen an; Wenn das System ein Modul mit diesem Namen kennt und dieses Modul ausgeführt wird, rufen wir die destroy-Methode des Moduls auf und und setzen dann die Instanz auf null. Wenn das Modul nicht existiert oder nicht ausgeführt wird, protokollieren wir den Fehler.

Die Funktion stop_all ist genau wie start_all, außer dass sie in jedem Modul stop aufruft.

Als nächstes: Umgang mit den Ereignissen.

1
registerEvents : function (evts, mod) { 
2
	if (this.is_obj(evts) && mod) { 
3
		if (moduleData[mod]) { 
4
			moduleData[mod].events = evts; 
5
		} else { 
6
			this.log(1, ""); 
7
		} 
8
	} else { 
9
		this.log(1, ""); 
10
	} 
11
}, 
12
triggerEvent : function (evt) { 
13
	var mod; 
14
	for (mod in moduleData) { 
15
		if (moduleData.hasOwnProperty(mod)){ 
16
			mod = moduleData[mod]; 
17
			if (mod.events && mod.events[evt.type]) { 
18
				mod.events[evt.type](evt.data); 
19
			} 
20
		} 
21
	} 
22
}, 
23
removeEvents : function (evts, mod) { 
24
	var i = 0, evt; 
25
	if (this.is_arr(evts) && mod && (mod = moduleData[mod]) && mod.events) { 
26
		for ( ; evt = evts[i++] ; ) { 
27
				delete mod.events[evt]; 
28
			} 
29
	} 
30
},

Wie wir wissen, verwendet registerEvents ein Ereignisobjekt und das Modul, das sie registriert. Auch hier führen wir einige Fehlerüberprüfungen durch (ich habe die Fehler in diesem Fall leer gelassen, nur um das Beispiel zu vereinfachen). Wenn evts ein Objekt ist und wir wissen, von welchem Modul wir sprechen, stopfen wir das Objekt einfach in das Schließfach des Moduls in moduleData.

Wenn es um das Auslösen von Ereignissen geht, erhalten wir ein Objekt mit einem Typ und Daten. Wir werden jedes Modul in moduleData noch einmal durchlaufen: Wenn das Modul eine Ereigniseigenschaft hat und dieses Ereignisobjekt einen Schlüssel hat, der dem Ereignis entspricht, das wir ausführen, rufen wir die für dieses Ereignis gespeicherte Funktion auf und übergeben ihr die Ereignisnummer Daten.

Das Entfernen von Ereignissen ist noch einfacher. wir erhalten das events-Objekt und (nach der üblichen Fehlerprüfung) durchlaufen wir es und entfernen die Events im Array aus dem Event-Objekt des Moduls. (Hinweis: Ich glaube, ich habe das beim Screencast etwas durcheinander gebracht, aber dies ist die richtige Version.)

1
log : function (severity, message) { 
2
	if (debug) { 
3
		console[ (severity === 1) ? 'log' : (severity === 2) ? 'warn' : 'error'](message); 
4
	} else { 
5
		// send to the server 
6
	}      
7
},

Hier ist die Log-Funktion, die uns schon seit einiger Zeit verfolgt; grundsätzlich, wenn wir uns im Debug-Modus befinden, werden wir Fehler in der Konsole protokollieren; andernfalls senden wir sie an den Server. Oh, das schicke ternäre Zeug? Das verwendet nur das Schweregrad-Argument, um zu entscheiden, welche der Funktionen von Firebug verwendet werden soll, um den Fehler zu protokollieren: 1 === console.log, 2 === console.warn, >2 === console.error.

Jetzt können wir uns den Teil des Kerns ansehen, der der Sandbox die Basisfunktionalität verleiht. für das meiste davon habe ich sie in ein dom-Objekt eingeordnet, weil ich auf diese Weise zwanghaft bin.

1
dom : { 
2
	query : function (selector, context) { 
3
		var ret = {}, that = this, jqEls, i = 0; 
4
 
5
		if (context && context.find) { 
6
			jqEls = context.find(selector); 
7
		} else { 
8
			jqEls = jQuery(selector); 
9
		} 
10
		 
11
		ret = jqEls.get(); 
12
		ret.length = jqEls.length; 
13
		ret.query = function (sel) { 
14
			return that.query(sel, jqEls); 
15
		} 
16
		return ret; 
17
	},

Hier ist unsere erste Funktion, query. Sie braucht einen Selektor und einen Kontext. Denken Sie daran, dies ist der Kern, auf den wir direkt auf die Basis verweisen können (die jQuery ist). In diesem Fall sollte der Kontext ein jQuery-Objekt sein. Wenn der Kontext eine find-Methode hat, setzen wir jqEls auf das Ergebnis von context.find(selector); Wenn Sie mit jQuery vertraut sind, wissen Sie, dass dies nur die Elemente erhält, die untergeordnete Elemente des context sind. So erhalten wir die Funktionalität von sandbox.query! Dann setzen wir unser Rückgabeobjekt auf das Ergebnis des Aufrufs der get-Methode von jQuery; Dies gibt ein Objekt mit unformatierten dom-Elementen zurück. Dann geben wir ret die Länge-Eigenschaft, damit sie leicht durchgeschleift werden kann. Schließlich geben wir ihm eine Abfragefunktion: Diese Funktion benötigt nur einen Parameter, einen Selektor, und ruft core.dom.query auf, wobei dieser Selektor und jqEls als Parameter übergeben werden. Das war's!

1
bind : function (element, evt, fn) { 
2
	if (element && evt) { 
3
		if (typeof evt === 'function') { 
4
			fn = evt; 
5
			evt = 'click'; 
6
		} 
7
		jQuery(element).bind(evt, fn); 
8
	} else { 
9
		// log wrong arguments 
10
	} 
11
}, 
12
unbind : function (element, evt, fn) { 
13
	if (element && evt) { 
14
		if (typeof evt === 'function') { 
15
			fn = evt; 
16
			evt = 'click'; 
17
		} 
18
		jQuery(element).unbind(evt, fn); 
19
	} else { 
20
		// log wrong arguments 
21
	} 
22
},

In den DOM-Event-Bind- und Unbind-Funktionen habe ich beschlossen, dem Benutzer einen Vorteil bereitzustellen. Die Benutzer müssen mindestens zwei Funktionen übergeben, aber wenn der evt-Parameter eine Funktion ist, gehen wir davon aus, dass der Benutzer den Ereignistyp verlassen hat, den er verarbeiten möchte. In diesem Fall gehen wir von einem Klick aus, da er am häufigsten vorkommt. Dann verwenden wir einfach die Bindefunktion von jQuery, um es zu verbinden. Ich sollte beachten, dass, da unsere Abfragefunktion (minimal) umschlossene DOM-Sätze zurückgibt, ähnlich wie die von jQuery, diese Funktion unser Set an das Jquery-Objekt übergeben kann und keine Probleme damit hat.

Unsere Funktion zum Aufheben der Bindung ist genau dieselbe, außer dass sie natürlich die Ereignisse aufhebt.

1
    create: function (el) { 
2
        return document.createElement(el);         
3
    }, 
4
    apply_attrs: function (el, attrs) { 
5
        jQuery(el).attr(attrs);              
6
    } 
7
}, // end of dom object 
8
is_arr : function (arr) { 
9
    return jQuery.isArray(arr);          
10
}, 
11
is_obj : function (obj) { 
12
    return jQuery.isPlainObject(obj);          
13
}

Ich habe die letzten vier Funktionen zusammengefasst, weil sie alle ziemlich einfach sind; dom.create gibt einfach ein neues DOM-Element zurück; dom.apply_attrs verwendet die attr-Methode von jQuery, um den Elementattributen zuzuweisen. Schließlich haben wir die beiden Hilfsfunktionen, die wir verwendet haben, um unsere Parameter zu überprüfen.

Ob Sie es glauben oder nicht, das ist der ganze Kern; und unsere Basis ist jQuery, also sind wir bereit, dies zusammenzustellen.


Alles zusammenfügen

Jetzt können wir etwas HTML erstellen, damit wir unser JavaScript in Aktion sehen können. Ich werde dies nicht im Detail durchgehen, da dies nicht der Sinn des Tutorials ist.

1
<!DOCTYPE HTML> 
2
<html lang="en"> 
3
<head> 
4
    <meta charset="UTF-8"> 
5
    <title>Online Store</title> 
6
    <link rel="stylesheet" href="default.css" /> 
7
</head> 
8
<body> 
9
    <div id="main"> 
10
        <div id="search-box"> 
11
            <input id="search_input" type="text" name='q' /> 
12
            <button id="search_button">Search</button> 
13
            <button id="quit_search">Reset</button> 
14
        </div> 
15
 
16
        <div id="filters-bar"> 
17
            <ul> 
18
                <li><a href="#red">Red</a></li> 
19
                <li><a href="#blue">Blue</a></li> 
20
                <li><a href="#mobile">Mobile</a></li> 
21
                <li><a href="#accessory">Accessory</a></li> 
22
            </ul> 
23
        </div> 
24
 
25
       <div id="product-panel"> 
26
            <ul> 
27
                <li id="1" data-8088-keyword="red"><img src="img/1.jpg"><p>First Item</p></li> 
28
                <li id="2" data-8088-keyword="blue"><img src="img/2.jpg"><p>Second Item</p></li> 
29
                <li id="3" data-8088-keyword="mobile"><img src="img/3.jpg"><p>Third Item</p></li> 
30
                <li id="4" data-8088-keyword="accessory"><img src="img/4.jpg"><p>Fourth Item</p></li> 
31
                <li id="5" data-8088-keyword="red mobile"><img src="img/5.jpg"><p>Fifth Item</p></li> 
32
                <li id="6" data-8088-keyword="blue mobile"><img src="img/6.jpg"><p>Sixth Item</p></li> 
33
                <li id="7" data-8088-keyword="red accessory"><img src="img/7.jpg"><p>Seventh Item </p></li> 
34
                <li id="8" data-8088-keyword="blue accessory"><img src="img/8.jpg"><p>Eighth Item</p></li> 
35
                <li id="9" data-8088-keyword="red blue"><img src="img/9.jpg"><p>Ninth Item</p></li> 
36
                <li id="10" data-8088-keyword="mobile accessory"><img src="img/10.jpg"><p>Tenth Item</p></li> 
37
            </ul> 
38
 
39
        </div> 
40
         
41
        <div id="shopping-cart"> 
42
            <ul> 
43
            </ul>  
44
        </div>  
45
    </div> 
46
    <script src="js/jquery.js"></script> 
47
    <script src="js/core-jquery.js"></script> 
48
    <script src="js/sandbox.js"></script> 
49
    <script src="js/modules.js"></script> 
50
</body> 
51
</html>

Es ist nichts Besonderes; Es ist wichtig zu beachten, dass jedes der Haupt-Divs IDs hat, die den JavaScript-Modulen entsprechen. Und vergessen Sie nicht die HTML5 data-*-Attribute, die uns die Kategorien zum Filtern geben.

Natürlich müssen wir es stylen:

1
 
2
body { 
3
    background:#ececec; 
4
    font:13px/1.5 helvetica, arial, san-serif; 
5
} 
6
#main {  
7
    width:950px; 
8
    margin:auto;  
9
    overflow:hidden;  
10
} 
11
#search-box, #filters-bar {  
12
    margin-left:10px;  
13
} 
14
#filters-bar ul {  
15
    list-style-type:none;  
16
    margin:10px 0;  
17
    padding:0;  
18
    border-top:2px solid #474747;  
19
    border-bottom:2px solid #474747;  
20
    } 
21
#filters-bar li {  
22
    display:inline-block;  
23
    padding:5px 10px 5px 0; 
24
} 
25
#filters-bar li a { 
26
    text-decoration:none; 
27
    font-weight:bold; 
28
    color:#474747; 
29
} 
30
#product-panel { 
31
    float:left; 
32
    width: 588px; 
33
} 
34
#product-panel ul { 
35
    margin:0; 
36
    padding:0; 
37
} 
38
#product-panel li { 
39
    list-style-type:none; 
40
    display:inline-block; 
41
    text-align:center; 
42
    background:#474747; 
43
    border:1px solid #eee; 
44
    padding:15px; 
45
    margin:10px; 
46
} 
47
#product-panel li p { 
48
    margin:10px 0 0 0; 
49
} 
50
 
51
#shopping-cart { 
52
    float:left; 
53
    background:#ccc; 
54
    height:300px; 
55
    width:300px; 
56
    padding:30px; 
57
    border:1px solid #474747; 
58
} 
59
 
60
#shopping-cart ul { 
61
    list-style-type:none; 
62
    padding:0; 
63
} 
64
 
65
#shopping-cart li { 
66
    padding:3px; 
67
    margin:2px 0; 
68
    background:#ececec; 
69
    border: 1px solid #333; 
70
} 
71
 
72
#shopping-cart .product_name { 
73
    display:inline-block; 
74
    width:230px; 
75
} 
76
 
77
#shopping-cart .price { 
78
    display: inline-block; 
79
    float:right; 
80
}

Es ist schwer, es in Aktion zu zeigen (dafür ist der Screencast da!), aber hier sind ein paar Aufnahmen:

Nun, das wäre alles, aber lassen Sie uns noch etwas tun: Lassen Sie uns einen Kern erstellen, der auf Dojo funktioniert, um zu zeigen, wie die Verwendung eines Kerns, wie wir es getan haben, das Wechseln der Basen erleichtert.

Warum Dojo? Um ganz ehrlich zu sein, habe ich Mootools und YUI ausprobiert, aber es gab ein paar Herausforderungen, die länger dauern würden als die Zeit, die ich brauchte, um es herauszufinden. Ich denke sicherlich nicht, dass es unmöglich ist, sie zu verwenden; Wenn Sie einen Kern mit Mootools, YUI oder einem anderen JavaScript-Framework erstellen, würde ich ihn gerne sehen. Lassen Sie uns vorerst einen mit Dojo erstellen.

Natürlich müssen wir keine der Modul-Handling-Funktionen ändern; In unserem Fall müssen wir lediglich den Abschnitt dom, is_arr und is_obj ändern.

1
query : function (selector, context) { 
2
    var ret = {}, that = this, len, i =0, djEls; 
3
 
4
    djEls = dojo.query( ((context) ? context + " " : "") + selector); 
5
 
6
    len = djEls.length; 
7
 
8
    while ( i < len) { 
9
        ret[i] = djEls[i++]; 
10
    } 
11
    ret.length = len; 
12
    ret.query = function (sel) { 
13
        return that.query(sel, selector); 
14
    } 
15
    return ret; 
16
},

Hier ist die Abfragefunktion, die so umgeschrieben wurde, dass sie mit Dojo funktioniert. Wie Sie sehen können, macht es genau das, was die jQuery-Version tut; Der Hauptunterschied besteht darin, dass Kontext eine Zeichenfolge ist, die am Anfang des Selektors angehängt wird.

Nun zu den DOM-Ereignisfunktionen:

1
eventStore : {}, 
2
bind : function (element, evt, fn) { 
3
    if (element && evt) { 
4
        if (typeof evt === 'function') { 
5
            fn = evt; 
6
            evt = 'click'; 
7
        } 
8
        if (element.length) { 
9
            var i = 0, len = element.length; 
10
            for ( ; i < len ; ) { 
11
                this.eventStore[element[i] + evt + fn] = dojo.connect(element[i], evt, element[i], fn); 
12
                i++; 
13
            } 
14
        } else { 
15
           this.eventStore[element + evt + fn] = dojo.connect(element, evt, element, fn); 
16
        } 
17
    } 
18
}, 
19
unbind : function (element, evt, fn) { 
20
    if (element && evt) { 
21
        if (typeof evt === 'function') { 
22
            fn = evt; 
23
            evt = 'click'; 
24
        } 
25
         if (element.length) { 
26
            var i = 0, len = element.length; 
27
            for ( ; i < len ; ) { 
28
                dojo.disconnect(this.eventStore[element[i] + evt + fn]); 
29
                delete this.eventStore[element[i] + evt + fn]; 
30
                i++; 
31
            } 
32
        } else { 
33
            dojo.disconnect(this.eventStore[element + evt + fn]); 
34
            delete this.eventStore[element + evt + fn]; 
35
        } 
36
    } 
37
},

Sie werden feststellen, dass wir ein eventStore-Objekt hinzugefügt haben. Dies liegt daran, dass Dojo Ereignisse mit dojo.connect bindet und mit dojo.disconnect aufhebt; Der Haken dabei ist, dass dojo.disconnect das von dojo.connect zurückgegebene Objekt als einzigen Parameter verwendet. Wir verwenden eventStore, um diese Werte zu verfolgen, damit wir Ereignisse leicht trennen können. Der Rest der Komplexität hier besteht nur darin, unsere verpackten DOM-Sets zu durchlaufen, da Dojo diese nicht allein bewältigen kann.

1
    create: function (el) { 
2
        return document.createElement(el);         
3
    }, 
4
    apply_attrs: function (el, attrs) { 
5
        var attr; 
6
        for (attr in attrs) { 
7
            dojo.attr(el, attr, attrs[attr]); 
8
        } 
9
    } 
10
}, // end of dom object 
11
is_arr : function (arr) { 
12
    return dojo.isArray(arr); 
13
}, 
14
is_obj : function (obj) { 
15
    return dojo.isObject(obj); 
16
}

Dieser Teil sollte nicht allzu schwer zu verstehen sein; es ist alles ziemlich ähnlich zu jQuery.

Jetzt sollten Sie in der Lage sein, Dojo als Ihre Basis und unseren neuen Dojo-Kern als Ihren Kern aufzunehmen und unsere App wird weiterhin wie bisher funktionieren.


Abschluss: Sollten Sie auf diese Weise codieren?

Nun, da wir genau gesehen haben, wie ein solches System funktionieren sollte, lassen Sie uns darüber sprechen, ob Sie Ihre JavaScript-Apps so schreiben möchten. Offensichtlich ist dies kein Muster, das Sie auf Ihrer durchschnittlichen Website verwenden würden. Es ist für vollwertige Anwendungen. Folgendes ist mir eingefallen:

Nachteile:

  • Es kann ziemlich schwierig sein, diese Art der Codierung zu erlernen. Modulares Denken erfordert definitiv einen Paradigmenwechsel.
  • Wenn Sie anfangen, ist es schwer zu wissen, wie viel Kraft Sie den Schichten geben sollen. Was sollen Module können? Sollte die Sandbox echte Funktionalität enthalten oder ist sie nur dazu da, den Modulen die richtige Menge an Kernfunktionalität zur Verfügung zu stellen? Wo führen wir Fehlerprüfungen durch? Können wir innerhalb der Module einfache Aufgaben wie das Erstellen von Dom-Elementen ausführen? Ich bin sicher, Sie haben Ihre eigenen Ansichten dazu und weitere Fragen, also lassen Sie mich Ihre Meinung wissen, entweder in den Kommentaren zum Nettuts+-Beitrag oder über das Formular auf meiner Website. Oder noch besser: Schreiben Sie auf Ihrem Blog / Ihrer Website darüber, damit die Welt es sehen kann, und senden Sie mir unbedingt einen Link.
  • Schließlich ist es seltsam, ein JavaScript-Framework als Basis zu verwenden; Mit meinem derzeitigen Verständnis davon eignen sie sich nicht wirklich dafür.

Vorteile

  • Sobald Sie einen soliden Kern und eine Sandbox haben, wird das Erstellen neuer Web-Apps viel schneller sein. Es geht im Grunde genommen darum, Module aus Ihrer ständig wachsenden Modulbibliothek auszuwählen und sie miteinander zu verbinden.
  • Es ist viel einfacher, Ihren Code zu testen, weil alles so modular ist.

Nun, das ist alles, was ich heute für Sie habe; Ich hoffe, ich habe Ihren Verstand ein wenig gedehnt; Ich weiß, dass das Lernen über all dies in der letzten Woche oder so meins gestreckt hat! Ich würde gerne wissen, was Sie davon halten, also lassen Sie es mich bitte wissen, wenn Sie Kommentare haben! Danke fürs Lesen!