从 vue 源码看问题 —— vue 编译器的解析(三)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 从 vue 源码看问题 —— vue 编译器的解析

preTransformNode() 方法

文件位置:src\platforms\web\compiler\modules\model.js

这里涉及到了下面几个方法:

  • getBindingAttr
  • getAndRemoveAttr
  • processFor
  • addRawAttr
  • processElement
/*
* 处理存在 v-model 的 input 标签,但没处理 v-model 属性
* 分别处理了 input 为 checkbox、radio 和 其它的情况
* input 具体是哪种情况由 el.ifConditions 中的条件来判断
* <input v-mode="test" :type="checkbox || radio || other(比如 text)" />
* @param {*} el 
* @param {*} options 
* @returns branch0
*/
function preTransformNode (el: ASTElement, options: CompilerOptions) {
  // 属于 input 标签
  if (el.tag === 'input') {
    const map = el.attrsMap
    // 不存在 v-model 属性,直接结束
    if (!map['v-model']) {
      return
    }
    // 获取 :type 的值
    let typeBinding
    if (map[':type'] || map['v-bind:type']) {
      typeBinding = getBindingAttr(el, 'type')
    }
    if (!map.type && !typeBinding && map['v-bind']) {
      typeBinding = `(${map['v-bind']}).type`
    }
    // type 类型存在
    if (typeBinding) {
      // 获取 v-if 的值,比如: <input v-model="test" :type="checkbox" v-if="isShow" />
      const ifCondition = getAndRemoveAttr(el, 'v-if', true)
      // 得到 &&isShow
      const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : ``
      // 是否存在 v-else 属性,<input v-else />
      const hasElse = getAndRemoveAttr(el, 'v-else', true) != null
      // 获取 v-else-if 属性的值 <inpu v-else-if="isShow" />
      const elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true)
      /*
        克隆一个新的 el 对象,分别处理 input 为 chekbox、radio 或 其它的情况
        具体是哪种情况,通过 el.ifConditins 条件来判断
      */ 
      // 1. checkbox
      const branch0 = cloneASTElement(el)
      // process for on the main node
      /*
       <input v-for="item in arr" :key="item" />
       处理 v-for 表达式,得到:
        branch0.for = arr;
        branch0.alias = item;
      */
      processFor(branch0)
      // 在 branch0.attrsMap 和 branch0.attrsList 对象中添加 type 属性
      addRawAttr(branch0, 'type', 'checkbox')
      /* 
         分别处理元素节点:
          key、ref、插槽、自闭合的 slot 标签、动态组件、class、style、v-bind、v-on、
          其它指令和一些原生属性
      */   
      processElement(branch0, options)
      // 标记当前对象已经被处理过了
      branch0.processed = true // prevent it from double-processed
      // 得到 true&&isShow || false&&isShow,标记当前 input 是否为 checkbox
      branch0.if = `(${typeBinding})==='checkbox'` + ifConditionExtra
      // 在 branch0.ifConfitions 数组中放入 { exp, block } 对象
      addIfCondition(branch0, {
        exp: branch0.if,
        block: branch0
      })
      // 克隆一个新的 ast 对象
      // 2. add radio else-if condition
      const branch1 = cloneASTElement(el)
      // 获取 v-for 属性值
      getAndRemoveAttr(branch1, 'v-for', true)
      // 在 branch1.attrsMap 和 branch1.attrsList 对象中添加 type 属性
      addRawAttr(branch1, 'type', 'radio')
       /* 
         分别处理元素节点:
          key、ref、插槽、自闭合的 slot 标签、动态组件、class、style、v-bind、v-on、
          其它指令和一些原生属性
      */   
      processElement(branch1, options)
      addIfCondition(branch0, {
        exp: `(${typeBinding})==='radio'` + ifConditionExtra,
        block: branch1
      })
      // 3. other input 为其它的情况
      const branch2 = cloneASTElement(el)
      // 获取 v-for 属性
      getAndRemoveAttr(branch2, 'v-for', true)
      addRawAttr(branch2, ':type', typeBinding)  
      processElement(branch2, options)
      addIfCondition(branch0, {
        exp: ifCondition,
        block: branch2
      })
      // 给 branch0 设置 else 或 elseif 条件
      if (hasElse) {
        branch0.else = true
      } else if (elseIfCondition) {
        branch0.elseif = elseIfCondition
      }
      // 返回
      return branch0
    }
  }
}
复制代码

getBindingAttr、getAndRemoveAttr、addRawAttr 方法

文件位置:src\compiler\helpers.js

getBindingAttr

// 获取 el 对象上执行属性 name 的值 
export function getBindingAttr (
  el: ASTElement,
  name: string,
  getStatic?: boolean
): ?string {
   // 获取指定属性的值
  const dynamicValue =
    getAndRemoveAttr(el, ':' + name) ||
    getAndRemoveAttr(el, 'v-bind:' + name)
  if (dynamicValue != null) {
    return parseFilters(dynamicValue)
  } else if (getStatic !== false) {
    const staticValue = getAndRemoveAttr(el, name)
    if (staticValue != null) {
      return JSON.stringify(staticValue)
    }
  }
}
复制代码

getAndRemoveAttr

/**
  从 el.attrsList 中删除指定的属性 name
  如果 removeFromMap 为 true,则同样删除 el.attrsMap 对象中的该属性,
  比如 v-if、v-else-if、v-else 等属性就会被移除,
  不过一般不会删除该对象上的属性,因为从 ast 生成 代码期间还需要使用该对象,返回指定属性的值
 */
export function getAndRemoveAttr (
  el: ASTElement,
  name: string,
  removeFromMap?: boolean
): ?string {
  let val
  // 将执行属性 name 从 el.attrsList 中移除
  if ((val = el.attrsMap[name]) != null) {
    const list = el.attrsList
    for (let i = 0, l = list.length; i < l; i++) {
      if (list[i].name === name) {
        list.splice(i, 1)
        break
      }
    }
  }
  // 如果 removeFromMap 为 true,则从 el.attrsMap 中移除指定的属性 name
  // 不过一般不会移除 el.attsMap 中的数据,因为从 ast 生成 代码期间还需要使用该对象
  if (removeFromMap) {
    delete el.attrsMap[name]
  }
  // 返回执行属性的值
  return val
}
复制代码

addRawAttr

// 获取 el 对象上执行属性 name 的值 
export function getBindingAttr (
  el: ASTElement,
  name: string,
  getStatic?: boolean
): ?string {
   // 获取指定属性的值
  const dynamicValue =
    getAndRemoveAttr(el, ':' + name) ||
    getAndRemoveAttr(el, 'v-bind:' + name)
  if (dynamicValue != null) {
    return parseFilters(dynamicValue)
  } else if (getStatic !== false) {
    const staticValue = getAndRemoveAttr(el, name)
    if (staticValue != null) {
      return JSON.stringify(staticValue)
    }
  }
}
复制代码

processFor、processRef、processKey、processElement

文件位置:/src/compiler/parser/index.js

processFor

/**
 * 处理 v-for,将结果设置到 el 对象上,得到:
 *   el.for = 可迭代对象,比如 arr
 *   el.alias = 别名,比如 item
 * @param {*} el 元素的 ast 对象
 */
export function processFor (el: ASTElement) {
  let exp
  // 获取 el 上的 v-for 属性的值
  if ((exp = getAndRemoveAttr(el, 'v-for'))) {
    // 解析 v-for 的表达式,得到 { for: 可迭代对象, alias: 别名 }
    // 比如 { for: arr, alias: item }
    const res = parseFor(exp)
    if (res) {
      // 将 res 对象上的属性拷贝到 el 对象上
      extend(el, res)
    } else if (process.env.NODE_ENV !== 'production') {
      warn(
        `Invalid v-for expression: ${exp}`,
        el.rawAttrsMap['v-for']
      )
    }
  }
}
复制代码

processRef

/**
 * 处理元素上的 ref 属性
 *  el.ref = refVal
 *  el.refInFor = boolean
 */
function processRef (el) {
  const ref = getBindingAttr(el, 'ref')
  if (ref) {
    el.ref = ref
    // 判断包含 ref 属性的元素是否包含在具有 v-for 指令的元素内或后代元素中
    // 如果是,则 ref 指向的则是包含 DOM 节点或组件实例的数组
    el.refInFor = checkInFor(el)
  }
}
复制代码

processKey

// 处理元素上的 key 属性,设置 el.key = val
function processKey (el) {
  // 拿到 key 的属性值
  const exp = getBindingAttr(el, 'key')
  // 关于 key 使用上的异常处理
  if (exp) {
    // template 标签不允许设置 key
    if (process.env.NODE_ENV !== 'production') {
      if (el.tag === 'template') {
        warn(
          `<template> cannot be keyed. Place the key on real elements instead.`,
          getRawBindingAttr(el, 'key')
        )
      }
      // 不要在 <transition=group> 的子元素上使用 v-for 的 index 作为 key
      // 否则等价与没有使用 key
      if (el.for) {
        const iterator = el.iterator2 || el.iterator1
        const parent = el.parent
        if (iterator && iterator === exp && parent && parent.tag === 'transition-group') {
          warn(
            `Do not use v-for index as key on <transition-group> children, ` +
            `this is the same as not using keys.`,
            getRawBindingAttr(el, 'key'),
            true /* tip */
          )
        }
      }
    }
    // 设置 el.key = exp
    el.key = exp
  }
}
复制代码

processElement

/**
 * 分别处理元素节点的 key、ref、插槽、自闭合的 slot 标签、动态组件、class、style、v-bind、v-on、其它指令和一些原生属性 
 * 然后在 el 对象上添加如下属性:
 * el.key、ref、refInFor、scopedSlot、slotName、component、inlineTemplate、staticClass
 * el.bindingClass、staticStyle、bindingStyle、attrs
 * @param {*} element 被处理元素的 ast 对象
 * @param {*} options 配置项
 * @returns 
 */
export function processElement (
  element: ASTElement,
  options: CompilerOptions
) {
  processKey(element)
  // determine whether this is a plain element after
  // removing structural attributes
  // 确定 element 是否为一个普通元素
  element.plain = (
    !element.key &&
    !element.scopedSlots &&
    !element.attrsList.length
  )
  // el.ref = val, el.refInFor = boolean
  processRef(element)
  // 处理作为插槽传递给组件的内容,得到 插槽名称、是否为动态插槽、作用域插槽的值,
  // 以及插槽中的所有子元素,子元素放到插槽对象的 children 属性中
  processSlotContent(element)
  // 处理自闭合的 slot 标签,得到插槽名称 => el.slotName = xx
  processSlotOutlet(element)
  // 处理动态组件,<component :is="compoName"></component>得到 el.component = compName,
  // 以及标记是否存在内联模版,el.inlineTemplate = true of false
  processComponent(element)
  /*
   为 element 对象分别执行 class、style、model 模块中的 transformNode 方法
   不过 web 平台只有 class、style 模块有 transformNode 方法,分别用来处理 class 属性和 style 属性
   得到 el.staticStyle、 el.styleBinding、el.staticClass、el.classBinding
   分别存放静态 style 属性的值、动态 style 属性的值,以及静态 class 属性的值和动态 class 属性的值
  */
  for (let i = 0; i < transforms.length; i++) {
    element = transforms[i](element, options) || element
  }
  /**
   处理元素上的所有属性:
    1. v-bind 指令变成:el.attrs 或 el.dynamicAttrs = [{ name, value, start, end, dynamic }, ...],
    或者是必须使用 props 的属性,变成了 el.props = [{ name, value, start, end, dynamic }, ...]
    2. v-on 指令变成:el.events 或 el.nativeEvents = { name: [{ value, start, end, modifiers, dynamic }, ...] }
    3. 其它指令:el.directives = [{name, rawName, value, arg, isDynamicArg, modifier, start, end }, ...]
    4. 原生属性:el.attrs = [{ name, value, start, end }],或者一些必须使用 props 的属性,
    变成了:el.props = [{ name, value: true, start, end, dynamic }]
   */
  processAttrs(element)
  return element
}
复制代码

processSlotContent() 方法

文件位置:/src/compiler/parser/index.js

/*
 处理作为插槽传递给组件的内容,得到:
  slotTarget => 插槽名
  slotTargetDynamic => 是否为动态插槽
  slotScope => 作用域插槽的值
 直接在 <comp> 标签上使用 v-slot 语法时,将上述属性放到 el.scopedSlots 对象上,其它情况直接放到 el 对象上
 handle content being passed to a component as slot,
 e.g. <template slot="xxx">, <div slot-scope="xxx">
*/
function processSlotContent (el) {
  let slotScope
  if (el.tag === 'template') {
    /* 
     template 标签上使用 scope 属性的提示
     scope 已经弃用,并在 2.5 之后使用 slot-scope 代替
     slot-scope 即可以用在 template 标签也可以用在普通标签上
    */
    slotScope = getAndRemoveAttr(el, 'scope')
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && slotScope) {
      warn(
        `the "scope" attribute for scoped slots have been deprecated and ` +
        `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
        `can also be used on plain elements in addition to <template> to ` +
        `denote scoped slots.`,
        el.rawAttrsMap['scope'],
        true
      )
    }
    // el.slotScope = val
    el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
  } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {
      warn(
        `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +
        `(v-for takes higher priority). Use a wrapper <template> for the ` +
        `scoped slot to make it clearer.`,
        el.rawAttrsMap['slot-scope'],
        true
      )
    }
    el.slotScope = slotScope
  }
  // slot="xxx"
  // 获取 slot 属性的值
  // slot="xxx",旧的具名插槽的写法
  const slotTarget = getBindingAttr(el, 'slot')
  if (slotTarget) {
    // el.slotTarget = 插槽名(具名插槽)
    el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
    // 动态插槽名
    el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot'])
    // preserve slot as an attribute for native shadow DOM compat
    // only for non-scoped slots.
    if (el.tag !== 'template' && !el.slotScope) {
      addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))
    }
  }
  // 2.6 v-slot syntax
  if (process.env.NEW_SLOT_SYNTAX) {
    if (el.tag === 'template') {
      // v-slot on <template>
       // v-slot 在 tempalte 标签上,得到 v-slot 的值
      // v-slot on <template>
      const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
      if (slotBinding) {
        if (process.env.NODE_ENV !== 'production') {
          if (el.slotTarget || el.slotScope) {
             // 不同插槽语法禁止混合使用
            warn(
              `Unexpected mixed usage of different slot syntaxes.`,
              el
            )
          }
          if (el.parent && !maybeComponent(el.parent)) {
             /* 
             <template v-slot> 只能出现在组件的根位置,比如:
              <comp>
                <template v-slot>xx</template>
              </comp>
              而不能是
              <comp>
                <div>
                  <template v-slot>xxx</template>
                </div>
              </comp>
             */
            warn(
              `<template v-slot> can only appear at the root level inside ` +
              `the receiving component`,
              el
            )
          }
        }
        // 得到插槽名称
        const { name, dynamic } = getSlotName(slotBinding)
        // 将插槽名称保存到 el.slotTarget 上
        el.slotTarget = name
        // 是否为动态插槽
        el.slotTargetDynamic = dynamic
        // 作用域插槽的值
        el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf
      }
    } else {
       /* 
        处理组件上的 v-slot,<comp v-slot:header />
        slotBinding = { name: "v-slot:header", value: "", start, end}
        v-slot on component, denotes default slot
       */
      const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
      if (slotBinding) {
        if (process.env.NODE_ENV !== 'production') {
          // el 不是组件的话,提示,v-slot 只能出现在组件上或 template 标签上
          if (!maybeComponent(el)) {
            warn(
              `v-slot can only be used on components or <template>.`,
              slotBinding
            )
          }
          // 语法混用
          if (el.slotScope || el.slotTarget) {
            warn(
              `Unexpected mixed usage of different slot syntaxes.`,
              el
            )
          }
          // 为了避免作用域歧义,当存在其他命名槽时,默认槽也应该使用<template>语法
          if (el.scopedSlots) {
            warn(
              `To avoid scope ambiguity, the default slot should also use ` +
              `<template> syntax when there are other named slots.`,
              slotBinding
            )
          }
        }
        // 将组件的孩子添加到它的默认插槽内
        // add the component's children to its default slot
        const slots = el.scopedSlots || (el.scopedSlots = {})
        // 获取插槽名称以及是否为动态插槽
        const { name, dynamic } = getSlotName(slotBinding)
        // 创建一个 template 标签的 ast 对象,用于容纳插槽内容,父级是 el
        const slotContainer = slots[name] = createASTElement('template', [], el)
        // 插槽名
        slotContainer.slotTarget = name
        // 是否为动态插槽
        slotContainer.slotTargetDynamic = dynamic
        // 所有的孩子,将每一个孩子的 parent 属性都设置为 slotContainer
        slotContainer.children = el.children.filter((c: any) => {
          if (!c.slotScope) {
            // 给插槽内元素设置 parent 属性为 slotContainer,也就是 template 元素
            c.parent = slotContainer
            return true
          }
        })
        slotContainer.slotScope = slotBinding.value || emptySlotScopeToken
        // remove children as they are returned from scopedSlots now
        el.children = []
        // mark el non-plain so data gets generated
        el.plain = false
      }
    }
  }
}


目录
相关文章
|
2月前
|
监控 网络协议 Java
Tomcat源码解析】整体架构组成及核心组件
Tomcat,原名Catalina,是一款优雅轻盈的Web服务器,自4.x版本起扩展了JSP、EL等功能,超越了单纯的Servlet容器范畴。Servlet是Sun公司为Java编程Web应用制定的规范,Tomcat作为Servlet容器,负责构建Request与Response对象,并执行业务逻辑。
Tomcat源码解析】整体架构组成及核心组件
|
19天前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
23天前
|
开发工具
Flutter-AnimatedWidget组件源码解析
Flutter-AnimatedWidget组件源码解析
|
19天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
174 37
|
11天前
|
编解码 开发工具 UED
QT Widgets模块源码解析与实践
【9月更文挑战第20天】Qt Widgets 模块是 Qt 开发中至关重要的部分,提供了丰富的 GUI 组件,如按钮、文本框等,并支持布局管理、事件处理和窗口管理。这些组件基于信号与槽机制,实现灵活交互。通过对源码的解析及实践应用,可深入了解其类结构、布局管理和事件处理机制,掌握创建复杂 UI 界面的方法,提升开发效率和用户体验。
55 12
|
7天前
|
JavaScript 前端开发 UED
Javaweb中Vue指令的详细解析与应用
Vue指令是Vue框架中非常强大的特性之一,它提供了一种简洁、高效的方式来增强HTML元素和组件的功能。通过合理使用这些指令,可以使你的JavaWeb应用更加响应用户的操作,提高交互性和用户体验。而且,通过创建自定义指令,你可以进一步扩展Vue的功能,使其更贴合你的应用需求。
10 1
|
2月前
|
设计模式 JavaScript 前端开发
Vue响应式原理全解析
Vue的响应式系统是其核心特性之一,它使得Vue能够以高效的方式响应数据的变化。通过对对象属性的getter和setter进行劫持,Vue实现了对数据变化的侦测和依赖收集,当数据变化时能够自动派发更新。Vue3中,响应式系统得到了进一步的加强和优化,使用Proxy替代了 `Object.defineProperty`,带来了更好的性能和更强大的拦截能力。理解Vue的响应式原理,对于深入理解Vue的工作机制和进行高效的Vue开发都具有重要意义。
36 1
|
2月前
|
测试技术 Python
python自动化测试中装饰器@ddt与@data源码深入解析
综上所述,使用 `@ddt`和 `@data`可以大大简化写作测试用例的过程,让我们能专注于测试逻辑的本身,而无需编写重复的测试方法。通过讲解了 `@ddt`和 `@data`源码的关键部分,我们可以更深入地理解其背后的工作原理。
30 1
|
2月前
|
存储 NoSQL Redis
redis 6源码解析之 object
redis 6源码解析之 object
56 6
|
2月前
|
开发者 Python
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
72 1

热门文章

最新文章

推荐镜像

更多
下一篇
无影云桌面