Vue双向数据绑定的原理解析及代码实现

参考:百度安全验证

本文转载自【微信公众号:手机电脑双黑客,ID:heikestudio】,经微信公众号授权转载,如需转载与原文作者联系

不多说了,我们直接来看看Vue的双向绑定机制从最简单到最终实现版本。

一、访问器属性

大家可能都知道,

关于Vue的双向绑定,

核心是Object.defineProperty()方法,那接下来我们就简单介绍一下!

语法:

Object.defineProperty(obj,prop,descriptor)

其中:

obj

要在其上定义属性的对象。

prop

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

descriptor

将被定义或修改的属性描述符。

其实,简单点来说,就是通过此方法来定义一个值。

调用,使用到了get方法,

赋值,使用到了set方法。

我们来看个例子:

Vue双向数据绑定的原理解析及代码实现_第1张图片

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

二、实现双向绑定

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

Vue双向数据绑定的原理解析及代码实现_第2张图片

实现效果如下:

Vue双向数据绑定的原理解析及代码实现_第3张图片

此例实现的效果是:随文本框输入文字的变化,span 中会同步显示相同的文字内容;在js或控制台显式的修改 obj.hello 的值,视图会相应更新。这样就实现了 model => view 以及 view => model 的双向绑定。

通过添加事件监听keyup来触发调用set方法,而set在修改了访问器属性的同时,也修改了dom样式,改变了span标签内的文本节点。

三、实现Vue双向绑定

3.1 实现效果

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

Vue双向数据绑定的原理解析及代码实现_第4张图片

Vue双向数据绑定的原理解析及代码实现_第5张图片

3.2 任务拆分

为了实现效果我们需要拆分一下任务:

① 将vm实例中的data中的内容绑定到输入框以及文本节点当中

② 当输入框改变时,vm实例中的data的内容也跟着改变,实现 【view => modle】

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

3.2.1 实现任务—内容绑定原理

先来了解一下DocumentFragment

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

而使用碎片化文档,也就是说我把10个节点都先放入到一个容器当中,最后我再把容器直接插入到文档就可以了!浏览器只回流了1次。

注意:还有一个很重要的特性是,如果使用appendChid方法将原dom树中的节点添加到DocumentFragment中时,会删除原来的节点。

举个例子,我使用:

console.log(document.getElementById('app'));

可以看到,我的app中有两个子节点,一个元素节点,一个文本节点 ;但是,当我通过DocumentFragment 劫持数据一下后 :

Vue双向数据绑定的原理解析及代码实现_第6张图片

Vue双向数据绑定的原理解析及代码实现_第7张图片

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

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

3.2.2 如何来实现内容绑定

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

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

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

Vue双向数据绑定的原理解析及代码实现_第8张图片

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

Vue双向数据绑定的原理解析及代码实现_第9张图片

创建Vue的实例化函数

Vue双向数据绑定的原理解析及代码实现_第10张图片

效果图如下:

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

3.2.3 实现任务—【view => model】

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

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

Vue双向数据绑定的原理解析及代码实现_第11张图片

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

Vue双向数据绑定的原理解析及代码实现_第12张图片

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

Vue双向数据绑定的原理解析及代码实现_第13张图片

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

Vue双向数据绑定的原理解析及代码实现_第14张图片

最终,效果如下:

Vue双向数据绑定的原理解析及代码实现_第15张图片

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

3.2.4 实现任务—【model => view】

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

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

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

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

订阅/发布者模式

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

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

举个例子:

Vue双向数据绑定的原理解析及代码实现_第16张图片

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

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

第一个是监听数据:observe(data),

第二个是编译 HTML:nodeToFragement(id)。

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

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

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

接下来我们要实现的是:发出通知 dep.notify() => 触发订阅者的 update 方法 => 更新视图。

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

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

Vue双向数据绑定的原理解析及代码实现_第17张图片

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

Vue双向数据绑定的原理解析及代码实现_第18张图片

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

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

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

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

Vue双向数据绑定的原理解析及代码实现_第19张图片

Vue双向数据绑定的原理解析及代码实现_第20张图片

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

你可能感兴趣的:(vue.js,前端,javascript)