vue2-指令

作者: AAA前端 | 来源:发表于2021-06-08 17:10 被阅读0次

指令是vue提供的带有 v-前缀的特殊特性。
指令的职责是,当表达式的值变化时,将其产生的连带影响响应式的作用于DOM。

指令原理

在模板解析阶段,我们将指令解析到AST,然后使用AST生成代码字符串的过程中 实现 某些内置指令的功能, 最后在虚拟DOM渲染的过程中触发自定义指令的钩子函数使指令生效。

实现原理
在模板解析阶段,将节点上的指令解析出来并添加到AST 的 directives属性中。
随后directives数据会传递到Vnode中,接着可以通过 vnode.data.directives获取一个阶段说绑定的指令。

最后当虚拟DOM进行修补时,会根据节点对比结果触发一些钩子函数。 更新指令的程序会监听 create、update、destroy钩子函数,
并在这三个钩子函数触发时 对 Vnode和oldVnode进行对比, 最终对比结果触发指令的钩子函数。

v-if指令
v-if是在模板编译阶段实现的。

<li v-if="has">if</if>
<li v-else>else</li>

模板编译后生成的代码字符串

(has)? _c('li', [_v("if")]) : _c('li', [_v("else")])

这段代码最终执行时,根据has变量的值来选择创建哪个节点

v-for指令
v-for指令也是模板编译的代码生成阶段实现的

<li v-for="(item, index) in list">v-for {{index}}</li>

生成的代码字符串

_l((list), function(item, index){
  return _c('li', [
    _v("v-for " + _s(index))
  ])
})

_l是函数renderList的别名。当执行这段代码时, _l函数会循环变量 list并依次调用第二个参数 所传递 的函数。

v-on指令

v-on指令的作用是绑定事件监听器,事件类型有参数指定。 它用在普通元素上时,监听原生DOM事件;用在自定义元素组件上时,监听子组件触发的自定义事件。

从模板解析到生成Vnode,最终事件都会被保存到Vnode中。通过vnode.data.on得到一个节点注册的所有事件。

虚拟DOM在修补(patch)的过程中会根据不同的时机触发不同的钩子函数。

事件绑定相关的处理逻辑分别设置了create和update钩子函数,也就是说patch的过程中,当DOM创建或更新时,会触发事件绑定相关的处理逻辑。

create和update钩子函数都会执行updateDOMListener函数
相关代码如下


// 通过对比两个VNode的事件对象,来决定绑定原生DOM事件还是解绑原生DOM事件。
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  // 如果两个Vnode中事件对象都不存在,
  // 说明上一次没有绑定任何事件, 这一次元素更新也没有新增事件绑定
  if (!oldVnode.data.on && !vnode.data.on) {
    return
  }
  // 获取新老Vnode上的事件
  const on = vnode.data.on || {}
  const oldOn = oldVnode.data.on || {}
  // vnode.elm上保存vnode所对应的DOM元素
  target = vnode.elm
  // 更新事件监听器,通过对比on 与 oldOn. 判断调用add方法还是 remove方法执行绑定事件还是解绑事件,
  updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
  target = undefined
}

function add (
  event: string,
  handler: Function,
  capture: boolean,
  passive?: boolean,
  params?: Array<any>
) {
  if (capture) {
    console.log('Weex do not support event in bubble phase.')
    return
  }
  target.addEvent(event, handler, params)
}

function remove (
  event: string,
  handler: any,
  capture: any,
  _target?: any
) {
  (_target || target).removeEvent(event)
}

自定义指令的内部原理
虚拟DOM通过diff算法对比 两个 VNode之间的差异并更新真实的DOM节点。

在更新的过程中,可能是创建新的节点,可能是更新节点,也可能是删除节点。

虚拟DOM在渲染时,处理更新DOM内容外,还会触发钩子函数。

指令的处理逻辑封闭监听了create、update、destroy.

虚拟DOM在触发钩子函数时,下面代码对应的函数都会被执行。

export default {
  create: updateDirectives,
  update: updateDirectives,
  destroy: function unbindDirectives (vnode: VNodeWithData) {
    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 // 是否新虚拟节点不存在

  // normalizeDirectives 将模板中 使用的 指令  从用户注册的 自定义指令集合中 取出来
  const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context) // 旧的指令集合
  const newDirs = normalizeDirectives(vnode.data.directives, vnode.context) // 新的指令集合

  const dirsWithInsert = [] // 保存需要触发inserted 指令钩子的指令列表
  const dirsWithPostpatch = [] // 保存需要触发 componentUpdated 钩子函数的指令列表

  let key, oldDir, dir
  // 循环新的指令集合
  for (key in newDirs) {
    oldDir = oldDirs[key]
    dir = newDirs[key]
    // 如果旧指令不存在,说明改指令首次绑定到元素
    if (!oldDir) {
      // 新指令  触发 bind 函数
      callHook(dir, 'bind', vnode, oldVnode)
      // 如果有inserted 方法 添加到dirsWithInsert。等待执行
      if (dir.def && dir.def.inserted) {
        dirsWithInsert.push(dir)
      }
    } else {
      // 已经存在的指令 更新即可
      dir.oldValue = oldDir.value
      dir.oldArg = oldDir.arg
      // 触发update钩子函数
      callHook(dir, 'update', vnode, oldVnode)
      if (dir.def && dir.def.componentUpdated) {
        // 如果有componentUpdated方法 保存到 dirsWithPostpatch 等待执行
        dirsWithPostpatch.push(dir)
      }
    }
  }

  // 执行 dirsWithInsert 
  if (dirsWithInsert.length) {
    // callInsert 函数 会等到执行时, 才会依次调用每个指令的inserted方法
    const callInsert = () => {
      for (let i = 0; i < dirsWithInsert.length; i++) {
        callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
      }
    }
    // 如果新 创建的 元素
    if (isCreate) {
      // mergeVNodeHook 可以将 一个钩子函数 与 虚拟节点 现有的 钩子函数合并。
      // 这样 钩子函数的执行 推迟到被绑定的 元素插入 到父节点之后进行
      mergeVNodeHook(vnode, 'insert', callInsert)
    } else {
      // 如果不是新的元素, 直接执行 即可
      callInsert()
    }
  }

  // componentUpdated 也需要将指令推迟到 指令所在组件的Vnode及其子Vnode全部更新之后调用
  if (dirsWithPostpatch.length) {
    // 虚拟DOM更新前 会触发 prepatch钩子函数
    // 虚拟DOM更新中 会触发 update钩子函数
    // 虚拟DOM更新后 会触发 postpatch钩子函数
    mergeVNodeHook(vnode, 'postpatch', () => {
      for (let i = 0; i < dirsWithPostpatch.length; i++) {
        callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
      }
    })
  }

  // 如果是新创建,是不需要解绑的。
  if (!isCreate) {
    // 旧的存在,新的不存在。那么调用callHook 执行upbind方法即可
    for (key in oldDirs) {
      if (!newDirs[key]) {
        // no longer present, unbind
        callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
      }
    }
  }
}

相关文章

  • vue2-指令

    指令是vue提供的带有 v-前缀的特殊特性。指令的职责是,当表达式的值变化时,将其产生的连带影响响应式的作用于DO...

  • AAA前端-目录

    记录自己在简书上发表文章链接,避免每次要翻页很久查找; vue: vue2-部分优化与理解[https://www...

  • vue2-优化器

    在vue2-AST抽象语法树 https://www.jianshu.com/p/d7822b42a3e3[htt...

  • vue2-微信分享

    一、首先引入sdk: npm install weixin-js-sdk --save-dev 二、index.h...

  • vue2-父子组件通信

    父组件到子组件通讯 父组件到子组件的通讯主要为:子组件接受使用父组件的数据,这里的数据包括属性和方法(String...

  • vue2-过滤器

    过滤器的原理 {{message | capitalize}}这个过滤器会被模板编译成下面的样子_s(_f("ca...

  • MIPS指令集与简要分析

    R格式指令 基本格式 指令 算数类指令 逻辑类指令 位移类指令 跳转指令 I格式指令 基本格式 指令 算数指令 逻...

  • 指令指令

    /tellraw @a {"rawtext":[{"text":"你的名字 获得了成就 §a[你要的成就]"}]}...

  • Linux——DAY2进阶指令

    1、df 指令 2、free指令 3、head指令 4、tail指令 5、less指令 6、wc指令 7、date...

  • Java Web开发学习中2.(JSP指令元素)

    JSP指令元素: page指令, include指令,taglib指令. 一. page指令: 用来设定JSP页面...

网友评论

    本文标题:vue2-指令

    本文链接:https://www.haomeiwen.com/subject/dzqasltx.html