开始

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


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

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

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()函数的参数解析子节点。举个

Vue源码解析:AST语法树转render函数的更多相关文章

  1. 【VUE】Vue 源码解析

    Vue 源码解析 Vue 的工作机制 在 new vue() 之后,Vue 会调用进行初始化,会初始化生命周期.事件.props.methods.data.computed和watch等.其中最重要的 ...

  2. 【vuejs深入二】vue源码解析之一,基础源码结构和htmlParse解析器

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. vuejs是一个优秀的前端mvvm框架,它的易用性和渐进式的理念可以使每一个前端开发人员感到舒服,感到easy.它内 ...

  3. 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...

  4. Vue源码解析---数据的双向绑定

    本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...

  5. Vue源码解析之nextTick

    Vue源码解析之nextTick 前言 nextTick是Vue的一个核心功能,在Vue内部实现中也经常用到nextTick.但是,很多新手不理解nextTick的原理,甚至不清楚nextTick的作 ...

  6. Vue源码解析(一):入口文件

    在学习Vue源码之前,首先要做的一件事情,就是去GitHub上将Vue源码clone下来,目前我这里分析的Vue版本是V2.5.21,下面开始分析: 一.源码的目录结构: Vue的源码都在src目录下 ...

  7. Vue源码解析之数组变异

    力有不逮的对象 众所周知,在 Vue 中,直接修改对象属性的值无法触发响应式.当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变. 这是什么原因? 原因在于: Vue 的响应式 ...

  8. VUE源码解析心得

    解读vue源码比较好奇的几个点: VUE MVVM 原理 http://www.cnblogs.com/guwei4037/p/5591183.html https://cn.vuejs.org/v2 ...

  9. vue源码解析之observe

    一. vue文档中有"由于 JavaScript 的限制,Vue 不能检测以下数组的变动",是否真是由于JavaScript的限制,还是出于其他原因考虑 当你利用索引直接设置一个数 ...

随机推荐

  1. HDU 6667 Roundgod and Milk Tea

    hdu题面 Time limit 6000 ms Memory limit 131072 kB OS Windows Source 2019 Multi-University Training Con ...

  2. x-admin

    https://blog.csdn.net/u014793102/article/details/80316335

  3. Arch linux(UEFI+GPT)安装及后续优化教程

    Arch Linux安装过程中需要从远程存储库获取软件包,电脑需要有效的互联网连接. 1.联网 查看是否有网 ping www.baidu.com 同步时间 timedatectl set-ntp t ...

  4. 关于 LDTP 操纵 windows 控件。

    LDTP doc:  https://ldtp.freedesktop.org/user-doc/ 对于 web 自动化,我们用 Selenium, 但是对于 windows 控件,我们可以使用 LD ...

  5. KVM + LinuxBridge 的网络虚拟化解决方案实践

    目录 文章目录 目录 前言 Linux bridge 的基本操作 创建 Bridge 将 veth pair 连上 Bridge 为 Bridge 配置 IP 地址 将物理网卡接口设备挂靠 Bridg ...

  6. 阶段3 2.Spring_07.银行转账案例_7 代理的分析

    新建项目 实现动态代理. 动态代理的概念 买电脑找代理商 代理的出现 解决了生产厂家的一些问题 需要java中的动态代理机制

  7. 十五:jinja2过滤器之实现自定义过滤器

    过滤器的本质就是函数,如果在模板中调用这个过滤器,那么就会将这个变量的值作为第一个参数传给过滤器函数,然后将函数的返回值作为滤器的返回值 1.在python文件中写好过滤的函数和逻辑2.将将函数注册到 ...

  8. HttpRunnerManager(二)--使用

    参考资料:https://sutune.me/2018/08/05/httprunner/

  9. Java学习之==>条件判断、循环控制

    一.条件判断 1.if-else 示例: /** * 第1种,1个分支 */ public void case1() { int age = 15; if (age > 18) { System ...

  10. python虚拟环境mkvirtualenv使用

    安装virtualenvwrapper  pip install virtualenvwrapper   修改默认虚拟环境目录: 环境变量中新建: 变量名:WORKON_HOME 变量值:目录位置 ( ...