You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
export functiongenText(text: ASTText|ASTExpression): string{return`_v(${text.type===2 ? text.expression// no need for () because already wrapped in _s() : transformSpecialNewlines(JSON.stringify(text.text))})`}
/** * 生成静态节点的渲染函数 * 1、将当前静态节点的渲染函数放到 staticRenderFns 数组中 * 2、返回一个可执行函数 _m(idx, true or '') */// hoist static sub-trees outfunctiongenStatic(el: ASTElement,state: CodegenState): string{// 标记当前静态节点已经被处理过了el.staticProcessed=true// Some elements (templates) need to behave differently inside of a v-pre// node. All pre nodes are static roots, so we can use this as a location to// wrap a state change and reset it upon exiting the pre node.constoriginalPreState=state.preif(el.pre){state.pre=el.pre}// 将静态根节点的渲染函数 push 到 staticRenderFns 数组中,比如:// [`with(this){return _c(tag, data, children)}`]state.staticRenderFns.push(`with(this){return ${genElement(el,state)}}`)state.pre=originalPreState// 返回一个可执行函数:_m(idx, true or '')// idx = 当前静态节点的渲染函数在 staticRenderFns 数组中下标return`_m(${state.staticRenderFns.length-1}${el.staticInFor ? ',true' : ''})`}
genOnce
/src/compiler/codegen/index.js
/** * 处理带有 v-once 指令的节点,结果会有三种: * 1、当前节点存在 v-if 指令,得到一个三元表达式,condition ? render1 : render2 * 2、当前节点是一个包含在 v-for 指令内部的静态节点,得到 `_o(_c(tag, data, children), number, key)` * 3、当前节点就是一个单纯的 v-once 节点,得到 `_m(idx, true of '')` */functiongenOnce(el: ASTElement,state: CodegenState): string{// 标记当前节点的 v-once 指令已经被处理过了el.onceProcessed=trueif(el.if&&!el.ifProcessed){// 如果含有 v-if 指令 && if 指令没有被处理过,则走这里// 处理带有 v-if 指令的节点,最终得到一个三元表达式,condition ? render1 : render2 returngenIf(el,state)}elseif(el.staticInFor){// 说明当前节点是被包裹在还有 v-for 指令节点内部的静态节点// 获取 v-for 指令的 keyletkey=''letparent=el.parentwhile(parent){if(parent.for){key=parent.keybreak}parent=parent.parent}// key 不存在则给出提示,v-once 节点只能用于带有 key 的 v-for 节点内部if(!key){process.env.NODE_ENV!=='production'&&state.warn(`v-once can only be used inside v-for that is keyed. `,el.rawAttrsMap['v-once'])returngenElement(el,state)}// 生成 `_o(_c(tag, data, children), number, key)`return`_o(${genElement(el,state)},${state.onceId++},${key})`}else{// 上面几种情况都不符合,说明就是一个简单的静态节点,和处理静态根节点时的操作一样,// 得到 _m(idx, true or '')returngenStatic(el,state)}}
genFor
/src/compiler/codegen/index.js
/** * 处理节点上的 v-for 指令 * 得到 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})` */exportfunctiongenFor(el: any,state: CodegenState,altGen?: Function,altHelper?: string): string{// v-for 的迭代器,比如 一个数组constexp=el.for// 迭代时的别名constalias=el.alias// iterator 为 v-for = "(item ,idx) in obj" 时会有,比如 iterator1 = idxconstiterator1=el.iterator1 ? `,${el.iterator1}` : ''constiterator2=el.iterator2 ? `,${el.iterator2}` : ''// 提示,v-for 指令在组件上时必须使用 keyif(process.env.NODE_ENV!=='production'&&state.maybeComponent(el)&&el.tag!=='slot'&&el.tag!=='template'&&!el.key){state.warn(`<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with `+`v-for should have explicit keys. `+`See https://vuejs.org/guide/list.html#key for more info.`,el.rawAttrsMap['v-for'],true/* tip */)}// 标记当前节点上的 v-for 指令已经被处理过了el.forProcessed=true// avoid r// 得到 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})`return`${altHelper||'_l'}((${exp}),`+`function(${alias}${iterator1}${iterator2}){`+`return ${(altGen||genElement)(el,state)}`+'})'}
Vue 源码解读(10)—— 编译器 之 生成渲染函数
当学习成为了习惯,知识也就变成了常识。 感谢各位的 关注、点赞、收藏和评论。
新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn
文章已收录到 github 仓库 liyongning/blog,欢迎 Watch 和 Star。
封面
前言
这篇文章是 Vue 编译器的最后一部分,前两部分分别是:Vue 源码解读(8)—— 编译器 之 解析、Vue 源码解读(9)—— 编译器 之 优化。
从 HTML 模版字符串开始,解析所有标签以及标签上的各个属性,得到 AST 语法树,然后基于 AST 语法树进行静态标记,首先标记每个节点是否为静态静态,然后进一步标记出静态根节点。这样在后续的更新中就可以跳过这些静态根节点的更新,从而提高性能。
这最后一部分讲的是如何从 AST 生成渲染函数。
目标
深入理解渲染函数的生成过程,理解编译器是如何将 AST 变成运行时的代码,也就是我们写的类 html 模版最终变成了什么?
源码解读
入口
generate
genElement
genChildren
genNode
genText
genComment
genData
genDirectives
genProps
genHandlers
genStatic
genOnce
genFor
genIf
genSlot
genComponent
总结
面试官 问:简单说一下 Vue 的编译器都做了什么?
答:
Vue 的编译器做了三件事情:
将组件的 html 模版解析成 AST 对象
优化,遍历 AST,为每个节点做静态标记,标记其是否为静态节点,然后进一步标记出静态根节点,这样在后续更新的过程中就可以跳过这些静态节点了;标记静态根用于生成渲染函数阶段,生成静态根节点的渲染函数
从 AST 生成运行渲染函数,即大家说的 render,其实还有一个,就是 staticRenderFns 数组,里面存放了所有的静态节点的渲染函数
面试官:详细说一下渲染函数的生成过程
答:
大家一说到渲染函数,基本上说的就是 render 函数,其实编译器生成的渲染有两类:
第一类就是一个 render 函数,负责生成动态节点的 vnode
第二类是放在一个叫 staticRenderFns 数组中的静态渲染函数,这些函数负责生成静态节点的 vnode
渲染函数生成的过程,其实就是在遍历 AST 节点,通过递归的方式,处理每个节点,最后生成形如:
_c(tag, attr, children, normalizationType)
的结果。tag 是标签名,attr 是属性对象,children 是子节点组成的数组,其中每个元素的格式都是_c(tag, attr, children, normalizationTYpe)
的形式,normalization 表示节点的规范化类型,是一个数字 0、1、2,不重要。在处理 AST 节点过程中需要大家重点关注也是面试中常见的问题有:
静态节点是怎么处理的
静态节点的处理分为两步:
将生成静态节点 vnode 函数放到 staticRenderFns 数组中
返回一个 _m(idx) 的可执行函数,意思是执行 staticRenderFns 数组中下标为 idx 的函数,生成静态节点的 vnode
v-once、v-if、v-for、组件 等都是怎么处理的
单纯的 v-once 节点处理方式和静态节点一致
v-if 节点的处理结果是一个三元表达式
v-for 节点的处理结果是可执行的 _l 函数,该函数负责生成 v-for 节点的 vnode
组件的处理结果和普通元素一样,得到的是形如
_c(compName)
的可执行代码,生成组件的 vnode到这里,Vue 编译器 的源码解读就结束了。相信大家在阅读的过程中不免会产生云里雾里的感觉。这个没什么,编译器这块儿确实是比较复杂,可以说是整个框架最难理解也是代码量最大的一部分了。一定要静下心来多读几遍,遇到无法理解的地方,一定要勤动手,通过示例代码加断点调试的方式帮助自己理解。
当你读完几遍以后,这时候情况可能就会好一些,但是有些地方可能还会有些晕,这没事,正常。毕竟这是一个框架的编译器,要处理的东西太多太多了,你只需要理解其核心思想(模版解析、静态标记、代码生成)就可以了。后面会有 手写 Vue 系列,编译器这部分会有一个简版的实现,帮助加深对这部分知识的理解。
编译器读完以后,会发现有个不明白的地方:编译器最后生成的代码都是经过
with
包裹的,比如:经过编译后生成:
都知道,
with
语句可以扩展作用域链,所以生成的代码中的_c、_l、_v、_s
都是 this 上一些方法,也就是说在运行时执行这些方法可以生成各个节点的 vnode。所以联系前面的知识,响应式数据更新的整个执行过程就是:
响应式拦截到数据的更新
dep 通知 watcher 进行异步更新
watcher 更新时执行组件更新函数 updateComponent
首先执行 vm._render 生成组件的 vnode,这时就会执行编译器生成的函数
问题:
渲染函数中的
_c、_l、、_v、_s
等方法是什么?它们是如何生成 vnode 的?
下一篇文章 Vue 源码解读(11)—— render helper 将会带来这部分知识的详细解读,也是面试经常被问题的:比如:
v-for
的原理是什么?链接
感谢各位的:关注、点赞、收藏和评论,我们下期见。
当学习成为了习惯,知识也就变成了常识。 感谢各位的 关注、 点赞、收藏和评论。
新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn
文章已收录到 github 仓库 liyongning/blog,欢迎 Watch 和 Star。
The text was updated successfully, but these errors were encountered: