1.directiveshow两个 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.格式化指令,保持一致的指令定义格局。如果定义指令的属性值是函数,则将赋值给bindupdate属性。

/** * 格式化组件指令 */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 元素,节点销毁)中有很多钩子被调用,用来解决节点对应标签的各种数据。 这些钩子中包含了指令的createupdatedestroy三个钩子函数,将在节点渲染的过程中被调用。

这里的钩子函数和指令定义时的钩子函数不雷同。
/* 指令的钩子函数 */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 封装了节点默认的 insertcomponentUpdated 钩子函数。默认钩子函数和指令定义的钩子函数顺次增加到数组中,保留在封装函数的 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;}