美文网首页
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原理学习(五)持续更新。。。

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