Vue原理学习(五)持续更新。。。

template模板是怎么通过Compile编译的

我们回头再来看Vue原理学习(一)中的这张图:

image

对于这张图,我们这次来研究complited到render function这一部分。
另外,我们同样再来看下面一段Vue代码:

<template>
  <div>hello</div>
</template>

这段Vue代码,使用了template语法,而我们本次讨论的就是 template模板是怎么通过Compile编译的?

前面我们也已经简单的提到过,Vue通过正则解析(parse)、优化(optimize),最终生成(generate)AST,转成render function

而这里,我们在详细的介绍下。

准备一个示例

首先我们准备一个示例,围绕这个示例来进行探讨学习。

<div :class="c" class="demo" v-if="isShow">
    <span v-for="item in sz">{{item}}</span>
</div>

字符串形式:

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

对于AST,我们可以先看下究竟长什么样子。

{
  // 标签属性map,记录标签上都有哪些属性
  'attrsMap': {
    ':class': c,
    'class': 'demo',
    'v-if': 'isShow'
  },
  // 解析得到的:class
  ‘classBinding’: 'c',
  // v-if 解析
  'if': 'isShow',
  // v-if 的条件
  'ifConditions': [
    {
      'exp': 'isShow'
    }
  ],
  // 标签属性class,非绑定class
  'staticClass': 'demo',
  // 标签tag
  'tag': 'div',
  // 子节点数据
  'children': [
    {
      'attrsMap': {
         'v-for': "item in sz"
       },
       // for 循环参数,alis:循环项的别名, 也就是 xx in data 中的 xx
      'alias': 'item',
      // for 循环对象
      'for': 'sz',
      // for 循环是否已被处理标记
      'forProcessed': true,
      'tag': 'span',
      'children': [
        {
          /* 表达式,_s是一个转字符串的函数 */
          'expression': '_s(item)',
          'text': '{{item}}'
        }
      ]
    }
  ]
}

你会发现,和Virtual DOM 有相似的感觉,但是不要混淆,其作用不同。另外,如果你仔细的阅读Vue源码,会发现,上述代码描述,在源码中都存在,包括下面介绍的正则代码。

定义正则

前面提到了,parse的过程,就是用到了正则表达式进行检索查询处理。

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采用的是循环进行字符串匹配的方式,也就是我们每次匹配解析完一段代码后,都要将已经匹配的代码去掉,头部指针重新指向接下来需要匹配的部分,直到没有可匹配项。

advance方法在Vue源码中存在定义,大概在8861行。

// 参数n就是每次匹配的位置,或者说每次匹配到的那段代码的长度
function advance(n) {
  index += n;
  html = html.substring(n);
}

当我们匹配完<div :class="c" class="demo" v-if="isShow">这段代代码后,指针将移动43个字符(因为长度等于43),此时,调用advancef方法:
advance(43)

parseHTML

parseHTML方法在Vue源码中也存在定义,大概在8734行。这里我们对其做了简化。

function parseHTML(html) {
  while(html) {
    let textEnd = html.indexOf('<');
    if (textEnd === 0) {
      if (html.match(endTag)) {
        //...process end tag
        // endTag: 前面定义的正则
        // 匹配到了结束标签 
        continue;
      }
      if (html.match(startTagOpen)) {
         //...process start tag
        // startTagOpen: 前面定义的正则
        // 匹配到了开始标签 
        continue;
      }
    } else {
      continue;
    }
  }
}

这里我们用到了前面定义的两个正则:endTagstartTagOpen。这里,我们把源码中的parseHTML方法做了简化,方便理解。总的来看,就是利用了while来循环解析template,用正则匹配结束和开始标签以及文本,分别来做不同的处理,直到整个template解析完成。

parseStartTag

到这里,我们大概了解了parseHTML的原理。但是前面我们也说了,parseHTML内部没有做任何处理,只是做了判断,直接continue了。

所以,这里的parseStartTag的作用就是用来解析开始标签的。比如<div :class="c" class="demo" v-if="isShow">

该方法大概存在与Vue源码中的8866行, 此处我们也直接拿过来用。

function parseStartTag() {
  const start = html.match(startTagOpen); // 匹配开始位置
  if (start) {
    // 成功匹配到了
    const match = {
      tagName: start[1], // 得到标签名
      attrs: [], // 用来存放标签内属性
      start: index // index: 全局变量,当前正则遍历的template字符位置
    };
    advance(start[0].length);
    let end, attr; // 变量初始化
    while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))){
      // 如果结束位置不等于标签的结束位置
      // 并且属性也不匹配
      // 说明该部分还没遍历完
      advance(attr[0].length);
      match.attrs.push({
        name: attr[1],
        value: attr[3]
      });
    }
    if (end) {
      match.unarySlash = end[1];
      advance(end[0].length);
      match.end = index;
      return match;
    }
  }
}

首先,我们利用了startTagOpen正则得到了标签的头部,拿到了tagName(标签名 start[1]),定义了attrs空数组,用来存放标签内属性。

const start = html.match(startTagOpen); 
const match = {
  tagName: start[1], // 得到标签名
  attrs: [], // 用来存放标签内属性
  start: index // index: 全局变量,当前正则遍历的template字符位置
};

接下来,我们使用了while,用startTagCloseattribute两个正则来匹配解析标签的结束和标签内的属性,一直循环到startTagClose,解析完内部所有属性为止。

stack

我们现在思考这样一个问题:标签是存在嵌套关系的,而我们解析是根据字符逐个解析的,这样就会导致嵌套关系的混乱,无法保存刚刚解析好的标签头。

为了解决这一问题,我们维护一个stack栈来保存已经解析好的标签头。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 137,063评论 19 139
  • 上期推荐非常及时,本来当晚写好了就发出来的,唯恐泄露天机,决定第二天8点多发出来,后来忙其他事务,居然忘记。第一期...
    观澜看彩阅读 765评论 4 2
  • 那天到办公室签外出条,和儿子小学一年级的数学老师、我的同事张老师攀谈几句,聊起儿子高中就读的学校,聊起她即将初中毕...
    爱如你我阅读 767评论 0 7
  • 想象你的样子,眼睛忽闪忽闪的,透亮的脸蛋,倔强的抿着嘴唇。天真如孩童,温婉如似水女子,倔强时如同决绝的小鹿,蹦跳着...
    LOVE玲媛阅读 105评论 0 0
  • 如果詹姆斯能设法率领这支并没有争冠实力的骑士夺下自己职业生涯第四座总冠军奖杯,那么这肯定是前所未有的成就。 但如果...
    zoneball阅读 389评论 0 0

友情链接更多精彩内容