在我们一个个了解之前,首选我们需要了解一下响应式数据原理,也就是我们常说的:订阅,发布模式。
vue响应式原理
这个是官网的结构图
再大概看看下图,简单画出一个响应式结构图,先从 Observer 开始谈起
observe
先看一下响应式这块的源码结构:
源码内容如下:
当我们调用new Observer(value)的时候,会去执行this.walk(value)这个方法,其功能主要是遍历value的属性,通过Object.defineProperty
方法来定义响应式数据。那么是如何实现响应通知的呢?来看一下他的具体代码:
这里其实就是我们依赖收集的订阅器,主要做了两件事
- dep.depend()
- dep.notify()
接下来详细看一下Dep class的实现。
Dep
通过响应式原理图,我们可以知道,Dom上通过指令或者大括号绑定的数据,会为数据添加观察者watcher,当实例化Watcher的时候,会触发属性的getter方法,此时会调用dep.depend()。可以看一下Dep的源码:
这里可能会有点疑问:什么是Dep.target,这个其实是在watcher初始化的时候给其赋值的,如下;一直传递到依赖收集的位置
Watcher
其中pushTarget 方法就是为Dep.target绑定此watcher实例,所以Dep.target.addDep(this)也就是执行此实例中的addDep方法.
这样我们就给dep实例添加了一个watcher实例,接着当我们更新data的时候,会触发他的set方法,执行dep.notify()方法。
这里遍历dep中收集到的watcher实例,集中进行update(),进行数据更新操作。这就是响应式原理。
另外当数据的getter触发后,会收集依赖,但是不是所有的触发方式都会收集依赖,只有通过watcher触发的getter触发的getter才会收集依赖:if (Dep.target) { dep.depend() }
,而所谓的被收集的依赖就是当前的watcher,DOM中的数据必须通过watcher来绑定,只通过watcher来读取。
双向绑定的简单实现
当然说了这么多最好还是上手实践,现在实现一个简单的vue:lite-vue,虽然是简单的实践,各种功能还是必须要有的。
- 实现$options参数处理
- 实现observer数据劫持
- 实现dep订阅器
- 实现watcher观察者
- 实现基础的compile编译
$options参数处理
首先,明确的是我们需要实现一个对象,该对象接受一个object类型的参数来提供初始化,按照Vue的思想,首先需要构建实例上的$options参数,这里我们简化一下:
observer数据劫持
数据劫持,前面已经说过了,我们需要为我们的定义的data参数进行observer:
observer的主要功能是对传入的数据进行过滤,判断是否需要进行数据劫持:
那么接下来就是去实现Observer类了,这里,为了更加简洁,我们暂时只考虑传入的value是一个普通的对象:
到这里,我们的数据劫持,基本上完成了,可以来调试一下:
到这里,我们访问属性是通过this._data.xxx 这样不是很优雅,所以,我们需要设置一层代理,也就是重新进行一次数据访问拦截。当我们访问this.xxx就可以了:
实现Dep订阅器 和 Watcher 订阅者
订阅-发布模式,就像买房的中介一样。我们(watcher)去买房,不可能天天去房地产开发商那边去问有没有房源,我们更多的是找一个中介(dep),然后把我们的需求和联系方式告诉中介(dep.depend()),中介一旦有满足需求的房源,便会打电话来通知我们dep.notify() 根据上面的描述,我们大概清楚了,我们需要一个订阅器Dep,同时,Dep需要有收集需求和联系方式的功能,也需要有打电话通知的功能:
紧接着,我们也需要一个Watcher,其中包含接受通知的功能,以及建立与中介dep的关联:
说到这里,我们知道了,还有2步没有去做:
- 收集联系方式
- 通知 那我们什么时候去收集联系方式呢,答案很简单:那就是我们主动询问中介的时候,中介会向我们要我们的联系方式:
|
|
那什么时候通知顾客呢?很简单:当有房产更新的时候:
到这里,我们以一个例子,简单的描述了这之间的过程。现在我们已经实现了一个简单的发布-订阅方式了。
实现基础的compile编译
options中的el 参数,为我们指定了我们需要编译哪些内容,而我们要做的仅仅是解析出通过v-model、v-text
、双花括号等标识和指令。然后获取绑定数据的值,替换掉标识的内容,并进行数据的变化监听watcher。 当再有值发生变化时,可以及时通知其修改对应dom元素。说到这里,我们开干:
到这里,我们便实现了一个简单的双向数据绑定:数据 ————> Dom
- 通过compile解析指令和数据,为其添加watcher
- 当watcher触发对应的get方法时,为其进行依赖收集,把对应的watcher进行收集
- 当数据发生变化的时候,触发set方法,使其通知watcher进行视图更新。
Dom ————> 数据
- 通过compile解析指令和数据
- 监听Dom input等更新动作,当触发dom更新时,在对应回调函数中更新实例vm中的数据值
后续
顺便,我们实现以下钩子函数功能: