一、diff算法的触发时机
首先要知道的是,vue通过数据绑定来修改视图,当某个数据被修改的时候,set方法会让闭包中的Dep调用notify通知所有订阅者Watcher,Watcher通过get方法执行vm._update(vm._render()),hydrating);render()函数中会调用Vnode函数来生成虚拟DOM。
其次,当生命周期走到created~beforeMounted的时候,会进行编译template模板成render函数,此时就会创建出虚拟DOM。之后当数据发生变化的时候会重新编译生成一个新的虚拟DOM(第一段中提到的,vm._update(vm._render()),hydrating),又会执行render()生成虚拟DOM),之后两个新旧虚拟DOM就会进行比较。
具体的流程概括起来就是:数据改变 -> setter -> Dep.notify() -> diff算法比较......
二、diff算法的触发过程
过程:Patch() -> patchVnode() -> updateChildren() -> pathVnode() -> updateChildren().....
步骤1:比较两个虚拟DON树,对根节点root执行Patch(oldVnode,newVnode)函数,比较两个虚拟DOM的根节点是否相同,如果不同就直接替换
步骤2:
步骤3:
三、一些diff中的细节点
1.怎么判定两个Vnode节点是同一个还是不是同一个呢
/*
判断两个VNode节点是否是同一个节点,需要满足以下条件
key相同
tag(当前节点的标签名)相同
isComment(是否为注释节点)相同
是否data(当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息)都有定义
当标签是<input>的时候,type必须相同
*/
function sameVnode (a, b) {
return (
a.key === b.key &&
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
)
}
//这里补充用vue定义的VNode类
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
functionalContext: Component | void; // only for functional component root nodes
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions
) {
/*当前节点的标签名*/
this.tag = tag
/*当前节点对应的对象,包含了具体的一些数据信息,是一个VNodeData类型,可以参考VNodeData类型中的数据信息*/
this.data = data
/*当前节点的子节点,是一个数组*/
this.children = children
/*当前节点的文本*/
this.text = text
/*当前虚拟节点对应的真实dom节点*/
this.elm = elm
/*当前节点的名字空间*/
this.ns = undefined
/*编译作用域*/
this.context = context
/*函数化组件作用域*/
this.functionalContext = undefined
/*节点的key属性,被当作节点的标志,用以优化*/
this.key = data && data.key
/*组件的option选项*/
this.componentOptions = componentOptions
/*当前节点对应的组件的实例*/
this.componentInstance = undefined
/*当前节点的父节点*/
this.parent = undefined
/*简而言之就是是否为原生HTML或只是普通文本,innerHTML的时候为true,textContent的时候为false*/
this.raw = false
/*静态节点标志*/
this.isStatic = false
/*是否作为根节点插入*/
this.isRootInsert = true
/*是否为注释节点*/
this.isComment = false
/*是否为克隆节点*/
this.isCloned = false
/*是否有v-once指令*/
this.isOnce = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
对于VnodeData的定义
/**
* 表示Vue.js中VNode的数据对象的接口
*/
export interface VNodeData {
key?: string | number; // VNode的唯一标识
slot?: string; // VNode所属的插槽名称
scopedSlots?: { [key: string]: ScopedSlot | undefined }; // VNode的作用域插槽
ref?: VNodeRef; // VNode的引用信息
refInFor?: boolean; // 表示VNode是否在v-for指令中
tag?: string; // VNode的HTML标签名
staticClass?: string; // VNode的静态CSS类
class?: any; // VNode的动态CSS类
staticStyle?: { [key: string]: any }; // VNode的静态内联样式
style?: StyleValue; // VNode的动态内联样式
props?: { [key: string]: any }; // 传递给VNode组件的属性
attrs?: { [key: string]: any }; // VNode元素的属性
domProps?: { [key: string]: any }; // VNode元素的DOM属性
hook?: { [key: string]: Function }; // VNode生命周期事件钩子
on?: { [key: string]: Function | Function[] }; // VNode的事件监听器
nativeOn?: { [key: string]: Function | Function[] }; // VNode的原生事件监听器
transition?: object; // VNode的过渡信息
show?: boolean; // 表示VNode是否应该显示
inlineTemplate?: {
render: Function; // 内联模板的渲染函数
staticRenderFns: Function[]; // 内联模板的静态渲染函数
};
directives?: VNodeDirective[]; // 应用于VNode的指令
keepAlive?: boolean; // 表示VNode是否应该保持活动状态
}