出来冒泡猜三个数字?

大家都知道,关于Vue的双向绑定,核心是Object.defineProperty()方法,那接下来我们就简单介绍一下!

  • obj——要在其上定义属性的对象。
  • prop——要定义或修改的属性的名称。
  • descriptor——将被定义或修改的属性描述符。

其实,简单点来说,就是通过此方法来定义一个值。调用,使用到了get方法,赋值,使用到了set方法。

当我们调用时候,就会自动打印出两行文字。注意:get 和 set 方法内部的 this 都指向 obj,这意味着 get 和 set 方法可以操作对象内部的值。另外,访问器属性(也就是Object.defineProperty()中的属性)的会”覆盖”同名的普通属性,因为访问器属性会被优先访问,与其同名的普通属性则会被忽略。

既然我们已经知道了,每当有改变的时候都会调用到set方法,我们可以根据此来实现一个双向绑定!

此例实现的效果是:随文本框输入文字的变化,h1中会同步显示相同的文字内容;在js或控制台显式的修改 obj.name 的值,视图会同步更新。这样就实现了 model => view 以及 view => model 的双向绑定。通过添加事件监听keyup来触发调用set方法,而set在修改了访问器属性的同时,既改变了文本框的内容,也改变了h1标签内的文本。

我们真正想实现的双向绑定是这样的:

  1. 将vm实例中的data中的内容绑定到输入框以及文本节点当中
  2. 当输入框改变时,vm实例中的data的内容也跟着改变,实现 【view => model】
  3. 当data中的内容发生变化的时候,输入框的内容以及文本节点的内容也发生变化,实现 【model=> view】

说到内容绑定,我们不得不来介绍DocuemntFragment(碎片化文档)这个概念,简单的来讲,你可以把它认为是一个dom节点的容器,当你创造了10个节点,当每个节点都插入到文档当中都会引发一次浏览器的回流,也就是说浏览器要回流10次,十分消耗资源。而使用碎片化文档,也就是说我把10个节点都先放入到一个容器当中,最后我再把容器直接插入到文档就可以了!浏览器只回流了1次。

注意:还有一个很重要的特性是,如果使用appendChid方法将原dom树中的节点添加到DocumentFragment中时,会删除原来的节点。
可以看到,我的app中有两个子节点,一个元素节点,一个文本节点 ;但是,当我通过DocumentFragment 劫持数据一下后:


注意:我的碎片化文档是将子节点都劫持了过来,而我的id为app的div内已经没有内容了。

同时要主要我while的判断条件。判断是否有子节点,因为我每次appendChild都把node中的第一个子节点劫持走了,node中就会少一个,直到没有的时候,child也就变成了undefined,也就终止了循环。

三、实现内容绑定(任务一)

我们要考虑两个问题,一个是如何绑定到input上,另一个是如何绑定到文本节点中。

这样思路就来了,我们已经获取到了div的所有子节点了,就在DocumentFragment里面,然后对每一个节点进行处理,看是不是有跟vm实例中有关联的内容,如果有,修改这个节点的内容。然后重新添加入DocumentFragment中。

首先,我们写一个处理每一个节点的编译方法,如果有input绑定v-model属性或者有{{ xxx }}的文本节点出现,就进行内容替换,替换为vm实例中的data中的内容 :


 
 
 
 
 

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


 

创建Vue的构造函数:


 
 

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

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

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


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


改写编译方法,注意由于改成了访问器属性,访问的方法也产生变化(vm.data[name]=>vm[name]),同时添加了事件监听器,把实例的text值随时更新(实时更新的是vm的访问器属性text,vm.data[text]并不会更新):


 
 
 
 
 
 

构造函数中,观察data中的所有属性值,注意增加了observe:


 
 
 
 

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

通过修改vm实例的属性,该改变输入框的内容与文本节点的内容。

这里涉及到一个问题 需要我们注意,当我们修改输入框,改变了vm实例的属性,这是1对1的。

但是,我们可能在页面中多处用到 data中的属性,这是1对多的。也就是说,改变1个model的值可以改变多个view中的值。

这就需要我们引入一个新的知识点:

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

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


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

回顾一下,每当 new 一个 Vue,主要做了两件事:

在监听数据的过程中,我们会为 data 中的每一个属性生成一个主题对象 dep。

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

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

这里的关键逻辑是:如何将 watcher 添加到关联属性的 dep 中。

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


 
 
 
 
 
 
 
 
 

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


首先在页面首次渲染的时候,将自己赋给了一个全局变量 Dep.target;其次,执行了 update 方法,进而执行了 get 方法,get 的方法读取了 vm 的访问器属性,从而触发了访问器属性的 get 方法,get 方法中将该 watcher 添加到了对应访问器属性的 dep 中;

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

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


 

 

Vue双向绑定原理完整代码:

至此,就实现了一个Vue双向绑定的机制!

我要回帖

更多关于 迷迷糊糊求解三个数字 的文章

 

随机推荐