本篇主要讲解,vue文本的解析。话不多说,先上源码:

//    源码位于 src/compiler/parser/text-parsre.js
const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g
const buildRegex = cached(delimiters => {
  const open = delimiters[0].replace(regexEscapeRE, '\\$&')
  const close = delimiters[1].replace(regexEscapeRE, '\\$&')
  return new RegExp(open + '((?:.|\\n)+?)' + close, 'g')
})
export function parseText (text,delimiters) {
  const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE
  if (!tagRE.test(text)) {
    return
  }
  const tokens = []
  const rawTokens = []
  /**
   * let lastIndex = tagRE.lastIndex = 0
   * 上面这行代码等同于下面这两行代码:
   * tagRE.lastIndex = 0
   * let lastIndex = tagRE.lastIndex
   */
  let lastIndex = tagRE.lastIndex = 0
  let match, index, tokenValue
  while ((match = tagRE.exec(text))) {
    index = match.index
    // push text token
    if (index > lastIndex) {
      // 先把'{{'前面的文本放入tokens中
      rawTokens.push(tokenValue = text.slice(lastIndex, index))
      tokens.push(JSON.stringify(tokenValue))
    }
    // tag token
    // 取出'{{ }}'中间的变量exp
    const exp = parseFilters(match[1].trim())
    // 把变量exp改成_s(exp)形式也放入tokens中
    tokens.push(`_s(${exp})`)
    rawTokens.push({ '@binding': exp })
    // 设置lastIndex 以保证下一轮循环时,只从'}}'后面再开始匹配正则
    lastIndex = index + match[0].length
  }
  // 当剩下的text不再被正则匹配上时,表示所有变量已经处理完毕
  // 此时如果lastIndex < text.length,表示在最后一个变量后面还有文本
  // 最后将后面的文本再加入到tokens中
  if (lastIndex < text.length) {
    rawTokens.push(tokenValue = text.slice(lastIndex))
    tokens.push(JSON.stringify(tokenValue))
  }

  // 最后把数组tokens中的所有元素用'+'拼接起来
  return {
    expression: tokens.join('+'),
    tokens: rawTokens
  }
}

创建来tagRE变量用来检查变量是动态还是文本,其原理主要是用来检测是否有{{}},从而判断是否是动态文本。如果包不包含就直接return,如果包含就继续往下创建变量。开启while循环,首先取得字符串中第一个变量在字符串中的起始位置赋给index,然后比较indexlastIndex的大小,此时你可能有疑问了,这个lastIndex是什么呢?在上面定义变量中,定义了let lastIndex = tagRE.lastIndex = 0,所以lastIndex就是tagRE.lastIndex,而tagRE.lastIndex又是什么呢?当调用exec( )的正则表达式对象具有修饰符g时,它将把当前正则表达式对象的lastIndex属性设置为紧挨着匹配子串的字符位置,当同一个正则表达式第二次调用exec( ),它会将从lastIndex属性所指示的字符串处开始检索,如果exec( )没有发现任何匹配结果,它会将lastIndex重置为0。index>lastIndex时,表示变量前面有纯文本,那么就把这段纯文本截取出来,存入rawTokens中,同时再调用JSON.stringify给这段文本包裹上双引号,存入tokens中。

如果index不大于lastIndex,那说明index也为0,即该文本一开始就是变量,例如:hello。那么此时变量前面没有纯文本,那就不用截取,直接取出匹配结果的第一个元素变量名,将其用_s()包裹存入tokens中,同时再把变量名构造成{'@binding': exp}存入rawTokens中。

while循环完毕时,表明文本中所有变量已经被解析完毕,如果此时lastIndex < text.length,那就说明最后一个变量的后面还有纯文本,那就将其再存入tokensrawTokens中。

这就是文本解析的原理。

vue也同样做了优化,就是vue区分了静态节点,和动态节点。这样未来在patch的过程中就可以直接跳过静态节点。

所有的节点都解析完毕之后,生成了AST树之后,是如何生成render函数的呢?

假如,我的template的模版是这样的:

<div id="NLRX"><p>Hello {{name}}</p></div>

那么模版编译后的结果应该是:

ast = {
    'type': 1,
    'tag': 'div',
    'attrsList': [
        {
            'name':'id',
            'value':'NLRX',
        }
    ],
    'attrsMap': {
      'id': 'NLRX',
    },
    'static':false,
    'parent': undefined,
    'plain': false,
    'children': [{
      'type': 1,
      'tag': 'p',
      'plain': false,
      'static':false,
      'children': [
        {
            'type': 2,
            'expression': '"Hello "+_s(name)',
            'text': 'Hello {{name}}',
            'static':false,
        }
      ]
    }]
  }

是的,就是这个鬼样子。

所以,我们需要对这个AST树进行递归遍历。

export function generate (ast,option) {
  const state = new CodegenState(options)
  const code = ast ? genElement(ast, state) : '_c("div")'
  return {
    render: `with(this){return ${code}}`,
    staticRenderFns: state.staticRenderFns
  }
}

调用generate函数并传入优化后得到的ast。generate内部会判断如果ast不为空,那么就调用genElement创建虚拟dom节点。

export function genElement (el: ASTElement, state: CodegenState): string {
  if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
    return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {
    return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {
    return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget) {
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    return genSlot(el, state)
  } else {
    // component or element
    let code
    if (el.component) {
      code = genComponent(el.component, el, state)
    } else {
      const data = el.plain ? undefined : genData(el, state)

      const children = el.inlineTemplate ? null : genChildren(el, state, true)
      code = `_c('${el.tag}'${
        data ? `,${data}` : '' // data
      }${
        children ? `,${children}` : '' // children
      })`
    }
    // module transforms
    for (let i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code)
    }
    return code
  }
}

通过以上的方法,生成不同类型的,文本节点,标签节点,注释节点。从而生成了render函数,这样在vue挂载阶段,就可以生成vNode.

以上就是vue模版解析的大致过程。初略分析,还需大师指点!!

Logo

前往低代码交流专区

更多推荐