Skip to content

Vue2.5源码学习之模板解析 #2

@AlvinYuXT

Description

@AlvinYuXT

Vue模板解析

Html文件example

<div id="app">
  <div v-if="show">v-if</div>
  <div v-text="msg"></div>
</div>

处理过程

  1. 循环遍历HTML
  2. 遇到标签tag的起始,调用start方法,设置根节点root=elementcurrentElement=element将currentElement压入stack
  3. 遇到<div v-if="show">的时候,当前有currentElement了,currentElement.children = element,然后currentElement = element,将currentElement压入stack
  4. 遇到文字“v-if”,调用chars方法将对象化的文字{type: 3, text: "v-if"}挂载到currentElement.children上
  5. 遇到</div>的结束tag,调用end方法,将currentElement从stack中pop出来,这里还有一步优化操作就是在pop之前判断一下是不是' '如果是的话会吧currentElement.childrenpop出去
  6. 重复2、3、4、5
  7. 调用generate方法生成render方法。
  8. 初始化Watcher
  9. 调用this.get,触getter
  10. _update -> _render(内部调用生成的render生成VNode)
  11. 调用渲染 具体是__patch__
  12. 逐级生成DOM

处理结果

  1. ast解析结果如下图:

image.png | left | 683x375

  1. v-if&文字节点结果如下图:

image.png | left | 676x257

  1. v-text结果如下图:

image.png | left | 703x256

  1. render方法返回
code.render = "with(this){return _c('div',{attrs:{"id":"app"}},[(show)?_c('div',[_v("v-if")]):_e(),_v(" "),_c('div',{domProps:{"textContent":_s(msg)}})])}"

这个_c函数就是在initRender中赋值的createElement方法

代码解析

parseHtml源码

while (html) {
    last = html
    // Make sure we're not in a plaintext content element like script/style
    if (!lastTag || !isPlainTextElement(lastTag)) {
      let textEnd = html.indexOf('<')
      if (textEnd === 0) {
        // End tag:</div>这种
        const endTagMatch = html.match(endTag)
        if (endTagMatch) { //如果是end tag就吧html中这个标签删掉得到新的html
          const curIndex = index
          advance(endTagMatch[0].length)
          parseEndTag(endTagMatch[1], curIndex, index) // 这里会把stack中的对应标签的element pop出来
          continue
        }

        // Start tag: 比如<div id="app">
        const startTagMatch = parseStartTag()
        if (startTagMatch) {
          // 处理第一行的tag 会将当前tag的element push到stack中
          handleStartTag(startTagMatch)
          if (shouldIgnoreFirstNewline(lastTag, html)) {
            advance(1)
          }
          continue
        }
      }

      let text, rest, next
      if (textEnd >= 0) { //处理文字内容的
        rest = html.slice(textEnd)
        while ( //处理空的tag
          !endTag.test(rest) &&
          !startTagOpen.test(rest) &&
          !comment.test(rest) &&
          !conditionalComment.test(rest)
        ) {
          // < in plain text, be forgiving and treat it as text
          next = rest.indexOf('<', 1)
          if (next < 0) break
          textEnd += next
          rest = html.slice(textEnd)
        }
        text = html.substring(0, textEnd) // 获得真是的text  比如<div>test</div>中的test
        advance(textEnd)
      }

      if (textEnd < 0) {
        text = html
        html = ''
      }

      if (options.chars && text) {
        // 处理文字的函数,这里会将text内容生成对象 {type:3,text: 'test' } 赋值给element.children
        options.chars(text)
      }
    } else {
      // 处理<script>等标签
    }
    // 处理最后可能存在的文字
    if (html === last) {
      options.chars && options.chars(html)
      if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) {
        options.warn(`Mal-formatted tag at end of template: "${html}"`)
      }
      break
    }

parseStartTag

  // 正则表达将start tag中的各种属性匹配出来
  function parseStartTag () {
    const start = html.match(startTagOpen)
    if (start) {
      const match = {
        tagName: start[1],
        attrs: [],
        start: index
      }
      advance(start[0].length)
      let end, attr
      // 判断条件 不是'>'并且能够匹配出xx=xx 的形式
      while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
        advance(attr[0].length)
        match.attrs.push(attr)
      }
      if (end) {
        match.unarySlash = end[1]
        advance(end[0].length)
        match.end = index
        return match
      }
    }
  }
function handleStartTag (match) {
    const tagName = match.tagName
    const unarySlash = match.unarySlash

    const l = match.attrs.length
    const attrs = new Array(l)
    // attrs是一个二维数组
    for (let i = 0; i < l; i++) {
      const args = match.attrs[i] // 匹配结果是match操作的结果
      const value = args[3] || args[4] || args[5] || ''
      attrs[i] = {
        name: args[1],
        value: decodeAttr(
          value,
          options.shouldDecodeNewlines
        )
      }
    }

    if (!unary) {
      stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs })
      lastTag = tagName
    }

    if (options.start) {
      // 调用start方法,start方法主要是生成ast数据结构
      options.start(tagName, attrs, unary, match.start, match.end)
    }
  }

流程图

image.png | left | 827x1165

参考文章

Vue2.0 源码阅读:模板渲染
Vue源码分析(7)--实例分析v-if

TO DO

接下来就是调用mount方法,然后进行new Watcher等数据依赖收集。

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions