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

时间:2022-09-26 00:12:37

开始

今天要说的代码全在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()函数的参数解析子节点。举个

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. PAT/字符串处理习题集(二)

    B1024. 科学计数法 (20) Description: 科学计数法是科学家用来表示很大或很小的数字的一种方便的方法,其满足正则表达式[+-][1-9]"."[0-9]+E[+ ...

  2. ATL开发 ActiveX控件的 inf文件模板

    ATL开发 ActiveX控件的 inf文件模板    

  3. 树形菜单的json字符串的拼接

    最近在学习权限管理, 要用到树形按钮, 但是字符串的拼接是一个难理解的问题, 然后从网上找了一个从前台用js来遍历组成这个json字符串, 很好! 但是没看懂... var data = [ {&qu ...

  4. 低版本的无法打开高版本的VM

    低版本VM工具運行高版本VM環境時,會彈出不支持虚拟机配置.例如:使用VM8工具打開VM9配置的VM環境,會彈出下面的提示 在遇到這種情況的時候,一般都會選擇升級VM工具.如果不想升級VM工具,可以通 ...

  5. TCP/IP详解学习笔记(9)-- 广播,多播,IGMP:网际组管理协议

    1.概述      IP有三种地址:单播地址, 广播地址,多播地址.      广播和多播仅应用于UDP.      每个以太网帧包含源主机和目的主机的以太网地址.通常每个以太网帧发往单个目的主机,目 ...

  6. 在android.app.Application中定义全局变量

    在Android应用中使用全局变量,除了public的静态变量,还有更优雅的方式是使用android.app.Application. 启动Application时,系统会创建一个PID,即进程ID, ...

  7. Hello World---C/C++

    C #include <stdio.h> void main() { printf("Hello World!\n"); } C++ #include <iost ...

  8. C&num; 学习之旅(2)--- 意外的收获

    今天在完成老师布置的C#作业(计算一元二次方程的根)的时候,收获到意外的知识,所以写此博文予以记录. 意外收获为: 对文本框的输入值进行检测,使之按照要求格式输入. 下面是整个的源代码: using ...

  9. List myList&equals;new ArrayList()的理解

    ArrayList不是继承List接口,是实现了List接口.你写成ArrayList arrayList = new ArrayList();这样不会有任何问题.和List list = new A ...

  10. 小甲鱼OD学习第9讲

    这次我们的任务是破解这个要注册的软件,如下图所示 当我们输入账号密码的时候,它会提示输入的账号密码是无效的,如下图 我们把程序载入OD,然后在查找字符串那里输入提示的无效账号密码的字符串,如下图 然后 ...