Vue源码解析:AST语法树转render函数

通过对 Vue2.0 源码阅读,想写一写自己的理解,能力有限故从尤大佬2016.4.11第一次提交开始读,准备陆续写:

  • 模版字符串转AST语法树
  • AST语法树转render函数
  • Vue双向绑定原理
  • Vue虚拟dom比较原理

其中包含自己的理解和源码的分析,尽量通俗易懂!由于是2.0的最早提交,所以和最新版本有很多差异、bug,后续将陆续补充,敬请谅解!包含中文注释的Vue源码已上传...

开始

今天要说的代码全在codegen文件夹中,在说实现原理前,还是先看个简单的例子!

<div class="container">
    <span>{{msg}}</span>
    <button :class="{active: isActive}"  @click="handle">change msg</button>
</div>

上述类名为container的元素节点包含5个子节点(其中3个是换行文本节点),转化成的AST语法树:
Vue源码解析:AST语法树转render函数

AST语法树转的render函数长这样:

function _render() {
  with (this) { 
    return __h__(
      'div', 
      {staticClass: "container"}, 
      [
        " ",
        __h__('span', {}, [String((msg))]),
        " ",
        __h__('button', {class: {active: isActive},on:{"click":handle}}, ["change msg"]),
        " "
      ]
    )
  };
}

可以的看出,render函数做的事情很简单,就是把语法树每个节点的指令进行解析。

看下render函数,它是由with函数包裹(为了改变作用域),要用的时候直接_render.call(vm);另外就是__h__函数,这个后面会说到,这个函数用于元素节点的解析,接收3个参数:元素节点标签名,节点数据,子节点数据。这个函数最后返回的就是虚拟dom了,不过今天先不深究,先说如何生成这样的render函数,主要是v-ifv-forv-bindv-on等指令的解析。

源码解析

这边解析的是从AST树转换成render函数部分的源码,由于vue2.0第一次提交的源码这部分不全,故做了部分更新,代码全在codegen文件夹中。

入口

整个AST语法树转render函数的起点是index.js文件中的generate()函数:

export function generate (ast) {
  const code = genElement(ast);
  return new Function (`with (this) { return ${code}}`);
}

明显看到,generate()函数传入参数为AST语法树,内部调用genElement()函数开始解析根节点(容器节点)。genElement()函数用于解析元素节点,它接收两个参数:AST对象节点标识(v-for的key),最后返回形如__h__('div', {}, [])的字符串,看一下内部逻辑:

function genElement (el, key) {
  let exp;
  if (exp = getAndRemoveAttr(el, 'v-for')) { // 解析v-for指令
    return genFor(el, exp);
  } else if (exp = getAndRemoveAttr(el, 'v-if')) { // 解析v-if指令
    return genIf(el, exp, key);
  } else if (el.tag === 'template') { // 解析子组件
    return genChildren(el);
  } else {
    return `__h__('${el.tag}', ${genData(el, key) }, ${genChildren(el)})`;
  }
}

genElement()函数内部依次调用getAndRemoveAttr()函数判断了v-forv-if标签是否存在,若存在则删除并返回表达式;随后判断节点名为template就直接进入子节点解析;以上条件都不符合就返回__h__函数字符串,该字符串将使用到属性解析和子节点解析。

function getAndRemoveAttr (el, attr) {
  let val;
  // 如果属性存在,则从AST对象的attrs和attrsMap移除
  if (val = el.attrsMap[attr]) {
    el.attrsMap[attr] = null;
    for (let i = 0, l = el.attrs.length; i < l; i++) {
      if (el.attrs[i].name === attr) {
        el.attrs.splice(i, 1);
        break;
      }
    }
  }
  return val;
}

v-for 和 v-if 指令解析

让我们先看看v-for的编译:

function genFor (el, exp) {
  const inMatch = exp.match(/([a-zA-Z_][\w]*)\s+(?:in|of)\s+(.*)/);
  if (!inMatch) {
    throw new Error('Invalid v-for expression: '+ exp);
  }
  const alias = inMatch[1].trim();
  exp = inMatch[2].trim();
  let key = getAndRemoveAttr(el, 'track-by'); // 后面用 :key 代替了 track-by

  if (!key) {
    key ='undefined';
  } else if (key !== '$index') {
    key = alias + '["' + key + '"]';
  }

  return `(${exp}) && (${exp}).map(function (${alias}, $index) {return ${genElement(el, key)}})`;
}

该函数先进行正则匹配,如"item in items",将解析出别名(item)和表达式(items),再去看看当前节点是否含:key,如果有那就作为genElement()函数的参数解析子节点。举个

相关推荐