一、数据驱动视图
- 用户操作或者后端返回数据引起数据变化,从而导致页面变化,这就是数据驱动视图。
- 公式:
UI = render(state)
, vue就充当了render的角色,当发现state发生变化的时候,就会通过数据监测,从而反应在页面UI上。
二、变化侦测
- 所谓的变化侦测就是追踪数据的变化。
- angular采用脏检查机制: 对脏数据的检查就是脏检查,比较ui和后台数据是否一致。 只有当ui事件或者网络请求,定时器延迟事件,才会触发脏检查。angular每一个绑定到ui的数据就会有一个
$watch
对象。一次脏检查就是调用一次$apply()
或者$digest()
,将数据中最新的值呈现在界面上 - react是通过对比虚拟DOM来实现变化侦测。
三、vue中的数据监测和依赖收集
3.1 Object的变化侦测
- vue采用
object.defineProperty()
监听属性,并把这个属性的读和写分别使用get()
和set()
进行拦截 - 使用
observer类
来将正常的object
转换成可观测的对象;变成可观测对象以后,就可以监听数据变化,当数据变化后就要通知视图更新。所以我们要为谁用到了这个对象创建一个依赖管理器,谁依赖了这个数据,把这个数据所有的依赖都管理起来,当数据发生变化的时候,就去对应的依赖管理器中中,把每个依赖都通知一遍。这就是依赖收集。 - 谁用到了数据,就代表一定会去读取数据,所以可以在getter里收集依赖,同样,数据变化也会触发setter,所以总结一句话就是 在getter中收集依赖,在setter中派发更新
3.1 依赖管理器和依赖
- 调用
dep.depend()
收集依赖,在setter中利用dep.notify()
通知所有依赖更新; - 所谓的依赖,也就是使用数据者,我们为使用数据者创建一个
Watcher
类。谁用到了数据,谁就是依赖,我们就为谁创建一个Watcher
实例.也就是说当数据发生变化时,不直接去通知依赖更新,而是通知依赖对应的watcher,由watcher去更新真正的视图。Watcher先把自己设置到全局唯一的指定位置(window.target),然后读取数据。因为读取了数据,所以会触发这个数据的getter。接着,在getter中就会从全局唯一的那个位置读取当前正在读取数据的Watcher,并把这个watcher收集到Dep中去。收集好之后,当数据发生变化时,会向Dep中的每个Watcher发送通知。通过这样的方式,Watcher可以主动去订阅任意一个数据的变化。
3.2 Array的变化侦测(数据劫持)
- 通过以上方法可以实现对对象的数据侦测,通过
Observer
方法把数据变成响应式,当外界通过Watcher读取数据时,会触发getter
从而将Watcher
添加到依赖中。当改变了数据,会触发setter
,从而向Dep
类中的依赖发送通知。Watcher
接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能触发用户的某个回调函数等。 - 对于数组的响应式则采用了不一样的方式.因为因为对于Object数据我们使用的是JS提供的对象原型上的方法Object.defineProperty,但是对于数组检测的思路还是一样的。
- 因为arr的数据始终还是存放在object数据对象里,所以Array型数据还是在getter中收集依赖。主要是重写数组的方法,利用数组拦截器,拦截在数组是和Array.prototype之间。Array原型中可以改变数组自身方法的内容有7个,
push
,pop
,shift
,unshift
,splice
,sort
,reverse
,对这7个方法,当我们调用者7个方法的时候实际上执行的是被拦截的方法,再指向自己的方法。 - 如何通知依赖: 通过
__ob__
属性获取到Observer类,然后就可以访问到其依赖管理器,就可以通知依赖了。 - 对新增元素的劫持,对
splice, push, unshift
方法进行处理,拿到新增元素进行处理 - 缺点: 对于取下标的方式,无法侦测到。所以新增了
vue.set
和vue.delete