Cet article personnalise une Vue et implémente progressivement la liaison bidirectionnelle des données. Il vous aidera à comprendre étape par étape le principe de la liaison bidirectionnelle de Vue à travers des exemples.
vue nécessite au moins deux paramètres : modèle et données. [Recommandations associées : Tutoriel vidéo vue.js]
Créez un objet Compiler, restituez les données dans le modèle et montez-le sur le nœud spécifié.
class MyVue { // 1,接收两个参数:模板(根节点),和数据对象 constructor(options) { // 保存模板,和数据对象 if (this.isElement(options.el)) { this.$el = options.el; } else { this.$el = document.querySelector(options.el); } this.$data = options.data; // 2.根据模板和数据对象,渲染到根节点 if (this.$el) { // 监听data所有属性的get/set new Observer(this.$data); new Compiler(this) } } // 判断是否是一个dom元素 isElement(node) { return node.nodeType === 1; } }
Compilateur
1 La fonction node2fragment extrait les éléments du modèle dans la mémoire pour faciliter le rendu des données dans le modèle, puis leur montage sur. la page à la fois.
2. Une fois le modèle extrait en mémoire, utilisez la fonction buildTemplate pour parcourir les éléments du modèle
nœuds d'élément
Nœud Texte
3, crée la classe CompilerUtil, qui est utilisée pour traiter les instructions vue et {{}} , et termine le rendu des données
4. Ceci termine le premier rendu des données. Ensuite, vous devez mettre à jour automatiquement la vue lorsque les données changent.
class Compiler { constructor(vm) { this.vm = vm; // 1.将网页上的元素放到内存中 let fragment = this.node2fragment(this.vm.$el); // 2.利用指定的数据编译内存中的元素 this.buildTemplate(fragment); // 3.将编译好的内容重新渲染会网页上 this.vm.$el.appendChild(fragment); } node2fragment(app) { // 1.创建一个空的文档碎片对象 let fragment = document.createDocumentFragment(); // 2.编译循环取到每一个元素 let node = app.firstChild; while (node) { // 注意点: 只要将元素添加到了文档碎片对象中, 那么这个元素就会自动从网页上消失 fragment.appendChild(node); node = app.firstChild; } // 3.返回存储了所有元素的文档碎片对象 return fragment; } buildTemplate(fragment) { let nodeList = [...fragment.childNodes]; nodeList.forEach(node => { // 需要判断当前遍历到的节点是一个元素还是一个文本 if (this.vm.isElement(node)) { // 元素节点 this.buildElement(node); // 处理子元素 this.buildTemplate(node); } else { // 文本节点 this.buildText(node); } }) } buildElement(node) { let attrs = [...node.attributes]; attrs.forEach(attr => { // v-model="name" => {name:v-model value:name} let { name, value } = attr; // v-model / v-html / v-text / v-xxx if (name.startsWith('v-')) { // v-model -> [v, model] let [_, directive] = name.split('-'); CompilerUtil[directive](node, value, this.vm); } }) } buildText(node) { let content = node.textContent; let reg = /\{\{.+?\}\}/gi; if (reg.test(content)) { CompilerUtil['content'](node, content, this.vm); } } }
let CompilerUtil = { getValue(vm, value) { // 解析this.data.aaa.bbb.ccc这种属性 return value.split('.').reduce((data, currentKey) => { return data[currentKey.trim()]; }, vm.$data); }, getContent(vm, value) { // 解析{{}}中的变量 let reg = /\{\{(.+?)\}\}/gi; let val = value.replace(reg, (...args) => { return this.getValue(vm, args[1]); }); return val; }, // 解析v-model指令 model: function (node, value, vm) { // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值 new Watcher(vm, value, (newValue, oldValue) => { node.value = newValue; }); let val = this.getValue(vm, value); node.value = val; }, // 解析v-html指令 html: function (node, value, vm) { // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值 new Watcher(vm, value, (newValue, oldValue) => { node.innerHTML = newValue; }); let val = this.getValue(vm, value); node.innerHTML = val; }, // 解析v-text指令 text: function (node, value, vm) { // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值 new Watcher(vm, value, (newValue, oldValue) => { node.innerText = newValue; }); let val = this.getValue(vm, value); node.innerText = val; }, // 解析{{}}中的变量 content: function (node, value, vm) { let reg = /\{\{(.+?)\}\}/gi; let val = value.replace(reg, (...args) => { // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值 new Watcher(vm, args[1], (newValue, oldValue) => { node.textContent = this.getContent(vm, value); }); return this.getValue(vm, args[1]); }); node.textContent = val; } }
Observer
1 Utilisez la fonction définirRecative pour effectuer le traitement Object.defineProperty sur les données, afin que chaque donnée contenue dans les données puisse être surveillée par get/set
2. . Ensuite, nous examinerons comment mettre à jour le contenu de la vue après avoir écouté les modifications de la valeur des données ? À l’aide du modèle de conception Observer, créez les classes Dep et Water.
class Observer { constructor(data) { this.observer(data); } observer(obj) { if (obj && typeof obj === 'object') { // 遍历取出传入对象的所有属性, 给遍历到的属性都增加get/set方法 for (let key in obj) { this.defineRecative(obj, key, obj[key]) } } } // obj: 需要操作的对象 // attr: 需要新增get/set方法的属性 // value: 需要新增get/set方法属性的取值 defineRecative(obj, attr, value) { // 如果属性的取值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法 this.observer(value); // 第三步: 将当前属性的所有观察者对象都放到当前属性的发布订阅对象中管理起来 let dep = new Dep(); // 创建了属于当前属性的发布订阅对象 Object.defineProperty(obj, attr, { get() { // 在这里收集依赖 Dep.target && dep.addSub(Dep.target); return value; }, set: (newValue) => { if (value !== newValue) { // 如果给属性赋值的新值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法 this.observer(newValue); value = newValue; dep.notify(); console.log('监听到数据的变化'); } } }) } }
Utilisez le modèle de conception d'observateur pour créer les classes Dep et Wather
1. Le but de l'utilisation du modèle de conception d'observateur est de :
analyser le modèle et collecter les données utilisées dans le modèle. Collection de nœuds DOM, lorsque les données changent, la mise à jour de la collection de nœuds DOM réalise la mise à jour des données.
Dep : utilisé pour collecter la collection de nœuds dom dont dépend un certain attribut de données et fournir des méthodes de mise à jour
Watcher : l'objet package de chaque nœud dom
2 lors de sa création. À ce stade, je sens que l'idée est bonne et je suis déjà confiant de gagner. Alors comment utiliser Dep et Watcher ?
Ajoutez un dep pour chaque attribut pour collecter le dom dépendant
Parce que les données seront lues lorsque la page sera rendue pour la première fois, et le getter des données sera déclenché à ce moment, donc récupérez le dom ici
Comment le collecter spécifiquement ? Lorsque la classe CompilerUtil analyse v-model, {{}} et d'autres commandes, le getter sera déclenché. Nous créons de l'eau avant le déclenchement, ajoutons un attribut statique à Watcher, pointez sur le dom, puis dans la fonction getter, obtenez la variable statique et ajoutez-la à la dépendance pour compléter une collection. Étant donné que la variable statique se voit attribuer une valeur à chaque fois que le getter est déclenché, il n'y a aucun cas de collecte de mauvaises dépendances.
class Dep { constructor() { // 这个数组就是专门用于管理某个属性所有的观察者对象的 this.subs = []; } // 订阅观察的方法 addSub(watcher) { this.subs.push(watcher); } // 发布订阅的方法 notify() { this.subs.forEach(watcher => watcher.update()); } }
class Watcher { constructor(vm, attr, cb) { this.vm = vm; this.attr = attr; this.cb = cb; // 在创建观察者对象的时候就去获取当前的旧值 this.oldValue = this.getOldValue(); } getOldValue() { Dep.target = this; let oldValue = CompilerUtil.getValue(this.vm, this.attr); Dep.target = null; return oldValue; } // 定义一个更新的方法, 用于判断新值和旧值是否相同 update() { let newValue = CompilerUtil.getValue(this.vm, this.attr); if (this.oldValue !== newValue) { this.cb(newValue, this.oldValue); } } }
3. À ce stade, la vue est automatiquement mise à jour lorsque les données sont liées. Au départ, je voulais implémenter le code étape par étape, mais j'ai trouvé cela difficile à gérer, j'ai donc publié le cours complet.
consiste en fait à surveiller les événements de saisie et de modification de la zone de saisie. Modifiez la méthode de modèle de CompilerUtil. Le code spécifique est le suivant
model: function (node, value, vm) { new Watcher(vm, value, (newValue, oldValue)=>{ node.value = newValue; }); let val = this.getValue(vm, value); node.value = val; // 看这里 node.addEventListener('input', (e)=>{ let newValue = e.target.value; this.setValue(vm, value, newValue); }) },
vue principe de liaison bidirectionnelle
vue reçoit un modèle et des paramètres de données. 1. Tout d'abord, parcourez récursivement les données dans data, exécutez Object.defineProperty sur chaque propriété et définissez les fonctions get et set. Et ajoutez un tableau dep pour chaque propriété. Lorsque get est exécuté, un observateur sera créé pour le nœud DOM appelé et stocké dans le tableau. Lorsque set est exécuté, la valeur est réaffectée et la méthode notify du tableau dep est appelée pour avertir tous les observateurs qui utilisent cet attribut et mettre à jour le contenu dom correspondant. 2. Chargez le modèle en mémoire, récurez les éléments du modèle et détectez que l'élément a une commande commençant par v- ou une instruction à double accolade, et la valeur correspondante sera extraite des données pour modifier le contenu du modèle à. cette fois, le contenu du modèle sera modifié. L'élément dom est ajouté au tableau dep de l'attribut. Cela implémente une vue basée sur les données. Lors du traitement de l'instruction v-model, ajoutez un événement d'entrée (ou un changement) au dom et modifiez la valeur de l'attribut correspondant lors de l'entrée, réalisant ainsi des données pilotées par page. 3. Après avoir lié le modèle aux données, ajoutez le modèle à la véritable arborescence DOM.
Comment mettre le watcher dans le tableau dep ?
Lors de l'analyse du modèle, la valeur de l'attribut de données correspondante sera obtenue selon la commande v. À ce moment, la méthode get de l'attribut sera appelée. Nous créons d'abord une instance Watcher et obtenons la valeur de l'attribut à l'intérieur. et stockons-la comme l'ancienne valeur dans l'observateur, avant d'obtenir la valeur, nous ajoutons l'attribut Watcher.target = this sur l'objet prototype Watcher ; puis obtenons la valeur, ce sera Watcher.target = null de cette manière ; , lorsque get est appelé, il peut être obtenu en fonction de l'objet d'instance Watcher.target watcher.
Le principe des méthodes
Lors de la création d'une instance de vue, elle reçoit le paramètre méthodes
Lors de l'analyse du modèle, elle rencontre l'instruction v-on. Un écouteur pour l'événement correspondant sera ajouté à l'élément dom, et la méthode d'appel sera utilisée pour lier vue à cette méthode : vm.$methods[value].call(vm, e);
Principe de calcul
Lors de la création d'une instance de vue, recevez le paramètre calculé
Initialize vue Pendant l'instance, effectuez le traitement Object.defineProperty pour la clé calculée et ajoutez l'attribut get.
(Partage de vidéos d'apprentissage : front-end web)
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!