Vue 模板编译

Vue 模板编译

总体概述

  • 获取 template:
    • 首先要拿到 Vue 构造函数中 $options 参数中的 el,判断有没有 el,如果有再进行下一步操作
    • 然后在 Vue 的原型上添加 $mount 方法用于渲染,在这个方法中,需要做如下操作:
      • 获取 $options,并在 Vue 上挂载 $el
      • 判断 $options 中有无 render 函数(目前不考虑有 render 函数的情况),有的话就使用 render 函数;然后判断有无 template,有的话就使用 template;最后判断在 body 标签中有无 el 所对应的元素节点,如果有就把该元素的 outerHTML 赋值给 template
  • template 转 AST(Abstract Syntax Tree,抽象语法树) 树:
    • 一边匹配一边删除,全部处理完就成为空字符串
    • 用 parseStartTag 处理开始标签获取属性,处理完后用 start 函数
    • 遇到文本节点用 chars 函数处理
    • 遇到结束标签用 end 函数处理
  • AST 树转 render 函数(_c、_v、_s):
    • 获取元素的子元素并将其全部转化为字符串【递归处理】
    • 将属性转为字符串
    • 最后将元素节点进行字符串拼接
  • render 函数转虚拟节点:
    • 使用with函数将this指向vm,然后调用函数产生虚拟节点(vnode)
    • 其中需要在原型上添加上 _c、_v、_s 方法
  • 设置 PATCH,打补丁到真实 DOM
    • 利用递归将虚拟DOM转化为真实DOM,然后替换 el
    • 转化的时候需要将各自的属性添加至节点上

1. 模板转 AST 树

函数名:parseHTMLToAST(html)

1.1 实现思路

  • 正则匹配 template 模板中的标签、属性和文本节点,正则匹配的所有功能如下,详情可查看 Vue 源码
// 匹配id="app"/id='app'/id=app
const attribute =
  /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`
const qnameCapture = `((?:${
     ncname}\\:)?${
     ncname})`
// 匹配开始标签的起始部分,如:<div
const startTagOpen = new RegExp(`^<${
     qnameCapture}`)
// 匹配开始标签的结束部分,如:>、/>
const startTagClose = /^\s*(\/?)>/
// 匹配结束标签,如:</div>
const endTag = new RegExp(`^<\\/${
     qnameCapture}[^>]*>`)
  • 将 template 中匹配一部分删除一部分(advance 函数),匹配开始标签的流程如下:

    • 函数名:parseStartTag()
    • 首先 template 字符串中判断 < 的下标是为为 0,如果为 0 则代表即将匹配标签的开始标签(大于 0 的情况下面会说)或者为结束标签,则匹配结束标签(endTag),放入 end 函数中处理
    • 然后匹配开始标签中的标签名(startTagOpen)、属性名和属性值(attribute),在匹配属性的时候要一直判读是否匹配到开始标签的结束部分,如果没有匹配到那就一直匹配属性
    • 最后如果匹配到开始标签的结束项 > 或者 /> (startTagClose),这样匹配结束,返回 match 对象,结构如下:
    match = {
         
      tagName: 'xxx',
      attrs: [
        {
         
          name: 'aaa',
          value: '......'
        }
      ]
      // attrs 中的 value 是字符串类型,不存在对象类型,如果匹配到像 style 	属性,最后转成 AST 树后 attrs 中的 value 是对象类型,处理在 render 		函数中将字符串转为对象形式
    }
    
    • 将返回的对象放入 start 函数中处理
  • 如果大于 0,则代表匹配文本内容,那么从开始到 < 前到内容都为文本,将其截取后放入 chars 函数中处理

  • 最后生成 AST 树,大致结构如下:

{
   
  tag: 'div',
  type: 1, // 代表元素节点
  attrs: [
    {
   
      name: 'id',
      value: 'app'
    },
    {
   
      name: 'style',
      value: {
   
        color: 'red',
        fontSize: '20px'
      }
    }
  ],
  children: [
    // 子元素
    ......
  ]
}

1.2 start 函数

  • 利用 createASTElement 函数创建 AST 元素,即返回如下对象:
{
   
  tag: tagName,
  type: 1,
  children: [],
  attrs 
}
  • 利用栈存储节点,每次匹配到开始标签的标签名,就将标签名入栈
  • 然后当前的父节点就是此元素
function start(tagName, attrs) {
   
  console.log('---------开始---------');
  console.log(tagName, attrs);
  const element = createElement(tagName, 1, attrs);
  !root && (root = element);
  currentParent = element;
  stack.push(element)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值