1.请你说下数据绑定(数据绑定原理):

通俗讲解vue源码分析 - 图1

  • 首先通过一次渲染操作触发Data的getter(这里保证只有视图中需要被用到的data才会触发getter)进行依赖收集,这时候其实Watcher与data可以看成一种被绑定的状态(实际上是data的闭包中有一个Deps订阅者,在修改的时候会通知所有的Watcher观察者),在data发生变化的时候会触发它的setter,setter通知Watcher,Watcher进行回调通知组件重新渲染的函数,之后根据diff算法来决定是否发生视图的更新。

初始化data(个人简化了的initData)

  1. function initData (vm: Component) {
  2. /*得到data数据*/
  3. let data = vm.$options.data
  4. /*遍历data,和props对象*/
  5. const keys = Object.keys(data)
  6. const props = vm.$options.props
  7. let i = keys.length
  8. //遍历data中的数据,且props和data没有key没有冲突
  9. while (i--) {
  10. if (!(props && hasOwn(props, keys[i]))) {
  11. proxy(vm, `_data`, keys[i])
  12. }
  13. /*这里是我们前面讲过的代理,将data上面的属性代理到了vm实例上*/
  14. }
  15. }
  16. /*从这里开始我们要observe了,开始对数据进行绑定*/
  17. observe(data, true /* asRootData */)
  18. }

initData函数主要做了两件事:

  • _data上面的数据代理到vm实例上
  • 通过observe将所有数据变成observable

proxy

  1. /*添加代理*/
  2. export function proxy (target: Object, sourceKey: string, key: string) {
  3. sharedPropertyDefinition.get = function proxyGetter () {
  4. return this[sourceKey][key]
  5. }
  6. sharedPropertyDefinition.set = function proxySetter (val) {
  7. this[sourceKey][key] = val
  8. }
  9. Object.defineProperty(target, key, sharedPropertyDefinition)
  10. }
  • 通过proxy函数将data上面的数据代理到vm上,这样就可以用app.text代替app._data.text了

Observe

  • Vue的响应式数据都会有一个ob的属性作为标记,里面存放了该属性的观察器,也就是Observer的实例,防止重复绑定。
  • Observer为数据加上响应式属性进行双向绑定。如果是对象则进行深度遍历,为每一个子对象都绑定上方法,如果是数组则为每一个成员都绑定上方法。

Watcher

  • Watcher是一个观察者对象。依赖收集以后Watcher对象会被保存在Deps中,数据变动的时候会由Deps通知Watcher实例,然后由Watcher实例回调cb进行视图的更新。

Dep

  • Dep是一个发布者,可以订阅多个观察者,依赖收集之后Deps中会存在一个或多个Watcher对象,在数据变更的时候通知所有的Watcher。

defineReactive

  • defineReactive的作用是通过Object.defineProperty为数据定义上getter\setter方法,进行依赖收集后闭包中的Deps会存放Watcher对象。触发setter改变数据的时候会通知Deps订阅者通知所有的Watcher观察者对象进行试图的更新。

2.小伙子,说下虚拟dom:

  • 咳咳,一般来说,我们要修改试图的话需要直接操作dom执行各种事件才行,是应用一大就会变得难以维护。
  • vnode就是把真实dom都抽象成一颗以js对象构成的抽象树,在修改抽象树的数据后将抽象树转换成真实dom重绘到页面上去。
  • ,当某个抽象树的某个数据被修改的时候,set方法会让闭包中的Dep调用notify通知所有订阅者Watcher,Watcher通过get方法执行vm._update(vm._render(),hydrating)
  • 经过diff算法只需要修改抽象树修改了的部分即可,相对于一大片的HTML修改,大大提高了性能。
  • Vue使用了这样的抽象节点VNode,它是对真实DOM的一层抽象,所以它不依赖某个平台,比如weex

3.那diff算法你知道怎么运作的吗?

  • diff算法在patch方法内,path将通过新老Vnode节点的对比,根据两者的比较结果进行最小单位地修改视图,而不是将整个视图根据Vnode重绘。
  • diff算法是通过同层的树节点进行比较而非对树进行逐层搜索遍历的方式,所以时间复杂度是O(n),是非常高效的算法。
  • 通俗讲解vue源码分析 - 图2
  • 这张图代表旧的VNode与新VNode进行patch的过程,他们只是在同层级的VNode之间进行比较得到变化(第二张图中相同颜色的方块代表互相进行比较的VNode节点)

4.使用v-for进行列表渲染的时候,加:key的效果是什么,为什么会这样?

  • 效果是更高效的更新虚拟dom。
  • 数据更新后,新老Vnode如果是同一节点,就会直接修改现有的节点,否则就是创建新的dom,移除旧的dom
  • 判断两个Vnode节点是否是同一节点,需要满足:
    • key相同
    • tag(当前节点的标签名)相同
    • isComment(是否为注释节点)相同
    • 是否data(当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息)都有定义
    • 当标签是的时候,type必须相同
  • 所以,这就是为什么尽可能在使用 v-for 时提供key

延伸: 更新虚拟dom的规则是这样:

1.如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),那么只需要替换elm以及componentInstance即可。

2.新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren,这个updateChildren也是diff的核心。

3.如果老节点没有子节点而新节点存在子节点,先清空老节点DOM的文本内容,然后为当前DOM节点加入子节点。

4.当新节点没有子节点而老节点有子节点的时候,则移除该DOM节点的所有子节点。

5.当新老节点都无子节点的时候,只是文本的替换。