Cricket 2019-06-30
通过对 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语法树:
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-if
、v-for
、v-bind
、v-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-for
、v-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
的编译:
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()
函数的参数解析子节点。举个