directive包含5个钩子函数
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind:只调用一次,指令与元素解绑时调用。
首先会将用户传入的指令转化为下面的形式
let app = new Vue({
el : '#box',
directives : {
test : {
bind(){
console.log('bind')
},
inserted(){
console.log('insert')
},
update(){
console.log('update')
},
componentUpdated(){
console.log('componentUpdated')
},
unbind(){
console.log('unbind')
}
}
}
})
先转化一层
directives : [{name: 'test', rawName: 'v-test'}]
第二层
{
v-test: {
def: {bind: ƒ, inserted: ƒ, update: ƒ, componentUpdated: ƒ, unbind: ƒ}
modifiers: {}
name: "test"
rawName: "v-test"
}
}
bind,update,unbind都是直接触发的,代码如下
for (key in newDirs) {
oldDir = oldDirs[key]
dir = newDirs[key]
if (!oldDir) { //判断旧指令不存在 就触发bind函数
// new directive, bind
callHook(dir, 'bind', vnode, oldVnode) // 直接执行bind函数
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir)
}
} else { // 更新了节点
// existing directive, update
dir.oldValue = oldDir.value
dir.oldArg = oldDir.arg
callHook(dir, 'update', vnode, oldVnode) // 旧指令和新指令都有 直接执行update函数
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir)
}
}
}
if (!isCreate) {
for (key in oldDirs) {
if (!newDirs[key]) { //拿旧指令对比新指令
// no longer present, unbind
callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy) //如果旧的指令在新指令中不存在就卸载,顺便执行unbind函数
}
}
}
接下来来看insert处理和componentUpdated处理
首先是insert
1.先保存钩子函数
if (dir.def && dir.def.inserted) {
dirsWithInsert.push(dir)
}
2.组装成函数callInsert
3.合并到insert钩子
if (dirsWithInsert.length) {
const callInsert = () => {
for (let i = 0; i < dirsWithInsert.length; i++) {
callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
}
}
if (isCreate) {
mergeVNodeHook(vnode, 'insert', callInsert)
} else {
callInsert()
}
}
保存之后,那么就需要执行
1.第一次创建的时候,会去调用mergeVNodeHook函数来将insert钩子函数合并到vnode身上,因为此时节点还没有插入到dom中
mergeVNodeHook(vnode, 'insert', callInsert)
export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) {
if (def instanceof VNode) {
def = def.data.hook || (def.data.hook = {})
}
let invoker
const oldHook = def[hookKey]
function wrappedHook () {
hook.apply(this, arguments)
// important: remove merged hook to ensure it's called only once
// and prevent memory leak
remove(invoker.fns, wrappedHook)
}
if (isUndef(oldHook)) {
// no existing hook
invoker = createFnInvoker([wrappedHook])
} else {
/* istanbul ignore if */
if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {
// already a merged invoker
invoker = oldHook
invoker.fns.push(wrappedHook)
} else {
// existing plain hook
invoker = createFnInvoker([oldHook, wrappedHook])
}
}
invoker.merged = true
def[hookKey] = invoker
}
export function createFnInvoker (fns: Function | Array<Function>, vm: ?Component): Function {
function invoker () {
const fns = invoker.fns
if (Array.isArray(fns)) {
const cloned = fns.slice()
for (let i = 0; i < cloned.length; i++) {
invokeWithErrorHandling(cloned[i], null, arguments, vm, `v-on handler`)
}
} else {
// return handler return value for single handlers
return invokeWithErrorHandling(fns, null, arguments, vm, `v-on handler`)
}
}
invoker.fns = fns
return invoker
}
2.绑定之后就要去执行了 执行的顺序是 updateComponent -> patch -> invokeInsertHook
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue
} else {
for (let i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i])
}
}
}
依次遍历里面的insert钩子函数并且执行
其次是componentUpdated执行
1.先保存钩子函数
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir)
}
2.更本节点的postpatch合并起来
if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode, 'postpatch', () => {
for (let i = 0; i < dirsWithPostpatch.length; i++) {
callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
}
})
}
3.在更新节点的时候就立马执行
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)