Der Inhalt dieses Artikels befasst sich im Detail mit der Implementierung der Vue-Zwei-Wege-Bindung. Ich hoffe, er wird Ihnen als Referenz dienen.
Die heutige Front-End-Welt wird von den drei Säulen Angular, React und Vue dominiert. Wenn Sie sich nicht für ein Lager entscheiden, können Sie im Front-End grundsätzlich nicht Fuß fassen sich für zwei oder drei Lager zu entscheiden. Das ist der allgemeine Trend.
Also müssen wir immer neugierig bleiben und Veränderungen annehmen. Nur durch ständige Veränderungen kann man nur auf den Tod warten.
Ich habe Vue kürzlich gelernt und habe nur ein begrenztes Verständnis für die bidirektionale Bindung. Ich habe vor, es in den letzten Tagen eingehend zu studieren Durch Ansehen der Informationen habe ich ein gewisses Verständnis für die Prinzipien gewonnen, die ich kenne, also habe ich selbst ein Beispiel für die bidirektionale Bindung geschrieben. Mal sehen, wie man es Schritt für Schritt umsetzt.
Ich glaube, dass Sie nach der Lektüre dieses Artikels ein klares Verständnis für das bidirektionale Bindungsprinzip von Vue haben werden. Es kann uns auch helfen, Vue besser zu verstehen.
Schauen Sie sich zuerst die Renderings an
//代码: <div> <input> <h1>{{name}}</h1> </div> <script></script> <script></script> <script></script> <script></script> <script> const vm = new Mvue({ el: "#app", data: { name: "我是摩登" } }); </script>
Lass uns darüber reden, bevor wir anfangen Datenbindung, mein Verständnis der Datenbindung besteht darin, die Daten M (Modell) in der Ansicht V (Ansicht) anzuzeigen. Zu unseren gängigen Architekturmustern gehören MVC-, MVP- und MVVM-Muster. Derzeit verwenden Front-End-Frameworks grundsätzlich das MVVM-Muster, um die bidirektionale Bindung zu implementieren, und Vue ist keine Ausnahme. Allerdings implementiert jedes Framework die bidirektionale Bindung auf leicht unterschiedliche Weise. Derzeit gibt es ungefähr drei Implementierungsmethoden.
Veröffentlichungs- und Abonnementmodell
Angulars Dirty-Checking-Mechanismus
Datenentführung
Vue verwendet eine Kombination aus Datenhijacking und Veröffentlichung und Abonnement, um eine bidirektionale Bindung zu erreichen. Datenhijacking wird hauptsächlich durch Object.defineProperty
erreicht.
In diesem Artikel werden wir die Verwendung von Object.defineProperty nicht im Detail besprechen, sondern uns hauptsächlich mit dem Abrufen und Festlegen der Speichereigenschaften befassen. Werfen wir einen Blick darauf, was passiert, nachdem die Objekteigenschaften dadurch festgelegt wurden.
var people = { name: "Modeng", age: 18 } people.age; //18 people.age = 20;
Der obige Code dient nur zum normalen Abrufen/Festlegen der Eigenschaften des Objekts und es sind keine seltsamen Änderungen erkennbar.
var modeng = {} var age; Object.defineProperty(modeng, 'age', { get: function () { console.log("获取年龄"); return age; }, set: function (newVal) { console.log("设置年龄"); age = newVal; } }); modeng.age = 18; console.log(modeng.age);
Sie werden feststellen, dass nach den oben genannten Vorgängen die Get-Funktion automatisch ausgeführt wird. Beim Festlegen des Altersattributs wird die Set-Funktion automatisch ausgeführt Möglichkeit für unsere Zwei-Wege-Bindung.
Wir wissen, dass das MVVM-Muster in der Synchronisierung von Daten und Ansichten liegt, was bedeutet, dass die Ansicht automatisch aktualisiert wird, wenn sich die Daten ändern, und die Daten aktualisiert werden, wenn sich die Daten ändern Änderungen anzeigen.
Was wir also tun müssen, ist, wie wir Änderungen in den Daten erkennen und uns dann benachrichtigen, die Ansicht zu aktualisieren, und wie wir Änderungen in der Ansicht erkennen und dann die Daten aktualisieren. Das Erkennen von Ansichten ist relativ einfach und besteht lediglich aus der Verwendung der Ereignisüberwachung.
Wie können wir also wissen, dass sich die Datenattribute geändert haben? Dabei wird die oben erwähnte Object.defineProperty verwendet. Wenn sich unsere Eigenschaften ändern, wird automatisch die Set-Funktion ausgelöst, um uns zu benachrichtigen, die Ansicht zu aktualisieren.
Durch die obige Beschreibung und Analyse wissen wir, dass Vue eine bidirektionale Bindung durch Datenhijacking in Kombination mit dem implementiert Publish-Subscribe-Modell von. Wir wissen auch, dass die Datenentführung über Object.defineProperty erfolgt Methode: Wenn wir das wissen, brauchen wir einen Listener-Beobachter, der auf Änderungen in den Eigenschaften wartet. Nachdem wir erfahren haben, dass sich die Eigenschaften geändert haben, benötigen wir einen Beobachter Um die Ansicht des Abonnenten zu aktualisieren, benötigen wir außerdem einen Parser für Kompilierungsanweisungen, um die Anweisungen unserer Knotenelemente zu analysieren und die Ansicht zu initialisieren. Wir benötigen also Folgendes:
Beobachter: Wird verwendet, um Änderungen in Attributen zu überwachen, um Abonnenten zu benachrichtigen.
Beobachter-Abonnenten: Attribute empfangen, ändern und dann aktualisieren die Ansicht
Parser kompilieren: Anweisungen analysieren, Vorlagen initialisieren, Abonnenten binden
Dieser Idee folgend werden wir sie Schritt für Schritt umsetzen.
Die Rolle des Listeners besteht darin, jedes Attribut der Daten zu überwachen. Wir haben oben auch die Verwendung der Object.defineProperty
-Methode erwähnt Wir müssen die Watcher-Abonnenten benachrichtigen und die Aktualisierungsfunktion ausführen, um die Ansicht zu aktualisieren. In diesem Prozess haben wir möglicherweise viele Abonnenten-Watcher, daher müssen wir eine Container-Dep für eine einheitliche Verwaltung erstellen.
function defineReactive(data, key, value) { //递归调用,监听所有属性 observer(value); var dep = new Dep(); Object.defineProperty(data, key, { get: function () { if (Dep.target) { dep.addSub(Dep.target); } return value; }, set: function (newVal) { if (value !== newVal) { value = newVal; dep.notify(); //通知订阅器 } } }); } function observer(data) { if (!data || typeof data !== "object") { return; } Object.keys(data).forEach(key => { defineReactive(data, key, data[key]); }); } function Dep() { this.subs = []; } Dep.prototype.addSub = function (sub) { this.subs.push(sub); } Dep.prototype.notify = function () { console.log('属性变化通知 Watcher 执行更新视图函数'); this.subs.forEach(sub => { sub.update(); }) } Dep.target = null;
Wir haben oben einen Listener-Beobachter erstellt. Jetzt können wir versuchen, einem Objekt einen Listener hinzuzufügen und dann die Eigenschaften zu ändern.
var modeng = { age: 18 } observer(modeng); modeng.age = 20;
我们可以看到浏览器控制台打印出 “属性变化通知 Watcher 执行更新视图函数” 说明我们实现的监听器没毛病,既然监听器有了,我们就可以通知属性变化了,那肯定是需要 Watcher 的时候了。
Watcher 主要是接受属性变化的通知,然后去执行更新函数去更新视图,所以我们做的主要是有两步:
把 Watcher 添加到 Dep 容器中,这里我们用到了 监听器的 get 函数
接收到通知,执行更新函数。
function Watcher(vm, prop, callback) { this.vm = vm; this.prop = prop; this.callback = callback; this.value = this.get(); } Watcher.prototype = { update: function () { const value = this.vm.$data[this.prop]; const oldVal = this.value; if (value !== oldVal) { this.value = value; this.callback(value); } }, get: function () { Dep.target = this; //储存订阅器 const value = this.vm.$data[this.prop]; //因为属性被监听,这一步会执行监听器里的 get方法 Dep.target = null; return value; } }
这一步我们把 Watcher 也给弄了出来,到这一步我们已经实现了一个简单的双向绑定了,我们可以尝试把两者结合起来看下效果。
function Mvue(options, prop) { this.$options = options; this.$data = options.data; this.$prop = prop; this.$el = document.querySelector(options.el); this.init(); } Mvue.prototype.init = function () { observer(this.$data); this.$el.textContent = this.$data[this.$prop]; new Watcher(this, this.$prop, value => { this.$el.textContent = value; }); }
这里我们尝试利用一个实例来把数据与需要监听的属性传递进来,通过监听器监听数据,然后添加属性订阅,绑定更新函数。
<p>{{name}}</p> const vm = new Mvue({ el: "#app", data: { name: "我是摩登" } }, "name");
我们可以看到数据已经正常的显示在页面上,那么我们在通过控制台去修改数据,发生变化后视图也会跟着修改。
到这一步我们我们基本上已经实现了一个简单的双向绑定,但是不难发现我们这里的属性都是写死的,也没有指令模板的解析,所以下一步我们来实现一个模板解析器。
Compile 的主要作用一个是用来解析指令初始化模板,一个是用来添加添加订阅者,绑定更新函数。
因为在解析 DOM 节点的过程中我们会频繁的操作 DOM, 所以我们利用文档片段(DocumentFragment)来帮助我们去解析 DOM 优化性能。
function Compile(vm) { this.vm = vm; this.el = vm.$el; this.fragment = null; this.init(); } Compile.prototype = { init: function () { this.fragment = this.nodeFragment(this.el); }, nodeFragment: function (el) { const fragment = document.createDocumentFragment(); let child = el.firstChild; //将子节点,全部移动文档片段里 while (child) { fragment.appendChild(child); child = el.firstChild; } return fragment; } }
然后我们就需要对整个节点和指令进行处理编译,根据不同的节点去调用不同的渲染函数,绑定更新函数,编译完成之后,再把 DOM 片段添加到页面中。
Compile.prototype = { compileNode: function (fragment) { let childNodes = fragment.childNodes; [...childNodes].forEach(node => { let reg = /\{\{(.*)\}\}/; let text = node.textContent; if (this.isElementNode(node)) { this.compile(node); //渲染指令模板 } else if (this.isTextNode(node) && reg.test(text)) { let prop = RegExp.$1; this.compileText(node, prop); //渲染{{}} 模板 } //递归编译子节点 if (node.childNodes && node.childNodes.length) { this.compileNode(node); } }); }, compile: function (node) { let nodeAttrs = node.attributes; [...nodeAttrs].forEach(attr => { let name = attr.name; if (this.isDirective(name)) { let value = attr.value; if (name === "v-model") { this.compileModel(node, value); } node.removeAttribute(name); } }); }, //省略。。。 }
因为代码比较长如果全部贴出来会影响阅读,我们主要是讲整个过程实现的思路,文章结束我会把源码发出来,有兴趣的可以去查看全部代码。
到这里我们的整个的模板编译也已经完成,不过这里我们并没有实现过多的指令,我们只是简单的实现了 v-model
指令,本意是通过这篇文章让大家熟悉与认识 Vue 的双向绑定原理,并不是去创造一个新的 MVVM 实例。所以并没有考虑很多细节与设计。
现在我们实现了 Observer、Watcher、Compile,接下来就是把三者给组织起来,成为一个完整的 MVVM。
这里我们创建一个 Mvue 的类(构造函数)用来承载 Observer、Watcher、Compile 三者。
function Mvue(options) { this.$options = options; this.$data = options.data; this.$el = document.querySelector(options.el); this.init(); } Mvue.prototype.init = function () { observer(this.$data); new Compile(this); }
然后我们就去测试一下结果,看看我们实现的 Mvue 是不是真的可以运行。
<p> </p><h1>{{name}}</h1> <script></script> <script></script> <script></script> <script></script> <script> const vm = new Mvue({ el: "#app", data: { name: "完全没问题,看起来是不是很酷!" } }); </script>
我们尝试去修改数据,也完全没问题,但是有个问题就是我们修改数据时时通过 vm.$data.name 去修改数据,而不是想 Vue 中直接用 vm.name 就可以去修改,那这个是怎么做到的呢?其实很简单,Vue 做了一步数据代理操作。
我们来改造下 Mvue 添加数据代理功能,我们也是利用 Object.defineProperty
方法进行一步中间的转换操作,间接的去访问。
function Mvue(options) { this.$options = options; this.$data = options.data; this.$el = document.querySelector(options.el); //数据代理 Object.keys(this.$data).forEach(key => { this.proxyData(key); }); this.init(); } Mvue.prototype.init = function () { observer(this.$data); new Compile(this); } Mvue.prototype.proxyData = function (key) { Object.defineProperty(this, key, { get: function () { return this.$data[key] }, set: function (value) { this.$data[key] = value; } }); }
到这里我们就可以像 Vue 一样去修改我们的属性了,非常完美。完全自己动手实现,你也来试试把,体验下自己动手写代码的乐趣。
本文主要是对 Vue 双向绑定原理的学习与实现。
主要是对整个思路的学习,并没有考虑到太多的实现与设计的细节,所以还存在很多问题,并不完美。
源码地址,整个过程的全部代码,希望对你有所帮助。
Das obige ist der detaillierte Inhalt vonErfahren Sie, wie Sie die bidirektionale Bindung von Vue im Detail implementieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!