对于指令(directive)

属性绑定、事件绑定和v-modal底层都是通过指令(directive)实现的,那么什么是指令呢?咱们一起看看Directive的定义吧。

//文件 ./src/directives/index.tsexport interface Directive<T = Element> {  (ctx: DirectiveContext<T>): (() => void) | void}

指令(directive)其实就是一个承受参数类型为DirectiveContext并且返回cleanup
函数或啥都不返回的函数。那么DirectiveContext有是如何的呢?

//文件 ./src/directives/index.tsexport interface DirectiveContext<T = Element> {  el: T  get: (exp?: string) => any // 获取表达式字符串运算后的后果  effect: typeof rawEffect // 用于增加副作用函数  exp: string // 表达式字符串  arg?: string // v-bind:value或:value中的value, v-on:click或@click中的click  modifiers?: Record<string, true> // @click.prevent中的prevent  ctx: Context}

深刻v-bind的工作原理

walk办法在解析模板时会遍历元素的个性汇合el.attributes,当属性名称name匹配v-bind:时,则调用processDirective(el, 'v-bind', value, ctx)对属性名称进行解决并转发到对应的指令函数并执行。

//文件 ./src/walk.ts// 为便于浏览,我将与v-bind无关的代码都删除了const processDirective = (  el: Element,  raw, string, // 属性名称  exp: string, // 属性值:表达式字符串  ctx: Context) => {  let dir: Directive  let arg: string | undefined  let modifiers: Record<string, true> | undefined // v-bind有且仅有一个modifier,那就是camel  if (raw[0] == ':') {    dir = bind    arg = raw.slice(1)  }  else {    const argIndex = raw.indexOf(':')    // 因为指令必须以`v-`结尾,因而dirName则是从第3个字符开始截取    const dirName = argIndex > 0 ? raw.slice(2, argIndex) : raw.slice(2)    // 优先获取内置指令,若查找失败则查找以后上下文的指令    dir = builtInDirectives[dirName] || ctx.dirs[dirName]    arg = argIndex > 0 ? raw.slice(argIndex) : undefined  }  if (dir) {    // 因为ref不是用于设置元素的属性,因而须要非凡解决    if (dir === bind && arg === 'ref') dir = ref    applyDirective(el, dir, exp, ctx, arg, modifiers)  }}

processDirective依据属性名称匹配相应的指令和抽取入参后,就会调用applyDirective来通过对应的指令执行操作。

//文件 ./src/walk.tsconst applyDirective = (  el: Node,  dir: Directive<any>,  exp: string,  ctx: Context,  arg?: string  modifiers?: Record<string, true>) => {  const get = (e = exp) => evaluate(ctx.scope, e, el)  // 指令执行后可能会返回cleanup函数用于执行资源开释操作,或什么都不返回  const cleanup = dir({    el,    get,    effect: ctx.effect,    ctx,    exp,    arg,    modifiers  })  if (cleanup) {    // 将cleanup函数增加到以后上下文,当上下文销毁时会执行指令的清理工作    ctx.cleanups.push(cleanup)  }}

当初咱们终于走到指令bind执行阶段了

//文件 ./src/directives/bind.ts// 只能通过个性的形式赋值的属性const forceAttrRE = /^(spellcheck|draggable|form|list|type)$/export const bind: Directive<Element & { _class?: string }> => ({  el,  get,  effect,  arg,  modifiers}) => {  let prevValue: any  if (arg === 'class') {    el._class = el.className  }  effect(() => {    let value = get()    if (arg) {      // 用于解决v-bind:style="{color:'#fff'}" 的状况      if (modifiers?.camel) {        arg = camelize(arg)      }      setProp(el, arg, value, prevValue)    }    else {      // 用于解决v-bind="{style:{color:'#fff'}, fontSize: '10px'}" 的状况      for (const key in value) {        setProp(el, key, value[key], prevValue && prevValue[key])      }      // 删除原视图存在,而以后渲染的新视图不存在的属性      for (const key in prevValue) {        if (!value || !(key in value)) {          setProp(el, key, null)        }      }    }    prevValue = value  })}const setProp = (  el: Element & {_class?: string},  key: string,  value: any,  prevValue?: any) => {  if (key === 'class') {    el.setAttribute(      'class',      normalizeClass(el._class ? [el._class, value] : value) || ''    )  }  else if (key === 'style') {    value = normalizeStyle(value)    const { style } = el as HTMLElement    if (!value) {      // 若`:style=""`则移除属性style      el.removeAttribute('style')    }    else if (isString(value)) {      if (value !== prevValue) style.cssText = value    }    else {      // value为对象的场景      for (const key in value) {        setStyle(style, key, value[key])      }      // 删除原视图存在,而以后渲染的新视图不存在的款式属性      if (prevValue && !isString(prevValue)) {        for (const key in prevValue) {          if (value[key] == null) {            setStyle(style, key, '')          }        }       }    }  }  else if (    !(el instanceof SVGElement) &&    key in el &&    !forceAttrRE.test(key)) {      // 设置DOM属性(属性类型能够是对象)      el[key] = value      // 留给`v-modal`应用的      if (key === 'value') {        el._value = value      }  } else {    // 设置DOM个性(个性值仅能为字符串类型)    /* 因为`<input v-modal type="checkbox">`元素的属性`value`仅能存储字符串,     * 通过`:true-value`和`:false-value`设置选中和未选中时对应的非字符串类型的值。     */    if (key === 'true-value') {      ;(el as any)._trueValue = value    }    else if (key === 'false-value') {      ;(el as any)._falseValue = value    }    else if (value != null) {      el.setAttribute(key, value)    }    else {      el.removeAttribute(key)    }  }}const importantRE = /\s*!important/const setStyle = (  style: CSSStyleDeclaration,  name: string,  val: string | string[]) => {  if (isArray(val)) {    val.forEach(v => setStyle(style, name, v))  }   else {    if (name.startsWith('--')) {      // 自定义属性      style.setProperty(name, val)    }    else {      if (importantRE.test(val)) {        // 带`!important`的属性        style.setProperty(          hyphenate(name),          val.replace(importantRE, ''),          'important'        )      }      else {        // 一般属性        style[name as any] = val      }    }  }}

总结

通过本文咱们当前不单能够应用v-bind:style绑定繁多属性,还用通过v-bind一次过绑定多个属性,尽管如同不太倡议这样做>_<

后续咱们会深刻了解v-on事件绑定的工作原理,敬请期待。