千家信息网

Vue指令的实现原理是什么

发表于:2025-12-02 作者:千家信息网编辑
千家信息网最后更新 2025年12月02日,本篇内容介绍了"Vue指令的实现原理是什么"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、基本使用
千家信息网最后更新 2025年12月02日Vue指令的实现原理是什么

本篇内容介绍了"Vue指令的实现原理是什么"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

一、基本使用

官网案例:

二、指令工作原理

2.1、初始化

初始化全局API时,在platforms/web下,调用createPatchFunction生成VNode转换为真实DOM的patch方法,初始化中比较重要一步是定义了与DOM节点相对应的hooks方法,在DOM的创建(create)、激活(avtivate)、更新(update)、移除(remove)、销毁(destroy)过程中,分别会轮询调用对应的hooks方法,这些hooks中一部分是指令声明周期的入口。

// src/core/vdom/patch.jsconst hooks = ['create', 'activate', 'update', 'remove', 'destroy']export function createPatchFunction (backend) {  let i, j  const cbs = {}  const { modules, nodeOps } = backend  for (i = 0; i < hooks.length; ++i) {    cbs[hooks[i]] = []    // modules对应vue中模块,具体有class, style, domListener, domProps, attrs, directive, ref, transition    for (j = 0; j < modules.length; ++j) {      if (isDef(modules[j][hooks[i]])) {        // 最终将hooks转换为{hookEvent: [cb1, cb2 ...], ...}形式        cbs[hooks[i]].push(modules[j][hooks[i]])      }    }  }  // ....  return function patch (oldVnode, vnode, hydrating, removeOnly) {    // ...  }}

2.2、模板编译

模板编译就是解析指令参数,具体解构后的ASTElement如下所示:

{  tag: 'input',  parent: ASTElement,  directives: [    {      arg: null, // 参数      end: 56, // 指令结束字符位置      isDynamicArg: false, // 动态参数,v-xxx[dynamicParams]='xxx'形式调用      modifiers: undefined, // 指令修饰符      name: "model",      rawName: "v-model", // 指令名称      start: 36, // 指令开始字符位置      value: "inputValue" // 模板    },    {      arg: null,      end: 67,      isDynamicArg: false,      modifiers: undefined,      name: "focus",      rawName: "v-focus",      start: 57,      value: ""    }  ],  // ...}

2.3、生成渲染方法

vue推荐采用指令的方式去操作DOM,由于自定义指令可能会修改DOM或者属性,所以避免指令对模板解析的影响,在生成渲染方法时,首先处理的是指令,如v-model,本质是一个语法糖,在拼接渲染函数时,会给元素加上value属性与input事件(以input为例,这个也可以用户自定义)。

with (this) {    return _c('div', {        attrs: {            "id": "app"        }    }, [_c('input', {        directives: [{            name: "model",            rawName: "v-model",            value: (inputValue),            expression: "inputValue"        }, {            name: "focus",            rawName: "v-focus"        }],        attrs: {            "type": "text"        },        domProps: {            "value": (inputValue) // 处理v-model指令时添加的属性        },        on: {            "input": function($event) { // 处理v-model指令时添加的自定义事件                if ($event.target.composing)                    return;                inputValue = $event.target.value            }        }    })])}

2.4、生成VNode

vue的指令设计是方便我们操作DOM,在生成VNode时,指令并没有做额外处理。

2.5、生成真实DOM

在vue初始化过程中,我们需要记住两点:

  • 状态的初始化是 父 -> 子,如beforeCreate、created、beforeMount,调用顺序是 父 -> 子

  • 真实DOM挂载顺序是 子 -> 父,如mounted,这是因为在生成真实DOM过程中,如果遇到组件,会走组件创建的过程,真实DOM的生成是从子到父一级级拼接。

在patch过程中,每此调用createElm生成真实DOM时,都会检测当前VNode是否存在data属性,存在,则会调用invokeCreateHooks,走初创建的钩子函数,核心代码如下:

// src/core/vdom/patch.jsfunction createElm (    vnode,    insertedVnodeQueue,    parentElm,    refElm,    nested,    ownerArray,    index  ) {    // ...    // createComponent有返回值,是创建组件的方法,没有返回值,则继续走下面的方法    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {      return    }    const data = vnode.data    // ....    if (isDef(data)) {        // 真实节点创建之后,更新节点属性,包括指令        // 指令首次会调用bind方法,然后会初始化指令后续hooks方法        invokeCreateHooks(vnode, insertedVnodeQueue)    }    // 从底向上,依次插入    insert(parentElm, vnode.elm, refElm)    // ...  }

以上是指令钩子方法的第一个入口,是时候揭露directive.js神秘的面纱了,核心代码如下:

// src/core/vdom/modules/directives.js// 默认抛出的都是updateDirectives方法export default {  create: updateDirectives,  update: updateDirectives,  destroy: function unbindDirectives (vnode: VNodeWithData) {    // 销毁时,vnode === emptyNode    updateDirectives(vnode, emptyNode)  }}function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {  if (oldVnode.data.directives || vnode.data.directives) {    _update(oldVnode, vnode)  }}function _update (oldVnode, vnode) {  const isCreate = oldVnode === emptyNode  const isDestroy = vnode === emptyNode  const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)  const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)  // 插入后的回调  const dirsWithInsert = [  // 更新完成后回调  const dirsWithPostpatch = []  let key, oldDir, dir  for (key in newDirs) {    oldDir = oldDirs[key]    dir = newDirs[key]    // 新元素指令,会执行一次inserted钩子方法    if (!oldDir) {      // new directive, bind      callHook(dir, 'bind', vnode, oldVnode)      if (dir.def && dir.def.inserted) {        dirsWithInsert.push(dir)      }    } else {      // existing directive, update      // 已经存在元素,会执行一次componentUpdated钩子方法      dir.oldValue = oldDir.value      dir.oldArg = oldDir.arg      callHook(dir, 'update', vnode, oldVnode)      if (dir.def && dir.def.componentUpdated) {        dirsWithPostpatch.push(dir)      }    }  }  if (dirsWithInsert.length) {    // 真实DOM插入到页面中,会调用此回调方法    const callInsert = () => {      for (let i = 0; i < dirsWithInsert.length; i++) {        callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)      }    }    // VNode合并insert hooks    if (isCreate) {      mergeVNodeHook(vnode, 'insert', callInsert)    } else {      callInsert()    }  }  if (dirsWithPostpatch.length) {    mergeVNodeHook(vnode, 'postpatch', () => {      for (let i = 0; i < dirsWithPostpatch.length; i++) {        callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)      }    })  }  if (!isCreate) {    for (key in oldDirs) {      if (!newDirs[key]) {        // no longer present, unbind        callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)      }    }  }}

对于首次创建,执行过程如下:

1.oldVnode === emptyNode,isCreate为true,调用当前元素中所有bind钩子方法。

2.检测指令中是否存在inserted钩子,如果存在,则将insert钩子合并到VNode.data.hooks属性中。

3.DOM挂载结束后,会执行invokeInsertHook,所有已挂载节点,如果VNode.data.hooks中存在insert钩子。则会调用,此时会触发指令绑定的inserted方法。

一般首次创建只会走bind和inserted方法,而update和componentUpdated则与bind和inserted对应。在组件依赖状态发生改变时,会用VNode diff算法,对节点进行打补丁式更新,其调用流程:

1.响应式数据发生改变,调用dep.notify,通知数据更新。

2.调用patchVNode,对新旧VNode进行差异化更新,并全量更新当前VNode属性(包括指令,就会进入updateDirectives方法)。

3.如果指令存在update钩子方法,调用update钩子方法,并初始化componentUpdated回调,将postpatch hooks挂载到VNode.data.hooks中。

4.当前节点及子节点更新完毕后,会触发postpatch hooks,即指令的componentUpdated方法

核心代码如下:

// src/core/vdom/patch.jsfunction patchVnode (    oldVnode,    vnode,    insertedVnodeQueue,    ownerArray,    index,    removeOnly  ) {    // ...    const oldCh = oldVnode.children    const ch = vnode.children    // 全量更新节点的属性    if (isDef(data) && isPatchable(vnode)) {      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)    }    // ...    if (isDef(data)) {    // 调用postpatch钩子      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)    }  }

unbind方法是在节点销毁时,调用invokeDestroyHook,这里不做过多描述。

三、注意事项

使用自定义指令时,和普通模板数据绑定,v-model还是存在一定的差别,如虽然我传递参数(v-xxx='param')是一个引用类型,数据变化时,并不能触发指令的bind或者inserted,这是因为在指令的声明周期内,bind和inserted只是在初始化时调用一次,后面只会走update和componentUpdated。

指令的声明周期执行顺序为bind -> inserted -> update -> componentUpdated,如果指令需要依赖于子组件的内容时,推荐在componentUpdated中写相应业务逻辑。

vue中,很多方法都是循环调用,如hooks方法,事件回调等,一般调用都用try catch包裹,这样做的目的是为了防止一个处理方法报错,导致整个程序崩溃,这一点在我们开发过程中可以借鉴使用。

"Vue指令的实现原理是什么"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

指令 方法 钩子 更新 节点 生成 属性 过程 元素 组件 模板 处理 参数 数据 时调 原理 事件 代码 内容 周期 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 当前网络安全应用技术包括 大数据数据库选择 盐城软件开发价格大全 网络安全舆情监控 网络安全考什么证 榆树有名的网络技术服务售后服务 计算机专业和软件开发 服务器上配置金万维动态域名 河北信息网络安全案件 大学生网络安全的培训 科密消费机连接数据库失败 嵌入式软件开发技术就业前景 青州中医院网络安全宣传 计算机网络技术分几级 厦门企业管理财务软件开发 上海电信软件开发职业规划 sql 数据库按时间排序 云南分会场网络安全 软件开发成本计入哪里 榆树智能网络技术质量推荐 软件开发中写前端的叫什么 现在建立数据库的常用软件 数据库大作业毕业报告书 杭州学习软件开发哪家靠谱 北京齐家网络技术 数据库的表性别类型 数据库mdf数据读取错误 驾校学生管理系统数据库 数据库查询分析器绿色官方版 泗洪直销网络技术解决方案
0