美文网首页
Vue原理学习(五)持续更新。。。

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

作者: IsaacHHH | 来源:发表于2018-05-30 22:39 被阅读68次

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栈来保存已经解析好的标签头。

相关文章

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

    template模板是怎么通过Compile编译的 我们回头再来看Vue原理学习(一)中的这张图: 对于这张图,我...

  • [持续更新]Vue学习笔记

    引言:没啥js基础,程序代码也很久没碰了,从头学起当下流行的Vue,把遇到的问题和解决的方法逐一更新。供自勉,也供...

  • 前端部分面试题整理

    (持续更新中) 1、js事件循环(vue.nextTick原理也差不多) 2、浏览器渲染流程 3、原型链 4、什么...

  • Vue3.0 Openlayers教程

    Vue3.0 Openlayers教程持续更新中......

  • vue-cli3

    终端里的vue命令(如:vue create 、vue serve 、vue ui 等命令)完整详细配置(持续更新...

  • Vue3响应式原理傻瓜式教程(三)——ActiveEffect

    上一节,我们学习到了Vue3如何通过Proxy来更新计算结果:Vue3响应式原理傻瓜式教程(二)——Proxy &...

  • Vue watch监听原理篇

    学习watch原理之前需要了解更新原理 首先清楚在vue中watch有几种常见用法 初始化watch时 需要看一下...

  • Vue实现原理

    vue实现原理 1、了解Object的属性defineProperty 2、vue中mvvm的实现: 数据变化更新...

  • vue原理与开发逻辑

    1、vue中的$nextTick()的用法和原理 vue的DOM更新是异步的,当数据更新了,再dom中渲染后,自动...

  • 前端TODO

    Vue.js 等框架原理了解 webpack 原理了解 browserify 插件开发 Vue.js 等框架原理学习

网友评论

      本文标题:Vue原理学习(五)持续更新。。。

      本文链接:https://www.haomeiwen.com/subject/lpbojftx.html