Heim > Artikel > Web-Frontend > Wie das MVVM-Framework die bidirektionale Bindung auflöst
Dieser Artikel stellt hauptsächlich die bidirektionale Bindung der MVVM-Framework-Analyse vor. Jetzt teile ich ihn mit Ihnen und gebe ihn als Referenz.
MVVM-Framework
Ein offensichtlicher Front-End-Entwicklungstrend der letzten Jahre ist die Migration der Architektur vom traditionellen MVC-Modell zum MVVM-Modell. Bei herkömmlichem MVC wird die gesamte Seite nach der Dateninteraktion zwischen dem aktuellen Front-End und Back-End aktualisiert, was zu einer schlechten Benutzererfahrung führt. Daher kommunizieren wir über Ajax mit der Gateway-REST-API und aktualisieren einen bestimmten Abschnitt der Seite asynchron, um das Erlebnis zu optimieren und zu verbessern.
Grundkonzepte des MVVM-Frameworks
Im MVVM-Framework können Ansicht (Ansicht) und Modell (Daten) nicht direkt kommunizieren Zwischen ihnen gibt es ViewModel als Vermittler, das als Beobachter fungiert. Wenn der Benutzer die Ansicht bedient, erkennt das ViewModel die Änderung und benachrichtigt dann das Modell über die entsprechende Änderung. Wenn sich hingegen das Modell (die Daten) ändert, kann das ViewModel die Änderung ebenfalls erkennen, wodurch die Ansicht entsprechend aktualisiert wird. Dieser Hin- und Hervorgang wird als bidirektionale Bindung bezeichnet.
Anwendungsszenarien des MVVM-Frameworks
Die Vorteile des MVVM-Frameworks liegen auf der Hand: Wenn das Front-End Daten verarbeitet, können die Daten über Ajax-Anfragen beibehalten werden , ändern Sie einfach den Teil des Dateninhalts im Dom, der geändert werden muss, ohne dass die gesamte Seite aktualisiert werden muss. Insbesondere auf Mobilgeräten ist die Aktualisierung der Seite zu teuer. Obwohl einige Ressourcen zwischengespeichert werden, werden DOM, CSS und JS der Seite vom Browser erneut analysiert, sodass mobile Seiten normalerweise in SPA-Einzelseitenanwendungen umgewandelt werden. Daraus wurden viele MVVM-Frameworks auf dieser Basis geboren, wie z. B. React.js, Vue.js, Angular.js usw.
Eine einfache Implementierung des MVVM-Frameworks
Simulieren Sie den bidirektionalen Bindungsfluss von Vue und implementieren Sie ein einfaches MVVM-Framework Bild oben Im Quadrat befindet sich die zuvor erwähnte ViewModel-Zwischenschicht, die die Rolle eines Beobachters spielt. Darüber hinaus können Sie feststellen, dass die Ansicht zum Modell im bidirektionalen Bindungsfluss tatsächlich über die Ereignisüberwachungsfunktion der Eingabe implementiert wird. Wenn Sie zu React wechseln (unidirektionaler Bindungsfluss), wird sie an den übergeben Zustandsverwaltungstool (z. B. Redux) in diesem Schritt durchführen. Darüber hinaus wird das anzuzeigende Modell im bidirektionalen Bindungsfluss tatsächlich von jedem MVVM-Framework auf die gleiche Weise implementiert. Die verwendete Kernmethode ist Object.defineProperty(). Durch diese Methode können Daten gekapert werden, wenn sich die Daten ändern , kann es für die spätere Verarbeitung entsprechend geändert werden.
Die Implementierung von Mvvm (Eintragsdatei)
Im Allgemeinen wird das Mvvm-Framework so aufgerufen
const vm = new Mvvm({ el: '#app', data: { title: 'mvvm title', name: 'mvvm name' }, })
Aber so: Wenn Sie das Titelattribut erhalten möchten, müssen Sie es in der Form vm.data.title erhalten. Damit vm.title das Titelattribut erhält, fügen Sie dem Prototyp von Mvvm eine Proxy-Methode hinzu Der Code lautet wie folgt:
function Mvvm (options) { this.data = options.data const self = this Object.keys(this.data).forEach(key => self.proxyKeys(key) ) } Mvvm.prototype = { proxyKeys: function(key) { const self = this Object.defineProperty(this, key, { get: function () { // 这里的 get 和 set 实现了 vm.data.title 和 vm.title 的值同步 return self.data[key] }, set: function (newValue) { self.data[key] = newValue } }) } }
Nach der Implementierung der Proxy-Methode beginnen Sie mit der Implementierung des Hauptprozesses
function Mvvm (options) { this.data = options.data // ... observe(this.data) new Compile(options.el, this) }
Die Implementierung des Beobachters (Beobachter)
Die Verantwortung des Beobachters besteht darin, das Modell (JS-Objekt) zu überwachen. Der Kernteil ist die Verwendung der get- und set-Methoden von Object.defineProperty(), wenn der Wert des Modells (JS-Objekts) abgerufen werden soll Die Get-Methode wird automatisch aufgerufen. Wenn der Wert des Modells (JS-Objekts) geändert wird, wird die Set-Methode automatisch aufgerufen, wodurch die Datenentführung realisiert wird.
let data = { number: 0 } observe(data) data.number = 1 // 值发生变化 function observe(data) { if (!data || typeof(data) !== 'object') { return } const self = this Object.keys(data).forEach(key => self.defineReactive(data, key, data[key]) ) } function defineReactive(data, key, value) { observe(value) // 遍历嵌套对象 Object.defineProperty(data, key, { get: function() { return value }, set: function(newValue) { if (value !== newValue) { console.log('值发生变化', 'newValue:' + newValue + ' ' + 'oldValue:' + value) value = newValue } } }) }
Führen Sie den Code aus und Sie können sehen, dass sich der Konsolenausgabewert ändert: newValue:1 oldValue:0. Damit ist die Logik des Beobachters abgeschlossen.
Die Beziehung zwischen Dep (Abonnenten-Array) und Watcher (Abonnent)
Nachdem wir Änderungen beobachtet haben, benachrichtigen wir immer bestimmte Personengruppen und lassen sie Entscheidungen treffen, damit umzugehen entsprechend. Um es einfacher zu verstehen, können wir uns ein Abonnement als das Abonnieren eines offiziellen WeChat-Kontos vorstellen. Wenn der Inhalt des offiziellen WeChat-Kontos aktualisiert wird, wird der Inhalt an die Personen weitergeleitet (aktualisiert), die ihn abonnieren.
Es gibt Tausende von Menschen, die dasselbe offizielle WeChat-Konto abonnieren. Das erste, was mir in den Sinn kommt, ist die Verwendung von new Array() zum Speichern dieser Personen (html Knoten) Balken. Wir haben also den folgenden Code:
// observer.js function Dep() { this.subs = [] // 存放订阅者 } Dep.prototype = { addSub: function(sub) { // 添加订阅者 this.subs.push(sub) }, notify: function() { // 通知订阅者更新 this.subs.forEach(function(sub) { sub.update() }) } } function observe(data) {...} function defineReactive(data, key, value) { var dep = new Dep() observe(value) // 遍历嵌套对象 Object.defineProperty(data, key, { get: function() { if (Dep.target) { // 往订阅器添加订阅者 dep.addSub(Dep.target) } return value }, set: function(newValue) { if (value !== newValue) { console.log('值发生变化', 'newValue:' + newValue + ' ' + 'oldValue:' + value) value = newValue dep.notify() } } }) }
Auf den ersten Blick ist der Code relativ flüssig, kann aber in Dep.target und sub.update hängen bleiben, daher richten wir unsere Aufmerksamkeit natürlich auf den Watcher,
// watcher.js function Watcher(vm, exp, cb) { this.vm = vm this.exp = exp this.cb = cb this.value = this.get() } Watcher.prototype = { update: function() { this.run() }, run: function() { // ... if (value !== oldVal) { this.cb.call(this.vm, value) // 触发 compile 中的回调 } }, get: function() { Dep.target = this // 缓存自己 const value = this.vm.data[this.exp] // 强制执行监听器里的 get 函数 Dep.target = null // 释放自己 return value } }
从代码中可以看到当构造 Watcher 实例时,会调用 get() 方法,接着重点关注 const value = this.vm.data[this.exp] 这句,前面说了当要获取 Model(JS 对象) 的值时,会自动调用 Object.defineProperty 的 get 方法,也就是当执行完这句的时候,Dep.target 的值传进了 observer.js 中的 Object.defineProperty 的 get 方法中。同时也一目了然地在 Watcher.prototype 中发现了 update 方法,其作用即触发 compile 中绑定的回调来更新界面。至此解释了 Observer 中 Dep.target 和 sub.update 的由来。
来归纳下 Watcher 的作用,其充当了 observer 和 compile 的桥梁。
1 在自身实例化的过程中,往订阅器(dep) 中添加自己
2 当 model 发生变动,dep.notify() 通知时,其能调用自身的 update 函数,并触发 compile 绑定的回调函数实现视图更新
最后再来看下生成 Watcher 实例的 compile.js 文件。
compile(编译) 的实现
首先遍历解析的过程有多次操作 dom 节点,为提高性能和效率,会先将跟节点 el 转换成 fragment(文档碎片) 进行解析编译,解析完成,再将 fragment 添加回原来的真实 dom 节点中。代码如下:
function Compile(el, vm) { this.vm = vm this.el = document.querySelector(el) this.fragment = null this.init() } Compile.prototype = { init: function() { if (this.el) { this.fragment = this.nodeToFragment(this.el) // 将节点转为 fragment 文档碎片 this.compileElement(this.fragment) // 对 fragment 进行编译解析 this.el.appendChild(this.fragment) } }, nodeToFragment: function(el) { const fragment = document.createDocumentFragment() let child = el.firstChild // △ 第一个 firstChild 是 text while(child) { fragment.appendChild(child) child = el.firstChild } return fragment }, compileElement: function(el) {...}, }
这个简单的 mvvm 框架在对 fragment 编译解析的过程中对 {{}} 文本元素、v-on:click 事件指令、v-model 指令三种类型进行了相应的处理。
Compile.prototype = { init: function() { if (this.el) { this.fragment = this.nodeToFragment(this.el) // 将节点转为 fragment 文档碎片 this.compileElement(this.fragment) // 对 fragment 进行编译解析 this.el.appendChild(this.fragment) } }, nodeToFragment: function(el) {...}, compileElement: function(el) {...}, compileText: function (node, exp) { // 对文本类型进行处理,将 {{abc}} 替换掉 const self = this const initText = this.vm[exp] this.updateText(node, initText) // 初始化 new Watcher(this.vm, exp, function(value) { // 实例化订阅者 self.updateText(node, value) }) }, compileEvent: function (node, vm, exp, dir) { // 对事件指令进行处理 const eventType = dir.split(':')[1] const cb = vm.methods && vm.methods[exp] if (eventType && cb) { node.addEventListener(eventType, cb.bind(vm), false) } }, compileModel: function (node, vm, exp) { // 对 v-model 进行处理 let val = vm[exp] const self = this this.modelUpdater(node, val) node.addEventListener('input', function (e) { const newValue = e.target.value self.vm[exp] = newValue // 实现 view 到 model 的绑定 }) }, }
在上述代码的 compileTest 函数中看到了期盼已久的 Watcher 实例化,对 Watcher 作用模糊的朋友可以往上回顾下 Watcher 的作用。另外在 compileModel 函数中看到了本文最开始提到的双向绑定流中的 View 到 Model 是借助 input 监听事件变化实现的。
项目地址
本文记录了些阅读 mvvm 框架源码关于双向绑定的心得,并动手实践了一个简版的 mvvm 框架,不足之处在所难免,欢迎指正。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
Das obige ist der detaillierte Inhalt vonWie das MVVM-Framework die bidirektionale Bindung auflöst. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!