Maison >interface Web >Voir.js >Quel est le principe de la liaison bidirectionnelle dans vuejs
Le principe de la liaison bidirectionnelle dans vuejs : utiliser le détournement de données et le mode publication-abonnement, détourner les setters et getters de chaque propriété via "Object.defineProperty()", publier des messages aux abonnés lorsque les données changent et déclencher les écouter les rappels, puis mettre à jour la vue.
L'environnement d'exploitation de ce tutoriel : système Windows 7, vue version 2.9.6, ordinateur DELL G3.
Vue implémente la liaison de données bidirectionnelle principalement via : le piratage de données et le mode publication-abonnement, en utilisant la méthode Object.defineProperty()
pour effectuer le piratage de données, et puis notifier L'éditeur (objet sujet) informe tous les observateurs Une fois que les observateurs reçoivent la notification, ils mettront à jour la vue. Object.defineProperty()
方法进行的数据劫持,然后通知发布者(主题对象)去通知所有观察者,观察者收到通知后,就会对视图进行更新。
https://jsrun.net/RMIKp/embedded/all/light
MVVM 框架主要包含两个方面,数据变化更新视图,视图变化更新数据。
视图变化更新数据,如果是像 input 这种标签,可以使用 oninput 事件..
数据变化更新视图可以使用 Object.definProperty()
的 set 方法可以检测数据变化,当数据改变就会触发这个函数,然后更新视图。
我们知道了如何实现双向绑定了,首先要对数据进行劫持监听,所以我们需要设置一个 Observer 函数,用来监听所有属性的变化。
如果属性发生了变化,那就要告诉订阅者 watcher 看是否需要更新数据,如果订阅者有多个,则需要一个 Dep 来收集这些订阅者,然后在监听器 observer 和 watcher 之间进行统一管理。
还需要一个指令解析器 compile,对需要监听的节点和属性进行扫描和解析。
因此,流程大概是这样的:
实现一个监听器 Observer,用来劫持并监听所有属性,如果发生变动,则通知订阅者。
实现一个订阅者 Watcher,当接到属性变化的通知时,执行对应的函数,然后更新视图,使用 Dep 来收集这些 Watcher。
实现一个解析器 Compile,用于扫描和解析的节点的相关指令,并根据初始化模板以及初始化相应的订阅器。
Observer 是一个数据监听器,核心方法是利用 Object.defineProperty()
Object.definProperty()
pour. détecter les changements de données Quand Cette fonction sera déclenchée lorsque les données changeront et la vue sera mise à jour. Processus de mise en œuvreNous savons comment implémenter la liaison bidirectionnelle. Tout d'abord, nous devons détourner et surveiller les données, nous devons donc configurer une fonction Observer pour surveiller les changements dans tous les attributs. Si les attributs changent, vous devez indiquer à l'observateur de l'abonné pour voir si les données doivent être mises à jour. S'il y a plusieurs abonnés, vous avez besoin d'un Dep pour collecter ces abonnés, puis effectuer une gestion unifiée entre l'observateur et l'observateur. . Vous avez également besoin d'une compilation d'analyseur de commandes pour analyser et analyser les nœuds et les attributs qui doivent être surveillés. Donc, le processus est à peu près comme ceci :
Implémentez un auditeur Observer pour détourner et surveiller toutes les propriétés, et informer les abonnés s'il y a des changements.
Implémentez un observateur d'abonné. Lorsque vous recevez une notification de modifications d'attribut, exécutez la fonction correspondante, puis mettez à jour la vue et utilisez Dep pour collecter ces observateurs.
Implémentez un analyseur Compile, qui est utilisé pour analyser et analyser les instructions associées du nœud, et initialiser l'abonné correspondant selon le modèle d'initialisation. Afficher un observateurObserver est un écouteur de données. La méthode principale consiste à utiliserObject.defineProperty()
pour ajouter de manière récursive des méthodes setter et getter à toutes les propriétés à surveiller. . var library = { book1: { name: "", }, book2: "", }; observe(library); library.book1.name = "vue权威指南"; // 属性name已经被监听了,现在值为:“vue权威指南” library.book2 = "没有此书籍"; // 属性book2已经被监听了,现在值为:“没有此书籍” // 为数据添加检测 function defineReactive(data, key, val) { observe(val); // 递归遍历所有子属性 let dep = new Dep(); // 新建一个dep Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { if (Dep.target) { // 判断是否需要添加订阅者,仅第一次需要添加,之后就不用了,详细看Watcher函数 dep.addSub(Dep.target); // 添加一个订阅者 } return val; }, set: function(newVal) { if (val == newVal) return; // 如果值未发生改变就return val = newVal; console.log( "属性" + key + "已经被监听了,现在值为:“" + newVal.toString() + "”" ); dep.notify(); // 如果数据发生变化,就通知所有的订阅者。 }, }); } // 监听对象的所有属性 function observe(data) { if (!data || typeof data !== "object") { return; // 如果不是对象就return } Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); }); } // Dep 负责收集订阅者,当属性发生变化时,触发更新函数。 function Dep() { this.subs = {}; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, notify: function() { this.subs.forEach((sub) => sub.update()); }, };Dans l'analyse des idées, il doit y avoir un dépôt d'abonnés aux messages pouvant accueillir les abonnés, qui est utilisé pour collecter les abonnés et exécuter la fonction de mise à jour correspondante lorsque les attributs changent. Du point de vue du code, l'abonné Dep est ajouté au getter afin de déclencher le Watcher lors de son initialisation. Il est donc nécessaire de déterminer si un abonné est nécessaire.
function Watcher(vm, exp, cb) { this.cb = cb; this.vm = vm; this.exp = exp; this.value = this.get(); // 将自己添加到订阅器的操作 } Watcher.prototype = { update: function() { this.run(); }, run: function() { var value = this.vm.data[this.exp]; var oldVal = this.value; if (value !== oldVal) { this.value = value; this.cb.call(this.vm, value, oldVal); } }, get: function() { Dep.target = this; // 缓存自己,用于判断是否添加watcher。 var value = this.vm.data[this.exp]; // 强制执行监听器里的get函数 Dep.target = null; // 释放自己 return value; }, };🎜Jusqu'à présent, la conception simple du Watcher est terminée, puis en associant l'Observer et le Watcher, une simple liaison bidirectionnelle peut être réalisée. 🎜🎜Étant donné que l'analyseur Compile n'a pas encore été conçu, les données du modèle peuvent d'abord être codées en dur. 🎜🎜Convertissez le code en méthode d'écriture du constructeur ES6 et prévisualisez-le. 🎜🎜https://jsrun.net/8SIKp/embedded/all/light🎜🎜Ce code n'implémente pas le compilateur mais transmet directement les variables liées. Nous ne définissons qu'une seule donnée (nom) sur un nœud) à lier, et. puis exécutez un nouveau MyVue sur la page pour obtenir une liaison bidirectionnelle. 🎜🎜Et apportez des modifications après deux secondes. Vous pouvez voir que la page a également changé. 🎜
// MyVue proxyKeys(key) { var self = this; Object.defineProperty(this, key, { enumerable: false, configurable: true, get: function proxyGetter() { return self.data[key]; }, set: function proxySetter(newVal) { self.data[key] = newVal; } }); }🎜La fonction du code ci-dessus est de proxy la clé de this.data vers this, afin que je puisse facilement utiliser this.xx pour obtenir this.data.xx. 🎜🎜Implémentation de la compilation🎜🎜Bien que la liaison de données bidirectionnelle soit implémentée ci-dessus, l'ensemble du processus n'analyse pas le magasin de sections DOM, mais le remplace de manière fixe, l'étape suivante consiste donc à implémenter un analyseur pour effectuer le travail d'analyse et de liaison des données. 🎜🎜Étapes d'implémentation de la compilation de l'analyseur : 🎜🎜🎜🎜Analysez les instructions du modèle, remplacez les données du modèle et initialisez la vue. 🎜
将模板指定对应的节点绑定对应的更新函数,初始化相应的订阅器。
为了解析模板,首先需要解析 DOM 数据,然后对含有 DOM 元素上的对应指令进行处理,因此整个 DOM 操作较为频繁,可以新建一个 fragment 片段,将需要的解析的 DOM 存入 fragment 片段中在进行处理。
function nodeToFragment(el) { var fragment = document.createDocumentFragment(); var child = el.firstChild; while (child) { // 将Dom元素移入fragment中 fragment.appendChild(child); child = el.firstChild; } return fragment; }
接下来需要遍历各个节点,对含有相关指令和模板语法的节点进行特殊处理,先进行最简单模板语法处理,使用正则解析“{{变量}}”这种形式的语法。
function compileElement (el) { var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node) { var reg = /\{\{(.*)\}\}/; // 匹配{{xx}} var text = node.textContent; if (self.isTextNode(node) && reg.test(text)) { // 判断是否是符合这种形式{{}}的指令 self.compileText(node, reg.exec(text)[1]); } if (node.childNodes && node.childNodes.length) { self.compileElement(node); // 继续递归遍历子节点 } }); }, function compileText (node, exp) { var self = this; var initText = this.vm[exp]; updateText(node, initText); // 将初始化的数据初始化到视图中 new Watcher(this.vm, exp, function (value) { // 生成订阅器并绑定更新函数 self.updateText(node, value); }); }, function updateText (node, value) { node.textContent = typeof value == 'undefined' ? '' : value; }
获取到最外层的节点后,调用 compileElement 函数,对所有的子节点进行判断,如果节点是文本节点切匹配{{}}这种形式的指令,则进行编译处理,初始化对应的参数。
然后需要对当前参数生成一个对应的更新函数订阅器,在数据发生变化时更新对应的 DOM。
这样就完成了解析、初始化、编译三个过程了。
接下来改造一个 myVue 就可以使用模板变量进行双向数据绑定了。
https://jsrun.net/K4IKp/embedded/all/light
添加完 compile 之后,一个数据双向绑定就基本完成了,接下来就是在 Compile 中添加更多指令的解析编译,比如 v-model、v-on、v-bind 等。
添加一个 v-model 和 v-on 解析:
function compile(node) { var nodeAttrs = node.attributes; var self = this; Array.prototype.forEach.call(nodeAttrs, function(attr) { var attrName = attr.name; if (isDirective(attrName)) { var exp = attr.value; var dir = attrName.substring(2); if (isEventDirective(dir)) { // 事件指令 self.compileEvent(node, self.vm, exp, dir); } else { // v-model 指令 self.compileModel(node, self.vm, exp, dir); } node.removeAttribute(attrName); // 解析完毕,移除属性 } }); } // v-指令解析 function isDirective(attr) { return attr.indexOf("v-") == 0; } // on: 指令解析 function isEventDirective(dir) { return dir.indexOf("on:") === 0; }
上面的 compile 函数是用于遍历当前 dom 的所有节点属性,然后判断属性是否是指令属性,如果是在做对应的处理(事件就去监听事件、数据就去监听数据..)
在 MyVue 中添加 mounted 方法,在所有操作都做完时执行。
class MyVue { constructor(options) { var self = this; this.data = options.data; this.methods = options.methods; Object.keys(this.data).forEach(function(key) { self.proxyKeys(key); }); observe(this.data); new Compile(options.el, this); options.mounted.call(this); // 所有事情处理好后执行mounted函数 } proxyKeys(key) { // 将this.data属性代理到this上 var self = this; Object.defineProperty(this, key, { enumerable: false, configurable: true, get: function getter() { return self.data[key]; }, set: function setter(newVal) { self.data[key] = newVal; }, }); } }
然后就可以测试使用了。
https://jsrun.net/Y4IKp/embedded/all/light
总结一下流程,回头在哪看一遍这个图,是不是清楚很多了。
可以查看的代码地址:Vue2.x 的双向绑定原理及实现
相关推荐:《vue.js教程》
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!