vue.js学习四:模板编译

97 阅读2分钟

渲染函数是创建HTML最原始的方法, 模板(可以理解为字符串)最终会通过编译转换成渲染函数, 执行后得到一份vnode用于渲染DOM渲染,其实模板编译是配合虚拟DOM进行渲染的。

关于 Vue 编译原理这块的整体逻辑主要分三个部分,也可以说是分三步,这三个部分是有前后关系的:

  • 第一步是将 模板字符串 转换成 element ASTs(解析器)

  • 第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)

  • 第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)

解析器: 将模板字符串解析成AST

<div>
  <p>{{age}}</p>
</div>

解析成如下的AST结构

{
  tag: "div"
  type: 1,
  staticRoot: false,
  static: false,
  plain: true,
  parent: undefined,
  attrsList: [],
  attrsMap: {},
  children: [
      {
      tag: "p"
      type: 1,
      staticRoot: false,
      static: false,
      plain: true,
      parent: {tag: "div", ...},
      attrsList: [],
      attrsMap: {},
      children: [{
          type: 2,
          text: "{{age}}",
          static: false,
          expression: "_s(age)"
      }]
    }
  ]
}

该过程是通过正则匹配进行截取解析

正则解析过程如下:

const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
const startTagClose = /^\s*(\/?)>/
let html = `<div></div>`
let index = 0
const start = html.match(startTagOpen)

const match = {
  tagName: start[1],
  attrs: [],
  start: 0
}
html = html.substring(start[0].length)
index += start[0].length
let end, attr
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
  html = html.substring(attr[0].length)
  index += attr[0].length
  match.attrs.push(attr)
}
if (end) {
  match.unarySlash = end[1]
  html = html.substring(end[0].length)
  index += end[0].length
  match.end = index
}
console.log(match)

优化器: 遍历AST,检出所有静态子树

静态节点无论怎样变都不需要重新渲染,当AST中的静态子树被打上静态标签后,每次重新渲染时,不需要新建虚拟节点,直接克隆已存在的虚拟节点即可

代码生成器:将AST转换成渲染函数中的内容,这个内容可以称为“代码字符串”

最后生成的render函数:

{
  render: `with(this){return _c('div',[_c('p',[_v(_s(name))])])}`
}


with(this){
  return _c(
    'div',
    [
      _c(
        'p',
        [
          _v(_s(name))
        ]
      )
    ]
  )
}

其中_c是createElement

_v是创建文本节点

_s是返回参数中的字符串