Heim > Web-Frontend > js-Tutorial > Wie das MVVM-Framework die bidirektionale Bindung auflöst

Wie das MVVM-Framework die bidirektionale Bindung auflöst

亚连
Freigeben: 2018-06-09 16:10:40
Original
2168 Leute haben es durchsucht

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'
      },
     })
Nach dem Login kopieren

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 dem Login kopieren

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)
}
Nach dem Login kopieren

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
   }
  }
 })
}
Nach dem Login kopieren

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()
   }
  }
 })
}
Nach dem Login kopieren

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
 }
}
Nach dem Login kopieren

从代码中可以看到当构造 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) {...},
}
Nach dem Login kopieren

这个简单的 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 的绑定
  })
 },
}
Nach dem Login kopieren

在上述代码的 compileTest 函数中看到了期盼已久的 Watcher 实例化,对 Watcher 作用模糊的朋友可以往上回顾下 Watcher 的作用。另外在 compileModel 函数中看到了本文最开始提到的双向绑定流中的 View 到 Model 是借助 input 监听事件变化实现的。

项目地址

本文记录了些阅读 mvvm 框架源码关于双向绑定的心得,并动手实践了一个简版的 mvvm 框架,不足之处在所难免,欢迎指正。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

通过微信小程序如何实现验证码获取倒计时效果

ES6 迭代器和 for.of循环(详细教程)

在vue中使用better-scroll滚动插件

在VUE + UEditor中如何实现单图片跨域上传功能

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!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage