1.directive
、show
两个 Vue 自带指令通过extend
办法保留在Vue.options.directives
上。
var platformDirectives = { model: directive, show: show,};extend(Vue.options.directives, platformDirectives);function extend(to, _from) { for (var key in _from) { to[key] = _from[key]; } return to;}
2.Vue 提供了Vue.directive
办法创立全局指令,增加到Vue.options.directives
上。
var ASSET_TYPES = ["component", "directive", "filter"];ASSET_TYPES.forEach(function (type) { Vue[type] = function (id, definition) { if (!definition) { return this.options[type + "s"][id]; } else { /* istanbul ignore if */ if (process.env.NODE_ENV !== "production" && type === "component") { validateComponentName(id); } if (type === "component" && isPlainObject(definition)) { definition.name = definition.name || id; definition = this.options._base.extend(definition); } if (type === "directive" && typeof definition === "function") { definition = { bind: definition, update: definition }; } this.options[type + "s"][id] = definition; return definition; } };});
3.不仅能够全局增加指令,也能够在组件选项上增加指令,仅在组件外部失效。创立组件时,会格式化组件选项上的指令,再合并全局和组件内的指令。
/** * 合并组件选项 */function mergeOptions ( parent, child, vm) { ... normalizeDirectives(child);// 格式化指令 ... if (!child._base) { if (child.extends) { parent = mergeOptions(parent, child.extends, vm); } if (child.mixins) { for (var i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm); } } } var options = {}; var key; for (key in parent) { mergeField(key);// 合并选项 } for (key in child) { if (!hasOwn(parent, key)) { mergeField(key);// 合并选项 } } function mergeField (key) { var strat = strats[key] || defaultStrat;// 获取合并策略 options[key] = strat(parent[key], child[key], vm, key); } return options}
4.格式化指令,保持一致的指令定义格局。如果定义指令的属性值是函数,则将赋值给bind
和update
属性。
/** * 格式化组件指令 */function normalizeDirectives(options) { var dirs = options.directives; if (dirs) { for (var key in dirs) { var def$$1 = dirs[key]; if (typeof def$$1 === "function") { // 值为函数类型 dirs[key] = { bind: def$$1, update: def$$1 }; } } }}
5.全局指令和组件内指令是通过Object.create
进行合并的,组件内指令对象的原型链指向全局的指令对象,当在组件内未找到该指令时就会去全局的指令对象内查找。
/** * Object.create的合并策略 */function mergeAssets(parentVal, childVal, vm, key) { var res = Object.create(parentVal || null); if (childVal) { process.env.NODE_ENV !== "production" && assertObjectType(key, childVal, vm); return extend(res, childVal); } else { return res; }}/** * 设置componet,directive,filter合并策略 */ASSET_TYPES.forEach(function (type) { strats[type + "s"] = mergeAssets;});
4.标签上的指令会被编译器解析成对象。解析后果会在组件渲染时保留在节点(VNode
实例)上。
<div v-test:name.fale="getName"></div>
指令解析后果:
{ directives: [ { name: "test", rawName: "v-test:name.fale", value: getName, expression: "getName", // 表达式 arg: "name", // 参数 modifiers: { fale: true }, // 修饰符 }, ];}
5.节点在渲染的过程(创立 DOM
或组件,更新节点,插入到父级 DOM
元素,节点销毁)中有很多钩子被调用,用来解决节点对应标签的各种数据。 这些钩子中包含了指令的create
、update
、destroy
三个钩子函数,将在节点渲染的过程中被调用。
这里的钩子函数和指令定义时的钩子函数不雷同。
/* 指令的钩子函数 */var emptyNode = new VNode("", {}, []);var directives = { create: updateDirectives, // 创立了DOM元素后调用 update: updateDirectives, // 更新节点时调用 destroy: function unbindDirectives(vnode) { // 销毁节点时调用 updateDirectives(vnode, emptyNode); },};function updateDirectives(oldVnode, vnode) { if (oldVnode.data.directives || vnode.data.directives) { _update(oldVnode, vnode); }}
6.指令的三个钩子都会调用_update
函数。
function _update(oldVnode, vnode) { var isCreate = oldVnode === emptyNode; var isDestroy = vnode === emptyNode; var oldDirs = normalizeDirectives$1( oldVnode.data.directives, oldVnode.context ); // 旧标签节点上的指令数据汇合(蕴含标签上的指令信息和指令定义) var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context); // 新标签节点上的指令数据汇合(蕴含标签上的指令信息和指令定义) var dirsWithInsert = []; var dirsWithPostpatch = []; var key, oldDir, dir; for (key in newDirs) { oldDir = oldDirs[key]; dir = newDirs[key]; if (!oldDir) { // 指令第一次绑定到元素上 // new directive, bind callHook$1(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$1(dir, "update", vnode, oldVnode); if (dir.def && dir.def.componentUpdated) { dirsWithPostpatch.push(dir); } } } if (dirsWithInsert.length) { var callInsert = function () { for (var i = 0; i < dirsWithInsert.length; i++) { callHook$1(dirsWithInsert[i], "inserted", vnode, oldVnode); } }; if (isCreate) { mergeVNodeHook(vnode, "insert", callInsert); } else { callInsert(); } } if (dirsWithPostpatch.length) { mergeVNodeHook(vnode, "postpatch", function () { for (var i = 0; i < dirsWithPostpatch.length; i++) { callHook$1(dirsWithPostpatch[i], "componentUpdated", vnode, oldVnode); } }); } if (!isCreate) { for (key in oldDirs) { if (!newDirs[key]) { // no longer present, unbind callHook$1(oldDirs[key], "unbind", oldVnode, oldVnode, isDestroy); } } }}
7.在_update
函数中,先调用normalizeDirective$1
函数,依据指令名称在组件$options.directives
查找到指令的定义,并和对应的标签指令数据保留在一起。
如果在组件$options.directives
上未找到指令的定义,就会从它的原型链上查找,也就从全局指令上查找。
var emptyModifiers = Object.create(null);function normalizeDirectives$1(dirs, vm) { var res = Object.create(null); if (!dirs) { // $flow-disable-line return res; } var i, dir; for (i = 0; i < dirs.length; i++) { dir = dirs[i]; if (!dir.modifiers) { // 没有修饰符 // $flow-disable-line dir.modifiers = emptyModifiers; } res[getRawDirName(dir)] = dir; // 可能存在同名指令、但参数和修饰符不同 dir.def = resolveAsset(vm.$options, "directives", dir.name, true); // 依据指令名称获取指令定义,并保留在标签指令数据上 } // $flow-disable-line return res;}//获取原始名称(蕴含前缀、参数、修饰符)function getRawDirName(dir) { return ( dir.rawName || dir.name + "." + Object.keys(dir.modifiers || {}).join(".") );}
8.当没有旧标签指令时(指令第一次绑定到元素上),调用指令定义的bind
钩子函数,如果指令定义中还有insert
钩子函数,就将指令数据增加到 insert
队列中暂存。
/* * 调用指令钩子函数(格式化钩子函数参数) */function callHook$1(dir, hook, vnode, oldVnode, isDestroy) { var fn = dir.def && dir.def[hook]; if (fn) { try { fn(vnode.elm, dir, vnode, oldVnode, isDestroy); } catch (e) { handleError( e, vnode.context, "directive " + dir.name + " " + hook + " hook" ); } }}
9.否则,即新旧标签指令都存在,就调用指令定义的update
钩子函数,如果指令定义中还有componentUpdated
钩子函数,就将指令数据增加到componentUpdated
队列中暂存。
10.暂存在insert
队列中的指令,会在节点生成的 DOM 元素插入到父级 DOM 元素后调用它定义的 insert
钩子函数。而暂存在componentUpdated
队列中的指令,会在节点以及子节点更新实现后调用它定义的componentUpdated
钩子函数。
mergeVNodeHook
封装了节点默认的insert
、componentUpdated
钩子函数。默认钩子函数和指令定义的钩子函数顺次增加到数组中,保留在封装函数的fns
属性上,调用封装钩子函数时,将会顺次调用fns
数组上的钩子函数。
function mergeVNodeHook(def, hookKey, hook) { if (def instanceof VNode) { def = def.data.hook || (def.data.hook = {}); // 节点钩子函数汇合 } var invoker; var 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; // 更新周期函数}/* *封装函数,将原始函数保留在封装函数的fns属性上。当原始函数产生变更时,只批改fns属性就能够 */function createFnInvoker(fns, vm) { function invoker() { var arguments$1 = arguments; var fns = invoker.fns; if (Array.isArray(fns)) { var cloned = fns.slice(); for (var i = 0; i < cloned.length; i++) { invokeWithErrorHandling( cloned[i], null, arguments$1, 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;}