vue双向绑定原理解析

vue通过使用数据劫持+发布订阅实现数据的双向绑定。

首先,先要搞懂两个问题:

1、什么是数据劫持(Object.defineProperty)。

2、什么是发布/订阅模式。

1、什么是数据劫持:

举个例子,现在有个obj对象,在他身上有个name属性是'vue',像这样: var obj = { name: 'vue' },现在我们想通过监听obj.name的变化,当name属性发生变化时,我们可以实时得到一个反馈,那么如何来做呢, Object.defineProperty可以实现上述需求:

利用Object.defineProperty重写obj.name的setter,和getter函数

可以看到,通过Object.defineProperty() 监控到obj的name属性,当对name属性发生变化的时候,便会执行setter函数在控制台输出 'change', 并且data.name的值为‘3’ 。

2、什么是发布订阅:

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知

映射到vue中,就是,我们可以有很多个div绑定了data中的name属性(订阅),当数据发生变化,将会通知所有绑定name属性的视图进行更新(发布)。

自定义事件便是一个比较简单的例子,这里不做赘述了。


明白这两个核心思想之后,我们看下整个过程:


那么知道了这些之后,实现双向绑定,需要做什么?

  • 入口函数
  • 数据监听器_observer 函数,对数据对象的所有属性进行监听,如有变动拿到最新值并通知订阅者
  • 指令解析器_compile函数,对每个元素节点进行遍历,根据指令绑定数据。
  • Watcher函数,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图


有了基本思路后,我们接下来一步步来实现。

1、入口函数:

入口函数不多做解释,唯一需要理解的就是watcher的用处,定义watcher池,目的是为了将多个订阅者对象存入,如果这些订阅者订阅的某个属性发生变化,则通知所有的订阅者进行视图更新。比如我们有多个div绑定了同一个name,那么我们就需要将所有绑定name的dom元素存入watcher池,如果name发生变化,则通知这些订阅者(也就是绑定name的div)进行视图更新,这个wacther大概呢,长这样

可以看到,绑定name的dom元素,有三个。

2、实现_observer:

上边也说了,_observer函数需要对data里边所有数据对象进行监控,这个实现起来也简单,不过有些细节需要注意:

  • 在对data进行遍历的时候,要将data里边的每个属性先存入watcher池占个坑位,这样,我们就可以在编译模板时,把对应的绑定值直接存入。
  • 在set中,需要通知对应绑定属性的dom元素节点,更新视图数据。

上述代码主要作了三件事:

1、遍历vue实例上的data对象(注意:此处没有深度遍历,vue实际上会对data进行深度遍历) 重写data上每个属性的getter和setter。

2、将每个属性的key都存入watcher池,默认为一个数组。

3、当修改data中的某一属性时,从watcher池中取出监控这个属性的数组进行遍历(也就是this._data[key])对所有绑定这个属性的元素update ( 通知视图进行更新 )。

3、实现_compile:

  • 首先需要深度遍历dom树,将带有v-model属性和v-bind属性的dom元素拿到。
  • 如果发现input或textarea标签绑定有v-model属性,则获取到v-model绑定的值,将push进_observer函数中data对应的的数据watcher池,并为其添加input事件。
  • 如果发现绑定有v-bind属性的标签,直接push进_observer函数中data对应的的数据watcher池。


到了这一步,基本上已经写的差不多了,我们只需要一个桥梁,把_compile和_observer联系起来,就大功告成。

3、Watcher:

watcher是个独立的构造函数,在上边的实例代码中我们也能看到,每个数据的watcher池中push的都是Watcher的实例。Watcher函数需要四个参数:当前的vue实例对象,dom元素绑定data的key,当前dom元素,绑定到当前dom元素的哪个属性上。以及一个update方法,因为我们需要在new Watcher()的时候进行视图更新(因为首先页面初始化时,遍历到绑有v-bind或v-model的元素,总要进行push Watcher实例的操作,且此时也应该将data[key]赋值给当前元素,也就是说,页面初始化过程中,遇到有v-bind的元素需要将数据更新到dom元素中去)

现在只需要在入口函数中,执行_compile和_observer即可

到目前为止,我们已经可以使用了,就像下边这样

参考链接:

https://javascript.ruanyifeng.com/stdlib/attributes.html  Object.defineProperty

https://blog.csdn.net/q1056843325/article/details/53353850  发布/订阅模式

https://github.com/DMQ/mvvm  vue双向绑定实现原理

流程图来源:知乎,具体哪篇找不到了

总结:

代码主要介绍了vue对于双向绑定的实现思路,vue真正在实现双向绑定的细节还有多未涉及的,比如重写了数组的一些原型方法如:push等。

在模版编译阶段也只是作了简单的演示,主要目的还在于理解vue双向绑定的实现思路。













猜你喜欢

转载自blog.csdn.net/badmoonc/article/details/80914447