响应式原理
init_state
export function initState(vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */ )
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}state 里面会做 options 接收函数判断:
1. props initProps
2. methods initMethods
3. data
if () {
initData(vm)
} else {
oberve vm._data = {}, true
}
4. computed
5. watch
为什么我们能够在mouted直接访问到 this.xxx,因为 proxy 做了一层代理(vm, _data, {xxx})
响应式对象
Vue会把 props、data 等变成响应式对象,在创建过程中,若发现子属性也为对象则递归把该对象变成响应式 props 初始化 遍历配置
调用defineReactive把 props 对应的值变成响应式的, vm._props.xxx定义到 props 对应属性
vm._props.xxx 的访问代理到 vm.xxx
initdata 同理
1.vm._data.xxx代理到vm.xxx 2.observe观察data 变成响应式的
proxy
代理的作用是把 props 和 data 上的属性代理到 vm 实例上
proxy代码如下:
proxy 方法的实现很简单,通过 Object.defineProperty 把 target[sourceKey][key] 的读写变成了对 target[key] 的读写
observe
observe 方法的作用就是给非 VNode 的对象类型数据添加一个 Observer
Observer
Observer是一个类, 作用是给对象添加属性 getter 和 setter, 用于依赖收集和派发更新。 实例化dep对象 接着 def 方法封装 defineProperty 对 ob
shouldObserver 控制 Observer()
Observer 对数组调用observerArray 对纯对象执行walk方法。
object.keys 拿到的是不可枚举的
defineReactive函数 getOwnPropertyDescriptor
依赖收集 reactiveGetter export function defineReactive ( 」 setter 的逻辑有 2 个关键的点,一个是 childOb = !shallow && observe(newVal),如果 shallow 为 false 的情况,会对新设置的值变成一个响应式对象;另一个是 dep.notify()
当我们在组件中对响应的数据做了修改,就会触发setter的逻辑-》dep.notify
Dep 是整个 getter 依赖收集的核心
Dep 是一个class, 定义了一些属性和方法
建立数据和watcher的桥梁
如果我们没有订阅的话, 即时去修改它 也不会重新渲染。 总结: 依赖收集就是订阅数据变化的 watcher 的收集 依赖收集的目的就是当这些响应式数据发送变化,触发它们的setter的时候, 能知道应该通知哪些订阅者去做相应的逻辑处理
派发更新
queueWatcher 这个函数有个优化点 就是针对数据改变都触发的 watcher, 而是把这些watcher先添加到一个队列中,然后在nextTick后运行flushSchedulerQueue
update 后 watcher 推到 nextTick()做了 flushSchedulerQueue
其中设置has对象保证同一个watcher只添加一次,接着对flushing的判断
flushSchedulerQueue这个函数主要做了 队列排序: queue.sort((a, b) => a.id - b.id)
组件的更新由父到子;因为父组件的创建过程是先于子的,所以 watcher 的创建也是先父后子,执行顺序也应该保持先父后子。
用户的自定义 watcher 要优先于渲染 watcher 执行;因为用户自定义 watcher 是在渲染 watcher 之前创建的。
如果一个组件在父组件的 watcher 执行期间被销毁,那么它对应的 watcher 执行都可以被跳过,所以父组件的 watcher 应该先执行。
队列遍历 在对queue 排序后,接着就是要对它进行遍历,拿到响应的watcher, 进行water.run
nextTick 是vue的一个核心:
所有的同步任务都在主线程上,形成一个任执行栈
主线程之外,还存在一个任务队列,只要异步任务又了运行结果,就在任务队列中放置一个事件
一旦执行栈中所有同步任务执行完毕,系统就会读取“任务队列”,那些对应的异步任务,于是结束等待状态,执行执行栈, 开始执行。
macro task 结束后,都要清空所有的 micro task。
flushcallbash
总结; nextTick是把要执行的任务推到一个队列中,在下一个tick 同步执行
数据改变后出发渲染watcher 的update,但是watcher的flush是在nextTick后,所以重新渲染是异步的。
监测数据变化不能被检测到
数组del
计算属性的数据原理:
侦听属性 配置方法:
编译 template 到render
编译入口:
最终调用的是
在 lifecycle 中
渲染watcher
在定义的watcher 里面 有个
watcher 初始化的时候会执行回调函数,另一个当vm实例中的监测数据发生变化的时候执行回调函数
最后更新于
这有帮助吗?