Haben Sie sich jemals gefragt, wie Frameworks funktionieren? Der Autor des Materials, dessen Übersetzung wir heute veröffentlichen, sagt, als er vor vielen Jahren nach dem Studium von
jQuery auf
Angular.js stieß , schien
ihm das , was er sah, sehr kompliziert und unverständlich. Dann erschien Vue.js und als er sich mit diesem Framework befasste, wurde er inspiriert, sein eigenes System der bidirektionalen
Datenbindung zu schreiben. Solche Experimente tragen zur beruflichen Entwicklung des Programmierers bei. Dieser Artikel richtet sich an diejenigen, die ihr eigenes Wissen auf dem Gebiet der Technologien erweitern möchten, auf denen moderne JS-Frameworks basieren. Insbesondere geht es darum, wie Sie den Kern Ihres eigenen Frameworks schreiben, das benutzerdefinierte Attribute von HTML-Elementen, Reaktivität und bidirektionale Datenbindung unterstützt.

Über das Reaktivitätssystem
Es wird gut sein, wenn wir gleich zu Beginn herausfinden, wie die Reaktivitätssysteme moderner Frameworks funktionieren. Tatsächlich ist hier alles ganz einfach. Zum Beispiel überträgt Vue.js beim Deklarieren einer neuen Komponente
jede Eigenschaft (Getter und Setter) mithilfe des Entwurfsmusters
"Proxy ".
Infolgedessen kann das Framework jede Tatsache erkennen, dass sich der Wert ändert, und zwar sowohl über den Code als auch über die Benutzeroberfläche.
Proxy-Muster
Das Proxy-Muster basiert auf dem Überladen von Zugriffsmechanismen auf ein Objekt. Dies ähnelt der Arbeitsweise von Personen mit ihren Bankkonten.
Zum Beispiel kann niemand direkt mit seinem Konto arbeiten und sein Guthaben entsprechend seinen Bedürfnissen ändern. Um Aktionen mit dem Konto ausführen zu können, müssen Sie sich an jemanden wenden, der die Erlaubnis hat, mit ihm zu arbeiten, dh an die Bank. Die Bank fungiert als Proxy zwischen dem Kontoinhaber und dem Konto selbst.
var account = { balance: 5000 } var bank = new Proxy(account, { get: function (target, prop) { return 9000000; } }); console.log(account.balance);
In diesem Beispiel wird bei Verwendung des Bankobjekts für den Zugriff auf den durch das Kontoobjekt dargestellten
account
die Getter-Funktion überlastet, was dazu führt, dass aufgrund einer solchen Anforderung immer der Wert 9.000.000 anstelle des Immobilienwerts zurückgegeben wird, auch wenn Diese Eigenschaft existiert nicht.
Und hier ist ein Beispiel für die Überlastung einer Setterfunktion.
var bank = new Proxy(account, { set: function (target, prop, value) { // 0 return Reflect.set(target, prop, 0); } }); account.balance = 5800; console.log(account.balance); // 5,800 bank.balance = 5400; console.log(account.balance); // 0 ( )
Hier können Sie durch Überladen der
set
Funktion deren Verhalten steuern. Sie können beispielsweise den Wert ändern, den Sie in eine Eigenschaft schreiben möchten, Daten in eine andere Eigenschaft schreiben oder einfach nichts tun.
Beispiel für ein Reaktivitätssystem
Nachdem wir das Proxy-Muster herausgefunden haben, werden wir mit der Entwicklung unseres eigenen JS-Frameworks beginnen.
Um dies nicht zu komplizieren, verwenden wir eine Syntax, die der in Angular.js verwendeten sehr ähnlich ist. Infolgedessen sehen die Deklaration des Controllers und die Bindung von Vorlagenelementen an die Eigenschaften des Controllers einfach und klar aus.
Hier ist der Vorlagencode.
<div ng-controller="InputController"> <input ng-bind="message"/> <input ng-bind="message"/> </div> <script type="javascript"> function InputController () { this.message = 'Hello World!'; } angular.controller('InputController', InputController); </script>
Zuerst müssen Sie einen Controller mit Eigenschaften deklarieren. Verwenden Sie als Nächstes diesen Controller in der Vorlage und schließlich das Attribut
ng-bind
, um eine bidirektionale Datenbindung für den Elementwert herzustellen.
Analysieren einer Vorlage und Instanziieren eines Controllers
Damit wir bestimmte Eigenschaften haben, an die Daten angehängt werden können, benötigen wir einen Ort (Controller), an dem diese Eigenschaften deklariert werden können. Daher ist es notwendig, den Controller zu beschreiben und in das Framework aufzunehmen.
Während der Arbeit mit Controllern sucht das Framework nach Elementen mit
ng-controller
Attributen. Wenn das, was Sie finden, mit einem der deklarierten Controller übereinstimmt, erstellt das Framework eine neue Instanz dieses Controllers. Diese Controller-Instanz ist nur für dieses bestimmte Fragment der Vorlage verantwortlich.
var controllers = {}; var addController = function (name, constructor) { // controllers[name] = { factory: constructor, instances: [] }; // , var element = document.querySelector('[ng-controller=' + name + ']'); if (!element){ return; // , } // var ctrl = new controllers[name].factory; controllers[name].instances.push(ctrl); // ..... }; addController('InputController', InputController);
Unten ist die Deklaration eines "hausgemachten" Variablenreglers. Beachten Sie, dass das
controllers
Objekt alle im Framework deklarierten
controllers
enthält, indem Sie
addController
aufrufen.
var controllers = { InputController: { factory: function InputController(){ this.message = "Hello World!"; }, instances: [ {message: "Hello World"} ] } };
Jeder Controller verfügt über eine
factory
Funktion. Dies geschieht, damit Sie bei Bedarf eine Instanz eines neuen Controllers erstellen können. Darüber hinaus speichert das Framework in der
instances
alle Instanzen des Controllers desselben Typs, der in der Vorlage verwendet wird.
Suchen Sie nach Elementen, die an der Datenbindung beteiligt sind
Im Moment haben wir eine Instanz des Controllers und ein Fragment der Vorlage, die diesen Controller verwendet. Unser nächster Schritt besteht darin, nach Elementen mit Daten zu suchen, die Sie an die Eigenschaften des Controllers binden müssen.
var bindings = {}; // : element DOM, Array.prototype.slice.call(element.querySelectorAll('[ng-bind]')) .map(function (element) { var boundValue = element.getAttribute('ng-bind'); if(!bindings[boundValue]) { bindings[boundValue] = { boundValue: boundValue, elements: [] } } bindings[boundValue].elements.push(element); });
Hier wird die Organisation der Speicherung aller Objektbindungen mithilfe einer
Hash-Tabelle gezeigt. Die betrachtete Variable enthält alle Eigenschaften für die Bindung mit ihren aktuellen Werten und alle DOM-Elemente, die an eine bestimmte Eigenschaft gebunden sind.
So sieht unsere Version der
bindings
aus:
var bindings = { message: { // : // controllers.InputController.instances[0].message boundValue: 'Hello World', // HTML- ( ng-bind="message") elements: [ Object { ... }, Object { ... } ] } };
Bilaterale Bindung von Controller-Eigenschaften
Nachdem das Framework die vorläufige Vorbereitung abgeschlossen hat, ist die Zeit für eine interessante Sache gekommen: die bidirektionale Datenbindung. Die Bedeutung davon ist wie folgt. Erstens müssen die Eigenschaften des Controllers an die DOM-Elemente gebunden werden, damit sie aktualisiert werden können, wenn sich die Eigenschaftswerte gegenüber dem Code ändern, und zweitens müssen die DOM-Elemente auch an die Eigenschaften des Controllers gebunden sein. Wenn der Benutzer auf solche Elemente einwirkt, führt dies zu einer Änderung der Eigenschaften des Controllers. Wenn mehrere HTML-Elemente an die Eigenschaft angehängt werden, führt dies auch dazu, dass ihr Status ebenfalls aktualisiert wird.
Erkennen von Änderungen aus Code mithilfe eines Proxys
Wie oben erwähnt, verpackt Vue.js Komponenten in einen Proxy, um Eigenschaftsänderungen zu erkennen. Wir werden dasselbe tun, indem wir nur den Setter für die Controller-Eigenschaften, die an der Datenbindung beteiligt sind, als Proxy verwenden:
// : ctrl - var proxy = new Proxy(ctrl, { set: function (target, prop, value) { var bind = bindings[prop]; if(bind) { // DOM, bind.elements.forEach(function (element) { element.value = value; element.setAttribute('value', value); }); } return Reflect.set(target, prop, value); } });
Als Ergebnis stellt sich heraus, dass der Proxy beim Schreiben des Werts in die gebundene Eigenschaft alle an diese Eigenschaft gebundenen Elemente erkennt und ihnen einen neuen Wert übergibt.
In diesem Beispiel unterstützen wir nur die Bindung von
input
, da hier nur das
value
Attribut festgelegt wird.
Reaktion auf Elementereignisse
Jetzt müssen wir nur noch eine Systemantwort auf Benutzeraktionen bereitstellen. Berücksichtigen Sie dabei die Tatsache, dass DOM-Elemente Ereignisse auslösen, wenn sie Änderungen in ihren Werten erkennen.
Wir organisieren das Abhören dieser Ereignisse und das Aufzeichnen in den Eigenschaften, die den Elementen zugeordnet sind, die neue Daten aus Ereignissen erhalten. Alle anderen Elemente, die an dieselbe Eigenschaft gebunden sind, werden dank des Proxys automatisch aktualisiert.
Object.keys(bindings).forEach(function (boundValue) { var bind = bindings[boundValue]; // bind.elements.forEach(function (element) { element.addEventListener('input', function (event) { proxy[bind.boundValue] = event.target.value; // , }); }) });
Zusammenfassung
Wenn Sie alles zusammenstellen, was wir hier besprochen haben, erhalten Sie ein eigenes System, das die grundlegenden Mechanismen moderner JS-Frameworks implementiert.
Hier ist ein Arbeitsbeispiel für die Implementierung eines solchen Systems. Wir hoffen, dass dieses Material allen hilft, besser zu verstehen, wie moderne Webentwicklungstools funktionieren.
Liebe Leser! Wenn Sie moderne JS-Frameworks professionell verwenden, teilen Sie uns bitte mit, wie Sie mit dem Studium begonnen haben und was Ihnen geholfen hat, sie zu verstehen.
