Le contenu de cet article concerne la mise en œuvre détaillée de la liaison bidirectionnelle. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.
Le monde front-end d'aujourd'hui est dominé par les trois piliers d'Angular, React et Vue. Si vous ne choisissez pas un camp, vous ne pouvez fondamentalement pas prendre pied dans le front-end. choisir deux ou trois camps, c'est la tendance générale.
Nous devons donc toujours rester curieux et accepter les changements. Ce n'est que grâce à des changements constants que l'on peut être invincible. Être conservateur ne peut qu'attendre la mort.
J'ai appris Vue récemment et je n'ai qu'une compréhension limitée de sa liaison bidirectionnelle. Je prévois de l'étudier en profondeur ces derniers jours. En consultant les informations, j'ai acquis une certaine compréhension de ses principes. Je le connais, j'ai donc moi-même écrit un exemple de liaison bidirectionnelle. Voyons comment l'implémenter étape par étape.
Après avoir lu cet article, je pense que vous aurez une compréhension claire du principe de liaison bidirectionnelle de Vue. Cela peut également nous aider à mieux comprendre Vue.
Regardez d'abord les rendus
//代码: <div> <input> <h1>{{name}}</h1> </div> <script></script> <script></script> <script></script> <script></script> <script> const vm = new Mvue({ el: "#app", data: { name: "我是摩登" } }); </script>
Faisons-le avant de commencer. Parlons de la liaison de données. Ma compréhension de la liaison de données est d'afficher les données M (modèle) dans la vue V (vue). Nos modèles architecturaux courants incluent les modèles MVC, MVP et MVVM. Actuellement, les frameworks frontaux utilisent essentiellement le modèle MVVM pour implémenter la liaison bidirectionnelle, et Vue ne fait pas exception. Cependant, chaque framework implémente la liaison bidirectionnelle de manière légèrement différente. Actuellement, il existe environ trois méthodes d'implémentation.
Modèle de publication et d'abonnement
Mécanisme de vérification sale d'Angular
Piratage de données
Vue utilise une combinaison de piratage de données, de publication et d'abonnement pour obtenir une liaison bidirectionnelle. Le piratage de données est principalement réalisé via Object.defineProperty
.
Dans cet article, nous ne discuterons pas de l'utilisation de Object.defineProperty en détail. Nous examinerons principalement ses propriétés de stockage get et set. Jetons un coup d'œil à ce qui se passe une fois que les propriétés de l'objet sont définies.
var people = { name: "Modeng", age: 18 } people.age; //18 people.age = 20;
Le code ci-dessus sert uniquement à obtenir/définir les propriétés de l'objet, et aucun changement étrange ne peut être vu.
var modeng = {} var age; Object.defineProperty(modeng, 'age', { get: function () { console.log("获取年龄"); return age; }, set: function (newVal) { console.log("设置年龄"); age = newVal; } }); modeng.age = 18; console.log(modeng.age);
Vous constaterez qu'après les opérations ci-dessus, lorsque nous accédons à l'attribut age, la fonction get sera automatiquement exécutée. Lors de la définition de l'attribut age, la fonction set sera automatiquement exécutée. moyen très pratique pour notre reliure bidirectionnelle. Grande commodité.
Nous savons que le modèle MVVM réside dans la synchronisation des données et de la vue, ce qui signifie que la vue sera automatiquement mise à jour lorsque les données changent, et les données seront mises à jour lorsque le afficher les changements.
Donc, ce que nous devons faire, c'est comment détecter les changements dans les données, puis nous avertir de mettre à jour la vue, et comment détecter les changements dans la vue, puis mettre à jour les données. La détection des vues est relativement simple, ce n'est rien de plus que l'utilisation de la surveillance des événements.
Alors, comment pouvons-nous savoir que les attributs des données ont changé ? Cela utilise le Object.defineProperty que nous avons mentionné ci-dessus. Lorsque nos propriétés changent, cela déclenchera automatiquement la fonction set pour nous avertir de mettre à jour la vue.
Grâce à la description et à l'analyse ci-dessus, nous savons que Vue implémente une liaison bidirectionnelle via le détournement de données combiné avec le modèle de publication-abonnement de. Nous savons également que le détournement de données se fait via Object.defineProperty Méthode, lorsque nous savons cela, nous avons besoin d'un observateur d'écoute pour écouter les changements de propriétés. Après avoir su que les propriétés ont changé, nous avons besoin d'un Watcher Abonné pour mettre à jour la vue, nous avons également besoin d'un analyseur de directives de compilation pour analyser les directives de nos éléments de nœud et initialiser la vue. Nous avons donc besoin des éléments suivants :
Observateur : utilisé pour surveiller les modifications des attributs afin de notifier les abonnés
Abonnés Watcher : recevoir les modifications des attributs, puis mettre à jour la vue
Compiler l'analyseur : analyser les instructions, initialiser les modèles, lier les abonnés
En suivant cette idée, nous la mettrons en œuvre étape par étape.
Le rôle de l'auditeur est de surveiller chaque attribut des données. Nous avons également mentionné l'utilisation de la méthode Object.defineProperty
ci-dessus. Lorsque nous écoutons les changements d'attributs, nous avons également mentionné l'utilisation de la méthode
function defineReactive(data, key, value) { //递归调用,监听所有属性 observer(value); var dep = new Dep(); Object.defineProperty(data, key, { get: function () { if (Dep.target) { dep.addSub(Dep.target); } return value; }, set: function (newVal) { if (value !== newVal) { value = newVal; dep.notify(); //通知订阅器 } } }); } function observer(data) { if (!data || typeof data !== "object") { return; } Object.keys(data).forEach(key => { defineReactive(data, key, data[key]); }); } function Dep() { this.subs = []; } Dep.prototype.addSub = function (sub) { this.subs.push(sub); } Dep.prototype.notify = function () { console.log('属性变化通知 Watcher 执行更新视图函数'); this.subs.forEach(sub => { sub.update(); }) } Dep.target = null;
var modeng = { age: 18 } observer(modeng); modeng.age = 20;
我们可以看到浏览器控制台打印出 “属性变化通知 Watcher 执行更新视图函数” 说明我们实现的监听器没毛病,既然监听器有了,我们就可以通知属性变化了,那肯定是需要 Watcher 的时候了。
Watcher 主要是接受属性变化的通知,然后去执行更新函数去更新视图,所以我们做的主要是有两步:
把 Watcher 添加到 Dep 容器中,这里我们用到了 监听器的 get 函数
接收到通知,执行更新函数。
function Watcher(vm, prop, callback) { this.vm = vm; this.prop = prop; this.callback = callback; this.value = this.get(); } Watcher.prototype = { update: function () { const value = this.vm.$data[this.prop]; const oldVal = this.value; if (value !== oldVal) { this.value = value; this.callback(value); } }, get: function () { Dep.target = this; //储存订阅器 const value = this.vm.$data[this.prop]; //因为属性被监听,这一步会执行监听器里的 get方法 Dep.target = null; return value; } }
这一步我们把 Watcher 也给弄了出来,到这一步我们已经实现了一个简单的双向绑定了,我们可以尝试把两者结合起来看下效果。
function Mvue(options, prop) { this.$options = options; this.$data = options.data; this.$prop = prop; this.$el = document.querySelector(options.el); this.init(); } Mvue.prototype.init = function () { observer(this.$data); this.$el.textContent = this.$data[this.$prop]; new Watcher(this, this.$prop, value => { this.$el.textContent = value; }); }
这里我们尝试利用一个实例来把数据与需要监听的属性传递进来,通过监听器监听数据,然后添加属性订阅,绑定更新函数。
<p>{{name}}</p> const vm = new Mvue({ el: "#app", data: { name: "我是摩登" } }, "name");
我们可以看到数据已经正常的显示在页面上,那么我们在通过控制台去修改数据,发生变化后视图也会跟着修改。
到这一步我们我们基本上已经实现了一个简单的双向绑定,但是不难发现我们这里的属性都是写死的,也没有指令模板的解析,所以下一步我们来实现一个模板解析器。
Compile 的主要作用一个是用来解析指令初始化模板,一个是用来添加添加订阅者,绑定更新函数。
因为在解析 DOM 节点的过程中我们会频繁的操作 DOM, 所以我们利用文档片段(DocumentFragment)来帮助我们去解析 DOM 优化性能。
function Compile(vm) { this.vm = vm; this.el = vm.$el; this.fragment = null; this.init(); } Compile.prototype = { init: function () { this.fragment = this.nodeFragment(this.el); }, nodeFragment: function (el) { const fragment = document.createDocumentFragment(); let child = el.firstChild; //将子节点,全部移动文档片段里 while (child) { fragment.appendChild(child); child = el.firstChild; } return fragment; } }
然后我们就需要对整个节点和指令进行处理编译,根据不同的节点去调用不同的渲染函数,绑定更新函数,编译完成之后,再把 DOM 片段添加到页面中。
Compile.prototype = { compileNode: function (fragment) { let childNodes = fragment.childNodes; [...childNodes].forEach(node => { let reg = /\{\{(.*)\}\}/; let text = node.textContent; if (this.isElementNode(node)) { this.compile(node); //渲染指令模板 } else if (this.isTextNode(node) && reg.test(text)) { let prop = RegExp.$1; this.compileText(node, prop); //渲染{{}} 模板 } //递归编译子节点 if (node.childNodes && node.childNodes.length) { this.compileNode(node); } }); }, compile: function (node) { let nodeAttrs = node.attributes; [...nodeAttrs].forEach(attr => { let name = attr.name; if (this.isDirective(name)) { let value = attr.value; if (name === "v-model") { this.compileModel(node, value); } node.removeAttribute(name); } }); }, //省略。。。 }
因为代码比较长如果全部贴出来会影响阅读,我们主要是讲整个过程实现的思路,文章结束我会把源码发出来,有兴趣的可以去查看全部代码。
到这里我们的整个的模板编译也已经完成,不过这里我们并没有实现过多的指令,我们只是简单的实现了 v-model
指令,本意是通过这篇文章让大家熟悉与认识 Vue 的双向绑定原理,并不是去创造一个新的 MVVM 实例。所以并没有考虑很多细节与设计。
现在我们实现了 Observer、Watcher、Compile,接下来就是把三者给组织起来,成为一个完整的 MVVM。
这里我们创建一个 Mvue 的类(构造函数)用来承载 Observer、Watcher、Compile 三者。
function Mvue(options) { this.$options = options; this.$data = options.data; this.$el = document.querySelector(options.el); this.init(); } Mvue.prototype.init = function () { observer(this.$data); new Compile(this); }
然后我们就去测试一下结果,看看我们实现的 Mvue 是不是真的可以运行。
<p> </p><h1>{{name}}</h1> <script></script> <script></script> <script></script> <script></script> <script> const vm = new Mvue({ el: "#app", data: { name: "完全没问题,看起来是不是很酷!" } }); </script>
我们尝试去修改数据,也完全没问题,但是有个问题就是我们修改数据时时通过 vm.$data.name 去修改数据,而不是想 Vue 中直接用 vm.name 就可以去修改,那这个是怎么做到的呢?其实很简单,Vue 做了一步数据代理操作。
我们来改造下 Mvue 添加数据代理功能,我们也是利用 Object.defineProperty
方法进行一步中间的转换操作,间接的去访问。
function Mvue(options) { this.$options = options; this.$data = options.data; this.$el = document.querySelector(options.el); //数据代理 Object.keys(this.$data).forEach(key => { this.proxyData(key); }); this.init(); } Mvue.prototype.init = function () { observer(this.$data); new Compile(this); } Mvue.prototype.proxyData = function (key) { Object.defineProperty(this, key, { get: function () { return this.$data[key] }, set: function (value) { this.$data[key] = value; } }); }
到这里我们就可以像 Vue 一样去修改我们的属性了,非常完美。完全自己动手实现,你也来试试把,体验下自己动手写代码的乐趣。
本文主要是对 Vue 双向绑定原理的学习与实现。
主要是对整个思路的学习,并没有考虑到太多的实现与设计的细节,所以还存在很多问题,并不完美。
源码地址,整个过程的全部代码,希望对你有所帮助。
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!