首页 > 编程知识 正文

vue绑定原理,vue双向绑定简单原理

时间:2023-05-05 18:20:18 阅读:133720 作者:381

题名今天,我看了一篇关于Vue双向绑定的博客,自己也跟着博客深入学习,试着自己牵手,结果http://www.Sina.com/——3358 www.Sina.com/3http://www.com

这篇文章可能会很长,请相信我。 你看了之后,一定会对双向绑定有很好的了解!

本文就不多说了,直接看看从vue的双向绑定最简单到最终实现版吧。

另一方面,您可能知道访问器的属性,但很多人都知道数据劫持的核心是Object.defineProperty )方法。 那么,接下来简单介绍一下吧。(订阅者/发布者)模式

object.defineproperty(obj,prop,descriptor )Vue的双向绑定机制

obj

定义属性的对象。

prop

要定义或更改的属性的名称。

描述者

要定义或更改的属性描述符。

实际上,简单来说,就是用这种方法定义值。

调用,使用了get方法

赋值,使用了set方法。

请看一个例子:

我们调用时,会自动打印两行文字。Vue的双向绑定

二、因为已经知道实现简单的双向绑定,所以每次有变更都可以调用set方法,在此基础上实现双向绑定!

效果如下。

在此示例中,根据文本框中输入的字符变化,span会同时显示同一字符的内容。 在js或控制台中显式更改obj.hello的值时,视图将相应更新。 这提供了model=view和view=model的双向绑定。

通过添加事件接收keyup,可以触发对set方法的调用。 set在更改访问器属性的同时更改了dom样式,并更改了span标记中的文本节点。

三、实现真正双向绑定的原理1、实现效果我们真正需要实现的双向绑定如下:

2、为了实现任务的分割效果,需要分割任务。

1 .将虚拟机实例的data内容绑定到输入框和文本节点

2 .当输入框发生变化时,vm实例中的data内容也会发生变化,从而实现【view=modle】

data的内容发生变化时,输入框的内容及文本节点的内容也发生变化,实现【modle=view】

3、在实现任务1——的内容绑定之前,DocumentFragment必须介绍说到内容绑定,就是语法:这个概念。 简而言之,可以认为是dom节点的容器。 小型大炮创建10个节点,每个节点插入文档时会引起浏览器回流

使用碎片文档意味着,可以将10个节点放在一个容器中,然后最后直接将容器插入到文档中。 浏览器只逆流了一次。其中:

举个例子:

我是console.log (document.getelementbyid (' app ' );

我的APP有两个子节点、一个元素节点和一个文本节点

但是,如果我在文档框架3358 www.Sina.com /上

注意:我的碎片文档侵占了子节点,我的id在app的div中已经没有内容了。

同时要以我怀特的判断条件为主。 判断是否有子节点。 我每次appendChild都劫持了节点中的第一个子节点,所以节点中会少一个。 直到没有为止,child也变为undefined,结束了循环。

要实现内容绑定,必须考虑两个问题:如何绑定到input和如何绑定到文本节点。

这样想,我们已经获得了div的所以子节点。 在DocumentFragment中,然后对每个节点进行处理,查看是否存在与虚拟机实例相关的内容(如果有),然后修改此节点的内容。 然后,将其重新添加到文档片段中。

首先,编写处理每个节点的函数,并在出现包含input绑定v-model属性或{{ xxx }}的文本节点时替换内容,然后替换为虚拟机实例的da

ta中的内容

然后,在向碎片化文档中添加节点时,每个节点都处理一下。

创建Vue的实例化函数

效果图如下:

我们成功将内容都绑定到了输入框与文本节点上!

4、实现任务2——【view => model】

对于此任务,我们从输入框考虑,输入框的问题,输入框如何改变data。我们通过事件监听器keyup,input等,来获取到最新的value,然后通过Object.defineProperty将获取的最新的value,赋值给实例vm的text,我们把vm实例中的data下的text通过Object.defineProperty设置为访问器属性,这样给vm.text赋值,就触发了set。set函数的作用一个是更新data中的text,另一个等到任务三再说。

首先实现一个响应式监听属性的函数。一旦有赋新值就发生变化

然后,实现一个观察者,对于一个实例 每一个属性值都进行观察。

改写编译函数,注意由于改成了访问器属性,访问的方法也产生变化,同时添加了事件监听器,把实例的text值随时更新

实例函数中,观察data中的所有属性值,注意增添了observe

最终,效果如下:

效果实现了,任务二也完成了,view => model 通过修改输入框 vm实例中的属性也跟着变化了!

4、实现任务3——【model => view】

通过修改vm实例的属性 该改变输入框的内容 与 文本节点的内容。
这里涉及到一个问题 需要我们注意,当我们修改输入框,改变了vm实例的属性,这是1对1的。
但是,我们可能在页面中多处用到 data中的属性,这是1对多的。也就是说,改变1个model的值可以改变多个view中的值。
这就需要我们引入一个新的知识点:

订阅/发布者模式

订阅发布模式(又称观察者模式)定义了一种一对多的关系,让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察者对象。

发布者发出通知 => 主题对象收到通知并推送给订阅者 => 订阅者执行相应操作

举个例子:

之前提到的set函数的第二个作用 就是来提醒订阅者 进行noticy操作,告诉他们:“我的text变了!” 文本节点变成了订阅者,接到消息后,立马进行update操作

回顾一下,每当 new 一个 Vue,主要做了两件事:第一个是监听数据:observe(data),第二个是编译 HTML:nodeToFragement(id)。
在监听数据的过程中,我们会为 data 中的每一个属性生成一个主题对象 dep。

在编译 HTML 的过程中,会为每个与数据绑定相关的节点生成一个订阅者 watcher,watcher 会将自己添加到相应属性的 dep 容器中。

我们已经实现:修改输入框内容 => 在事件回调函数中修改属性值 => 触发属性的 set 方法。

接下来我们要实现的是:发出通知 dep.notify() => 触发订阅者的 update 方法 => 更新视图。
这里的关键逻辑是:如何将 watcher 添加到关联属性的 dep 中。

注意: 我把直接赋值的操作改为了 添加一个 Watcher 订阅者

那么,Watcher又该做些什么呢?

首先,将自己赋给了一个全局变量 Dep.target;

其次,执行了 update 方法,进而执行了 get 方法,get 的方法读取了 vm 的访问器属性,从而触发了访问器属性的 get 方法,get 方法中将该 watcher 添加到了对应访问器属性的 dep 中;

再次,获取属性的值,然后更新视图。

最后,将 Dep.target 设为空。因为它是全局变量,也是 watcher 与 dep 关联的唯一桥梁,任何时刻都必须保证 Dep.target 只有一个值。


到此,就实现了一个双向绑定的机制,如果你能自己手敲一遍,肯定会有很大的帮助,下面是源码:

<!DOCTYPE html> <head> <meta charset="UTF-8"> <title>双向绑定</title> </head> <body> <!-- 实现vue --> <div id="app"> <input type="text" v-model="text"> {{ text }} </div> <script type="text/javascript"> function defineReactive(obj, key, val){ var dep = new Dep(); Object.defineProperty(obj, key, { get: function(){ if(Dep.target){ dep.addSub(Dep.target); } return val }, set: function(newVal){ if(newVal === val){ return } val = newVal; console.log('新值:' + val); // 一旦更新立马通知 dep.notify(); } }) } /*观察者函数*/ function observe(obj,vm){ for(let key of Object.keys(obj)){ defineReactive(vm, key, obj[key]); } } function nodeToFragment(node,vm){ var fragment = document.createDocumentFragment(); var child; while(child = node.firstChild){ compile(child, vm); fragment.appendChild(child); } return fragment } /*编译函数*/ function compile(node, vm){ var reg = /{{(.*)}}/; // 来匹配 {{ xxx }} 中的xxx // 如果是元素节点 if(node.nodeType === 1){ var attr = node.attributes; // 解析元素节点的所有属性 for(let i=0;i<attr.length;i++){ if(attr[i].nodeName == 'v-model'){ var name = attr[i].nodeValue; // 看看是与哪一个数据相关 node.addEventListener('input', function(e){ vm[name] = e.target.value; // 将实例的text 修改为最新值 }); node.value = vm[name]; // 将data的值赋给该node node.removeAttribute('v-model'); } }; } // 如果是文本节点 if(node.nodeType === 3){ if(reg.test(node.nodeValue)){ var name = RegExp.$1; // 获取到匹配的字符串 name = name.trim(); // node.nodeValue = vm[name]; // 将data的值赋给该node new Watcher(vm, node, name); // 不直接通过赋值的操作,而是通过绑定一个订阅者 } } } /*Watcher构造函数*/ function Watcher(vm, node, name){ Dep.target = this; // Dep.target 是一个全局变量 this.vm = vm; this.node= node; this.name = name; this.update(); Dep.target = null; } Watcher.prototype = { update(){ this.get(); this.node.nodeValue = this.value; // 注意,这是更改节点内容的关键 }, get(){ this.value = this.vm[this.name]; // 触发相应的get } } /*dep构造函数*/ function Dep(){ this.subs = []; } Dep.prototype = { addSub(sub){ this.subs.push(sub); }, notify(){ this.subs.forEach(function(sub){ sub.update(); }) } } /*Vue构造函数*/ function Vue(options){ this.data = options.data; var data = this.data; observe(data, this); var id = options.el; var dom = nodeToFragment(document.getElementById(id), this); // 处理完所有dom节点后,重新将内容添加回去 document.getElementById(id).appendChild(dom); } var vm = new Vue({ el: 'app', data: { text: 'hello world' } }); </script> </body></html>

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。