响应式原理
- 获取到vue中data
- 遍历data的keys值,为每个关键字添加defineReactive函数
- defineReactive调用Object.defineProperty这个静态方法,为data的keys设置get、set方法
- get方法主要是用于依赖收集,收集该keys的所有监听
set,方法用于数据改变时通知监听器调用
依赖收集
Dep
在最开始进行一次render,那么所有被渲染所依赖的data中的数据就会被getter收集到Dep的subs中去。在对data中的数据进行修改的时候setter只会触发Dep的subs的函数。Watcher
当调用vue的watch方法时会生成一个watcher,此时会调用一个get,将自身添加到Dep的target对象上,之后调用一次get方法,就会addSub到sub中,在修改data中数据的时候会触发dep对象的notify,通知所有Watcher对象去修改对应视图。具体操作
将观察者Watcher实例赋值给全局的Dep.target,然后触发render操作只有被Dep.target标记过的才会进行依赖收集。有Dep.target的对象会将Watcher的实例push到subs中,在对象被修改出发setter操作的时候dep会调用subs中的Watcher实例的update方法进行渲染。
从源码角度看数据绑定
- 调用initData,其实这段代码主要做了两件事,一是将_data上面的数据代理到vm上,另一件事通过observe将所有数据变成observable
- 调用observer方法,尝试创建一个Observer实例(ob),如果成功创建Observer实例则返回新的Observer实例,如果已有Observer实例则返回现有的Observer实例。
- Observer为数据加上响应式属性进行双向绑定。如果是对象则进行深度遍历,为每一个子对象都绑定上方法,如果是数组则为每一个成员都绑定上方法。
- Watcher是一个观察者对象。依赖收集以后Watcher对象会被保存在Deps中,数据变动的时候会由Deps通知Watcher实例,然后由Watcher实例回调cb进行视图的更新。
- 来看看Dep类。其实Dep就是一个发布者,可以订阅多个观察者,依赖收集之后Deps中会存在一个或多个Watcher对象,在数据变更的时候通知所有的Watcher。
- defineReactive的作用是通过Object.defineProperty为数据定义上getter\setter方法,进行依赖收集后闭包中的Deps会存放Watcher对象。触发setter改变数据的时候会通知Deps订阅者通知所有的Watcher观察者对象进行试图的更新。
diff实现
- patch将新老VNode节点进行比对,然后将根据两者的比较结果进行最小单位地修改视图,而不是将整个视图根据新的VNode重绘。patch的核心在于diff算法,这套算法可以高效地比较virtual DOM的变更,得出变化以修改视图。
- diff算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度只有O(n),是一种相当高效的算法。从代码中不难发现,当oldVnode与vnode在sameVnode的时候才会进行patchVnode,也就是新旧VNode节点判定为同一节点的时候才会进行patchVnode这个过程,否则就是创建新的DOM,移除旧的DOM。
- sameVnode,当两个VNode的tag、key、isComment都相同,并且同时定义或未定义data的时候,且如果标签为input则type必须相同。这时候这两个VNode则算sameVnode,可以直接进行patchVnode操作。
patchVnode的规则是这样的:
如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),那么只需要替换elm以及componentInstance即可。
新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren,这个updateChildren也是diff的核心。
如果老节点没有子节点而新节点存在子节点,先清空老节点DOM的文本内容,然后为当前DOM节点加入子节点。
当新节点没有子节点而老节点有子节点的时候,则移除该DOM节点的所有子节点。
当新老节点都无子节点的时候,只是文本的替换。
nextTick
nextTick的实现比较简单,执行的目的是在microtask或者task中推入一个funtion,在当前栈执行完毕(也行还会有一些排在前面的需要执行的任务)以后执行nextTick传入的funtion 看了源码发现timerFunc会检测当前环境而不同实现,其实就是按照Promise,MutationObserver,setTimeout优先级,哪个存在使用哪个,最不济的环境下使用setTimeout。 这里解释一下,一共有Promise、MutationObserver(对节点的监听,插入完成时触发)以及setTimeout三种尝试得到timerFunc的方法。 优先使用Promise,在Promise不存在的情况下使用MutationObserver,这两个方法的回调函数都会在microtask中执行,新版使用MessageChannel,它们会比setTimeout更早执行,所以优先使用。 如果上述两种方法都不支持的环境则会使用setTimeout,在task尾部推入这个函数,等待调用执行。 参考文档:https://github.com/answershuto/learnVue