在vue的模板渲染中插值表达式是如何转为实际值

本文主要在于说明其中的实现思想,如果想看实际代码实现可至文末查阅。

简单来说,最基本的实现从插值表达式到变量值这一过程只要两步就行

  1. 模板解析: Vue 的编译器会将模板字符串解析成抽象语法树(AST),表示模板的结构。
  2. 虚拟 DOM 生成: 将 AST 转换为可执行的渲染函数。渲染函数是一个 JavaScript 函数,它接收数据作为参数,并返回一个虚拟 DOM(VNode)树,描述了如何渲染数据到实际的 DOM。

这两步就可以实现基本的插值表达式转实际值,但是在vue的完整的渲染流程中,还有其它的流程,如AST 优化、虚拟 DOM 比对、DOM更新等等. 这些都是优化dom更新的策略,本文重心不在这里,所以不说。

模板解析

写插值表达式都是在html代码中,以{{ }}来表明。那么需要解析插值表达式内的值就必须要操作整个被vue接管的html或者说模板内的内容。可以说是将模板字符串逐字分析

设定一个基础的可以表示插值表达式的模板

html 复制代码
<div id="app" style="color: rgb(45, 98, 245); font-size: 18px">
    现在的毫秒时是 {{ dateTime }}
    <p class="p">今天周 {{ Week }}</p>
</div>

这一段html代码由几个东西组成,标签元素、元素属性、文本、插值表达式。 记住这几个,他们将是抽象语法树(AST)的重要组成部分。

抽象语法树(AST)

在模板解析中,AST就是一个用于表示html代码的结构的树形对象,它用一组数据记录每一层元素的各种信息及与它本身的父子元素关系。

基本结构

这是一个在模板渲染中生成AST得最基本得结构。插值表达式在这个结构得text属性值中。

js 复制代码
{
    attr: '元素属性',
    tag: '标签名',
    type: '节点类型'
    text: '文本节点',
    children: '子元素',
    parent: '父元素'
}

将上面设定得html代码解析为AST语法树大致是下面这个样子。插值表达式存在于text属性中。此时并没有去解析。

json 复制代码
{
  "tag": "div",
  "attrs": [
    {
      "name": "id",
      "value": "app"
    },
    {
      "name": "style",
      "value": "color: rgb(45, 98, 245); font-size: 18px"
    }
  ],
  "children": [
    {
      "type": 3,
      "text": "现在的毫秒时是{{dateTime}}"
    },
    {
      "tag": "p",
      "attrs": [
        {
          "name": "class",
          "value": "p"
        }
      ],
      "children": [
        {
          "type": 3,
          "text": "今天周{{Week}}"
        }
      ],
      "type": 1,
      "parent": "div"
    }
  ],
  "type": 1,
  "parent": null
}

如何解析成AST的

上面说到,模板字符串转为AST的过程是一个将字符串逐字分析的过程。虽然有点儿夸张,但也贴切。

vue准备了很多的正则语法来匹配对应的词,来匹配不同的部分,确定这部分内容属于什么。

js 复制代码
// 标签属性
var attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;

// 标签名称
var ncname = "[a-zA-Z_][\\-\\.0-9_a-zA-Z" + (unicodeRegExp.source) + "]*";

// <span:xx>
var qnameCapture = "((?:" + ncname + "\\:)?" + ncname + ")";

// 开始标签 获取标签名 <
var startTagOpen = new RegExp(("^<" + qnameCapture));

// 匹配开始标签的结束 >
var startTagClose = /^\s*(\/?)>/;

// 匹配结束标签 </div>
var endTag = new RegExp(("^<\\/" + qnameCapture + "[^>]*>"));

利用这些正则表达式的流程我用一张图来表示,看图比文字说明更易懂。

抽象语法树的建立是虚拟 DOM 生成的必要前提,虚拟dom的建立才是解析插值表达式的地方。

虚拟 DOM 生成

在得到了AST语法树之后,首先要做的是生成渲染函数,在执行这个渲染函数的同时就会取解析插值表达式的值。 生成渲染函数 vue在生成渲染函数时定义了三个函数_c、_v、_s,分别代表标签、文本、变量 ,最后将靠这些函数来生成虚拟dom。

渲染函数需要接受一个可执行的字符串,这个样子(是一个长字符串)。

js 复制代码
_c('div',{id:"app",style:{"color":"rgb(45, 98, 245)","font-size":"18px"}},_v("现在的毫秒时是"+_s(dateTime)),_c('p',{class:"p"},_v("今天周"+_s(Week))))

转化步骤:

  • 元素节点:使用_c第一个参数为标签名,第二个为属性,第三个为文本和_s的组合,第四个递归子元素。
  • 文本节点:直接放在_c的第三个参数
  • 变量:使用_s包裹与文本放在一起

将渲染函数可接受的字符串生成了之后,需要先提出一个函数with。虽然在MDN上表明此方法已被弃用,但是vue2是使用这个方法,作为学习了解不用关心。

with函数

用于创建一个临时的作用域,以便在其中访问特定对象的属性,从而可以在代码中更简洁地引用这些属性。也就是在with块作用域内可以直接访问传入的参数的属性,类似解构的作用。

js 复制代码
with({name: 1, age: 2}){ console.log(name, age); // 1  2 }

渲染函数完整示例

js 复制代码
new Function(`with(this){ return _c('div',{id:"app",style:{"color":"rgb(45, 98, 245)","font-size":"18px"}},_v("现在的毫秒时是"+_s(dateTime)),_c('p',{class:"p"},_v("今天周"+_s(Week)))) }`);

with的this就是当前vue实例,变量在渲染函数执行的过程中解析为实际的值。渲染函数执行完返回的就是虚拟dom。得到虚拟dom就可以把解析完插值表达式的模板渲染到页面上。渲染就是根据虚拟dom的结构(类似AST的结构)创建元素的过程。

相关链接

  • 想了解本文的代码实现的可以看这里
  • vue2模板解析AST源码
  • vue2生成渲染函数源码
相关推荐
m0_7400437318 分钟前
3、Vuex-Axios-Element UI
前端·javascript·vue.js
鹏北海32 分钟前
微信扫码登录 iframe 方案中的状态拦截陷阱
前端·javascript·vue.js
狗哥哥33 分钟前
Vite 插件实战 v2:让 keep-alive 的“组件名”自动长出来
前端·vue.js·架构
小黑的铁粉33 分钟前
Vue2 vs Vue3
vue.js
AAA阿giao34 分钟前
代码宇宙的精密蓝图:深入探索 Vue 3 + Vite 项目的灵魂结构
前端·javascript·vue.js
半桶水专家1 小时前
vue中的props详解
前端·javascript·vue.js
前端不太难1 小时前
RN 遇到复杂手势(缩放、拖拽、旋转)时怎么设计架构
javascript·vue.js·架构
白兰地空瓶1 小时前
一行 npm init vite,前端工程化的世界就此展开
前端·vue.js·vite
码力巨能编1 小时前
Markdown 作为 Vue 组件导入
前端·javascript·vue.js
仰望.2 小时前
vue 甘特图 vxe-gantt table 拖拽任务调整开始日期和结束日期的使用,拖拽任务调整日期
vue.js·甘特图·vxe-ui