博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从源码理解 Vue 模板编译
阅读量:5770 次
发布时间:2019-06-18

本文共 3629 字,大约阅读时间需要 12 分钟。

Vue 的 template 是如何编译成真正的 HTML 并做到双向绑定等等特殊功能的呢?以往这个问题对我来说一直是个黑洞。最近看了 Vue 的源码,对模板编译的整个过程的脉络有了更为清晰的了解。

先甩一张图

Vue 渲染过程

在这张图中,我们可以看到 Vue 的模板编译是在 $mount 的过程中进行的,在 $mount 的时候执行了 compile 这个方法来将 template 里的内容转换成真正的 HTML 代码。complie 之后执行的事情也蛮重要的,这个我们留到最后再说。complie 最终生成 render 函数,等待调用。这个方法分为三步:

  • parse 函数解析 template
  • optimize 函数优化静态内容
  • generate 函数创建 render 函数字符串

parse 解析

在了解 parse 的过程之前,我们需要了解 AST,AST 的全称是 Abstract Syntax Tree,也就是所谓抽象语法树,用来表示代码的数据结构。在 Vue 中我把它理解为嵌套的、携带标签名、属性和父子关系的 JS 对象,以树来表现 DOM 结构。

下面是 Vue 里的 AST 的定义:

AST

我们可以看到 AST 有三种类型,并且通过 children 这个字段层层嵌套形成了树状的结构。而每一个 AST 节点存放的就是我们的 HTML 元素、插值表达式或文本内容。AST 正是 parse 函数生成和返回的。

parse 函数里定义了许多的正则表达式,通过对标签名开头、标签名结尾、属性字段、文本内容等等的递归匹配。把字符串类型的 template 转化成了树状结构的 AST。

// parse 里定义的一些正则export const onRE = /^@|^v-on:/ //匹配 v-onexport const dirRE = /^v-|^@|^:/ //匹配 v-on 和 v-bindexport const forAliasRE = /(.*?)\s+(?:in|of)\s+(.*)/ //匹配 v-for 属性export const forIteratorRE = /\((\{[^}]*\}|[^,]*),([^,]*)(?:,([^,]*))?\)/ //匹配 v-for 的多种形式

我们可以把这个过程理解为一个截取的过程,它把 template 字符串里的元素、属性和文本一个个地截取出来,其中的细节十分琐碎,涉及到各种不同情况(比如不同类型的 v-for,各种 vue 指令、空白节点以及父子关系等等),我们不再赘述。

Parse 过程

假设我们有一个元素<div id="test">texttext</div>,在 parse 完之后会变成如下的结构并返回:

ele1 = {    type: 1,    tag: "div",    attrsList: [{name: "id", value: "test"}],    attrsMap: {id: "test"},    parent: undefined,    children: [{        type: 3,        text: 'texttext'      }    ],    plain: true,    attrs: [{name: "id", value: "'test'"}]  }

optimize 优化

在第二步中,会对 parse 生成的 AST 进行静态内容的优化。静态内容指的是和数据没有关系,不需要每次都刷新的内容。标记静态节点的作用是为了在后面做 Vnode 的 diff 时起作用,用来确认一个节点是否应该做 patch 还是直接跳过。optimize 的过程分为两步:

  • 标记所有的静态和非静态结点
  • 标记静态根节点

标记所有的静态和非静态结点

关于这一段我们可以直接看源码:

function markStatic (node: ASTNode) {  // 标记 static 属性  node.static = isStatic(node)  if (node.type === 1) {    // 注意这个判断逻辑    if (      !isPlatformReservedTag(node.tag) &&      node.tag !== 'slot' &&      node.attrsMap['inline-template'] == null    ) {      return    }    for (let i = 0, l = node.children.length; i < l; i++) {      const child = node.children[i]      markStatic(child)      if (!child.static) {        node.static = false      }    }  }}

上面的代码中有几个需要注意的地方:

  • isStatic 函数

isStatic 函数顾名思义是判断该节点是否 static 的函数,符合如下内容的节点就会被认为是 static 的节点:

1. 如果是表达式AST节点,直接返回 false2. 如果是文本AST节点,直接返回 true3. 如果元素是元素节点,阶段有 v-pre 指令 ||  1. 没有任何指令、数据绑定、事件绑定等 &&  2. 没有 v-if 和 v-for &&  3. 不是 slot 和 component &&  4. 是 HTML 保留标签 &&  5. 不是 template 标签的直接子元素并且没有包含在 for 循环中  则返回 true
  • if 判断条件
  1. !isPlatformReservedTag(node.tag):node.tag 不是 HTML 保留标签时返回true。
  2. node.tag !== 'slot':标签不是slot。
  3. node.attrsMap['inline-template'] == null:node不是一个内联模板容器。

如果满足上面的所有条件,那么这个节点的 static 就会被置为 false 并且不递归子元素,当不满足上面某一个条件时,递归子元素判断子元素是否 static,只有所有元素都是 static 的时候,该元素才是 static。

标记静态根节点

这部分理解起来很简单,只有当一个节点是 static 并且其不能只拥有一个静态文本节点时才能被称为 static root。因为作者认为这种情况去做优化,其消耗会超过获得的收益。

if (node.static && node.children.length && !(  node.children.length === 1 &&  node.children[0].type === 3)) {  node.staticRoot = true  return} else {  node.staticRoot = false}

generate 生成 render

生成 render 的 generate 函数的输入也是 AST,它递归了 AST 树,为不同的 AST 节点创建了不同的内部调用方法,等待后面的调用。生成 render 函数的过程如下:

generate 函数

几种内部方法_c:对应的是 createElement 方法,顾名思义,它的含义是创建一个元素(Vnode)_v:创建一个文本结点。_s:把一个值转换为字符串。(eg: {
{data}})_m:渲染静态内容

假设我们有这么一段 template

最终会被转换成这样子的函数字符串

{render: "with(this){return _c('div',{attrs:{"id":"test"}},[[_v(_s(val))]),_v(" "),_m(0)])}"}

后话

整个 Vue 渲染过程,前面我们说了 complie 的过程,在做完 parse、optimize 和 generate 之后,我们得到了一个 render 函数字符串。

那么接下来 Vue 做的事情就是 new watcher,这个时候会对绑定的数据执行监听,render 函数就是数据监听的回调所调用的,其结果便是重新生成 vnode。当这个 render 函数字符串在第一次 mount、或者绑定的数据更新的时候,都会被调用,生成 Vnode。如果是数据的更新,那么 Vnode 会与数据改变之前的 Vnode 做 diff,对内容做改动之后,就会更新到我们真正的 DOM 上啦~

转载地址:http://yysux.baihongyu.com/

你可能感兴趣的文章
Puppet 配置管理工具安装
查看>>
ciscodk:1D0-610已上架
查看>>
Bug多,也别乱来,别被Bug主导了开发
查看>>
sed 替换基础使用
查看>>
高性能的MySQL(5)创建高性能的索引一B-Tree索引
查看>>
附件3:eclipse memory analyze使用教程
查看>>
《51CTO博客2.0——活跃之星评选大赛》送的获奖书籍及个人感言
查看>>
oracle备份与恢复--rman
查看>>
近70万美国运通印度分公司客户个人详细信息遭泄露
查看>>
Memcached主主复制+Keepalived高可用群集
查看>>
Postfix邮件发送和接收实验
查看>>
在LNMP架构中搭建zabbix监控服务!!!
查看>>
MySQL多实例介绍及安装
查看>>
bit.ly短网址API
查看>>
AIX修改root # 号提示符
查看>>
HELLXMAN公布百度影音远程代码执行漏洞
查看>>
解决OUTLOOK2010在收发邮件时老提示“正在同步已订阅文件夹”
查看>>
详解Jquery和AngularJs,Servlet中jsonp解决跨域问题(转)
查看>>
CentOS6.9安装mysql5.7.21教程
查看>>
lable控件
查看>>