Vue 进阶 (五)

template 模板编译

Compile 分为三个阶段 parse optimize generate。最终会得到 render function

![]()

  • 实例
<div :class="c" class="demo" v-if="isShow">
    <span v-for="item in sz">{{item}}</span>
</div>
var html = '<div :class="c" class="demo" v-if="isShow"><span v-for="item in sz">{{item}}</span></div>';

一、parse

首先通过 正则表达式对template字符串进行处理解析,得到指令、class、style、等数据,形成AST(抽象语法树)。

// AST 如下

{
    // 标签属性的map,记录了标签上的属性 
    "attrsMap": {
        ':class':'c',
        'class':'demo',
        'v-if': 'isShow'
    },
    // 解析得到的:class
    'classBinding': 'c',
    'if': 'isShow',
    // if条件
    'ifConditions': [
        'exp': 'isShow'
    ],
    // 静态class
    'staticClass': 'demo',
    // 标签的tag
    'tag': 'div',
    children: [
        {
            'attrsMap': {
                'v-for': 'item in sz'
            }.
            // 循环的对象
            'for': 'sz',
            // 循环的参数
            'alias': 'item',
            // 循环是否已经被处理的标记位
            'forProcessed': true,
            'tag' : 'span',
            'children' :[
                {
                    /* 表达式,_s是一个转字符串的函数 */
                    'expression': '_s(item)',
                    'text': '{{item}}'
                }
            ]
        }
    ]
}

这样一颗AST树,比较清晰的描述了标签的属性以及依赖关系。

  • 如何通过正则来把template模板编译为我们需要的AST呢?
// parse 里定义的一些正则
export const onRE = /^@|^v-on:/ //匹配 v-on
export const dirRE = /^v-|^@|^:/ //匹配 v-on 和 v-bind
export const forAliasRE = /(.*?)\s+(?:in|of)\s+(.*)/ //匹配 v-for 属性
export const forIteratorRE = /\((\{[^}]*\}|[^,]*),([^,]*)(?:,([^,]*))?\)/ //匹配 v-for 的多种形式


const ncname = '[a-zA-Z_][\\w\\-\\.]*';
const singleAttrIdentifier = /([^\s"'<>/=]+)/
const singleAttrAssign = /(?:=)/
const singleAttrValues = [
  /"([^"]*)"+/.source,
  /'([^']*)'+/.source,
  /([^\s"'=<>`]+)/.source
]
const attribute = new RegExp(
  '^\\s*' + singleAttrIdentifier.source +
  '(?:\\s*(' + singleAttrAssign.source + ')' +
  '\\s*(?:' + singleAttrValues.join('|') + '))?'
)
const qnameCapture = '((?:' + ncname + '\:)?' + ncname + ')'
const startTagOpen = new RegExp('^<' + qnameCapture)
const startTagClose = /^\s*(/?)>/


const endTag = new RegExp('^<\/' + qnameCapture + '[^>]*>')


const defaultTagRE = /{{((?:.|\n)+?)}}/g


const forAliasRE = /(.?)\s+(?:in|of)\s+(.)/

advance

因为我们解析 template 采用循环进行字符串匹配的方式,所以每匹配解析完一段我们需要将已经匹配掉的去掉,头部的指针指向接下来需要匹配的部分。

function advance (n) {
    index += n
    html = html.substring(n)
}

举个例子,当我们把第一个 div 的头标签全部匹配完毕以后,我们需要将这部分除去,也就是向右移动 43 个字符。

advance(43);

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

假设我们有一个元素<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

optimize 主要作用就跟它的名字一样,用作「优化」

这个涉及到后面要讲 patch 的过程,因为 patch 的过程实际上是将 VNode 节点进行一层一层的比对,然后将「差异」更新到视图上。那么一些静态节点是不会根据数据变化而产生变化的,这些节点我们没有比对的需求,是不是可以跳过这些静态节点的比对,从而节省一些性能呢?

那么我们就需要为静态的节点做上一些「标记」,在 patch 的时候我们就可以直接跳过这些被标记的节点的比对,从而达到「优化」的目的。

经过 optimize 这层的处理,每个节点会加上 static 属性,用来标记是否是静态的。

{
    'attrsMap': {
        ':class': 'c',
        'class': 'demo',
        'v-if': 'isShow'
    },
    'classBinding': 'c',
    'if': 'isShow',
    'ifConditions': [
        'exp': 'isShow'
    ],
    'staticClass': 'demo',
    'tag': 'div',
    /* 静态标志 */ 【1】
    'static': false,
    'children': [
        {
            'attrsMap': {
                'v-for': "item in sz"
            },
            'static': false,
            'alias': "item",
            'for': 'sz',
            'forProcessed': true,
            'tag': 'span',
            'children': [
                {
                    'expression': '_s(item)',
                    'text': '{{item}}',
                    'static': false
                }
            ]
        }
    ]
}

三、generate 生成 render

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

假设我们有这么一段 template

<template>
  <div id="test">
    {{val}}
    <img src="http://xx.jpg">
  </div>
</template>
// 最终转换为函数字符串
{render: "with(this){return _c('div',{attrs:{"id":"test"}},[[_v(_s(val))]),_v(" "),_m(0)])}"}
几种内部方法
_c:对应的是 createElement 方法,顾名思义,它的含义是创建一个元素(Vnode)
_v:创建一个文本结点。
_s:把一个值转换为字符串。(eg: {{data}})
_m:渲染静态内容

总结

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

接下来就会对数据做watcher,对绑定的数据进行监听,当数据发生变化,通过diff对新的VNode 和 之前的VNode 进行比较,重新渲染改变了的部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值