Maison > interface Web > js tutoriel > Comment le framework MVVM résout la liaison bidirectionnelle

Comment le framework MVVM résout la liaison bidirectionnelle

亚连
Libérer: 2018-06-09 16:10:40
original
2168 Les gens l'ont consulté

Cet article présente principalement la liaison bidirectionnelle de l'analyse du framework MVVM. Maintenant, je le partage avec vous et le donne comme référence.

MVVM Framework

Une tendance évidente du développement front-end ces dernières années est la migration de l'architecture du modèle MVC traditionnel vers le modèle MVVM. Sous MVC traditionnel, la page entière sera actualisée après l'interaction des données entre le front-end et le back-end actuels, ce qui entraînera une mauvaise expérience utilisateur. Par conséquent, nous communiquons avec l'API REST de la passerelle via Ajax et actualisons une certaine section de la page de manière asynchrone pour optimiser et améliorer l'expérience.

Concepts de base du framework MVVM

Dans le framework MVVM, View (view) et Model (data) ne peuvent pas communiquer directement Oui , il y a ViewModel comme intermédiaire entre eux, qui agit en tant qu'observateur. Lorsque l'utilisateur exploite la vue, le ViewModel détecte le changement, puis informe le modèle du changement correspondant. À l'inverse, lorsque le modèle (données) change, le ViewModel peut également détecter le changement, provoquant la mise à jour de la vue en conséquence. Ce processus de va-et-vient est ce que nous appelons une liaison bidirectionnelle.

Scénarios d'application du framework MVVM

Les avantages du framework MVVM sont évidents : lorsque le front-end opère sur des données, les données peuvent être conservées via des requêtes Ajax , changez simplement La partie du contenu des données dans le dom qui doit être modifiée sans avoir à actualiser la page entière. Surtout sur mobile, rafraîchir la page coûte trop cher. Bien que certaines ressources soient mises en cache, les DOM, CSS et JS de la page seront ré-analysés par le navigateur, de sorte que les pages mobiles sont généralement transformées en applications SPA monopage. Sur cette base, de nombreux frameworks MVVM sont nés, comme React.js, Vue.js, Angular.js, etc.

Une implémentation simple du framework MVVM

simule le flux de liaison bidirectionnel de Vue et implémente un framework MVVM simple. Vous pouvez voir la ligne pointillée depuis le. image ci-dessus Dans le carré se trouve la couche intermédiaire ViewModel mentionnée plus haut, qui joue le rôle d'observateur. De plus, vous pouvez constater que la vue vers le modèle dans le flux de liaison bidirectionnelle est en fait implémentée via la fonction d'écoute d'événements de l'entrée. Si elle est remplacée par React (flux de liaison unidirectionnel), elle sera transmise à. l'outil de gestion d'état (tel que Redux) à cette étape. De plus, le modèle à afficher dans le flux de liaison bidirectionnel est en fait implémenté de la même manière par chaque framework MVVM. La méthode principale utilisée est Object.defineProperty(). Grâce à cette méthode, les données peuvent être détournées et lorsque les données sont modifiées. , il peut être capturé en conséquence pour un traitement ultérieur.

L'implémentation de Mvvm (fichier d'entrée)

appelle généralement le framework Mvvm comme ceci

const vm = new Mvvm({
      el: '#app',
      data: {
       title: 'mvvm title',
       name: 'mvvm name'
      },
     })
Copier après la connexion

Mais dans ce cas, si vous souhaitez obtenir l'attribut title, vous devez l'obtenir sous la forme de vm.data.title. Pour que vm.title obtienne l'attribut title, ajoutez une méthode proxy au prototype de. Mvvm. Le code est le suivant :

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
   }
  })
 }
}
Copier après la connexion

Après avoir implémenté la méthode proxy, nous passons à la mise en œuvre du processus principal

function Mvvm (options) {
 this.data = options.data
 // ...
 observe(this.data)
 new Compile(options.el, this)
}
Copier après la connexion

implémentation de l'observateur (observer)

responsabilités de l'observateur Il s'agit de surveiller les modifications du modèle (objet JS). La partie principale consiste à utiliser les méthodes get et set de Object.defineProperty() lorsque la valeur du modèle (objet JS). ) doit être obtenu, la méthode get sera automatiquement appelée ; lorsque la valeur du modèle (objet JS) est modifiée, la méthode set sera automatiquement appelée, réalisant ainsi le détournement de données, le code est le suivant :

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
   }
  }
 })
}
Copier après la connexion

Exécutez le code et vous pouvez voir que la valeur de sortie de la console change newValue:1 oldValue:0 Ceci complète la logique de l'observateur.

La relation entre Dep (tableau d'abonnés) et watcher (abonné)

Après avoir observé les changements, nous informons toujours des groupes spécifiques de personnes et les laissons prendre des décisions. par conséquent. Afin de le comprendre plus facilement, nous pouvons considérer l'abonnement comme l'abonnement à un compte officiel WeChat. Lorsque le contenu du compte officiel WeChat est mis à jour, il poussera (mettra à jour) le contenu aux personnes qui s'y abonnent.

Il y a des milliers de personnes qui s'abonnent au même compte officiel WeChat, donc la première chose qui me vient à l'esprit est d'utiliser new Array() pour stocker ces personnes (html nœuds) Barre. Nous avons donc le code suivant :

// 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()
   }
  }
 })
}
Copier après la connexion

À première vue, le code est relativement fluide, mais il peut rester bloqué dans Dep.target et sub.update, nous tournons donc naturellement notre attention vers 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
 }
}
Copier après la connexion

从代码中可以看到当构造 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) {...},
}
Copier après la connexion

这个简单的 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 的绑定
  })
 },
}
Copier après la connexion

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

项目地址

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

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

相关文章:

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

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

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

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal