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
/** * 设置 el.attrs 数组对象,每个元素都是一个属性对象 { name: attrName, value: attrVal, start, end } */functionprocessRawAttrs(el){constlist=el.attrsListconstlen=list.lengthif(len){constattrs: Array<ASTAttr> = el.attrs = new Array(len)
for (let i = 0; i <len;i++){attrs[i]={name: list[i].name,value: JSON.stringify(list[i].value)}if(list[i].start!=null){attrs[i].start=list[i].startattrs[i].end=list[i].end}}}elseif(!el.pre){// non root node in pre blocks with no attributesel.plain=true}}
/** * 检查根元素: * 不能使用 slot 和 template 标签作为组件的根元素 * 不能在有状态组件的 根元素 上使用 v-for 指令,因为它会渲染出多个元素 * @param {*} el */functioncheckRootConstraints(el){// 不能使用 slot 和 template 标签作为组件的根元素if(el.tag==='slot'||el.tag==='template'){warnOnce(`Cannot use <${el.tag}> as component root element because it may `+'contain multiple nodes.',{start: el.start})}// 不能在有状态组件的 根元素 上使用 v-for,因为它会渲染出多个元素if(el.attrsMap.hasOwnProperty('v-for')){warnOnce('Cannot use v-for on stateful component root element because '+'it renders multiple elements.',el.rawAttrsMap['v-for'])}}
closeElement
/src/compiler/parser/index.js
/** * 主要做了 3 件事: * 1、如果元素没有被处理过,即 el.processed 为 false,则调用 processElement 方法处理节点上的众多属性 * 2、让自己和父元素产生关系,将自己放到父元素的 children 数组中,并设置自己的 parent 属性为 currentParent * 3、设置自己的子元素,将自己所有非插槽的子元素放到自己的 children 数组中 */functioncloseElement(element){// 移除节点末尾的空格,当前 pre 标签内�的元素除外trimEndingWhitespace(element)// 当前元素不再 pre 节点内,并且也没有被处理过if(!inVPre&&!element.processed){// 分别处理元素节点的 key、ref、插槽、自闭合的 slot 标签、动态组件、class、style、v-bind、v-on、其它指令和一些原生属性 element=processElement(element,options)}// 处理根节点上存在 v-if、v-else-if、v-else 指令的情况// 如果根节点存在 v-if 指令,则必须还提供一个具有 v-else-if 或者 v-else 的同级别节点,防止根元素不存在// tree managementif(!stack.length&&element!==root){// allow root elements with v-if, v-else-if and v-elseif(root.if&&(element.elseif||element.else)){if(process.env.NODE_ENV!=='production'){// 检查根元素checkRootConstraints(element)}// 给根元素设置 ifConditions 属性,root.ifConditions = [{ exp: element.elseif, block: element }, ...]addIfCondition(root,{exp: element.elseif,block: element})}elseif(process.env.NODE_ENV!=='production'){// 提示,表示不应该在 根元素 上只使用 v-if,应该将 v-if、v-else-if 一起使用,保证组件只有一个根元素warnOnce(`Component template should contain exactly one root element. `+`If you are using v-if on multiple elements, `+`use v-else-if to chain them instead.`,{start: element.start})}}// 让自己和父元素产生关系// 将自己放到父元素的 children 数组中,然后设置自己的 parent 属性为 currentParentif(currentParent&&!element.forbidden){if(element.elseif||element.else){processIfConditions(element,currentParent)}else{if(element.slotScope){// scoped slot// keep it in the children list so that v-else(-if) conditions can// find it as the prev node.constname=element.slotTarget||'"default"';(currentParent.scopedSlots||(currentParent.scopedSlots={}))[name]=element}currentParent.children.push(element)element.parent=currentParent}}// 设置自己的子元素// 将自己的所有非插槽的子元素设置到 element.children 数组中// final children cleanup// filter out scoped slotselement.children=element.children.filter(c=>!(c: any).slotScope)// remove trailing whitespace node againtrimEndingWhitespace(element)// check pre stateif(element.pre){inVPre=false}if(platformIsPreTag(element.tag)){inPre=false}// 分别为 element 执行 model、class、style 三个模块的 postTransform 方法// 但是 web 平台没有提供该方法// apply post-transformsfor(leti=0;i<postTransforms.length;i++){postTransforms[i](element,options)}}
trimEndingWhitespace
/src/compiler/parser/index.js
/** * 删除元素中空白的文本节点,比如:<div> </div>,删除 div 元素中的空白节点,将其从元素的 children 属性中移出去 */functiontrimEndingWhitespace(el){if(!inPre){letlastNodewhile((lastNode=el.children[el.children.length-1])&&lastNode.type===3&&lastNode.text===' '){el.children.pop()}}}
processIfConditions
/src/compiler/parser/index.js
functionprocessIfConditions(el,parent){// 找到 parent.children 中的最后一个元素节点constprev=findPrevElement(parent.children)if(prev&&prev.if){addIfCondition(prev,{exp: el.elseif,block: el})}elseif(process.env.NODE_ENV!=='production'){warn(`v-${el.elseif ? ('else-if="'+el.elseif+'"') : 'else'} `+`used on element <${el.tag}> without corresponding v-if.`,el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else'])}}
findPrevElement
/src/compiler/parser/index.js
/** * 找到 children 中的最后一个元素节点 */functionfindPrevElement(children: Array<any>): ASTElement|void{leti=children.lengthwhile(i--){if(children[i].type===1){returnchildren[i]}else{if(process.env.NODE_ENV!=='production'&&children[i].text!==' '){warn(`text "${children[i].text.trim()}" between v-if and v-else(-if) `+`will be ignored.`,children[i])}children.pop()}}}
Vue 源码解读(8)—— 编译器 之 解析(下)
当学习成为了习惯,知识也就变成了常识。 感谢各位的 关注、点赞、收藏和评论。
新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn
文章已收录到 github 仓库 liyongning/blog,欢迎 Watch 和 Star。
封面
特殊说明
由于文章篇幅限制,所以将 Vue 源码解读(8)—— 编译器 之 解析 拆成了两篇文章,本篇是对 Vue 源码解读(8)—— 编译器 之 解析(上) 的一个补充,所以在阅读时请同时打开 Vue 源码解读(8)—— 编译器 之 解析(上) 一起阅读。
processAttrs
addHandler
addIfCondition
processPre
processRawAttrs
processIf
processOnce
checkRootConstraints
closeElement
trimEndingWhitespace
processIfConditions
findPrevElement
帮助
到这里编译器的解析部分就结束了,相信很多人看的是云里雾里的,即使多看几遍可能也没有那么清晰。
不要着急,这个很正常,编译器这块儿的代码量确实是比较大。但是内容本身其实不复杂,复杂的是它要处理东西实在是太多了,这才导致这部分的代码量巨大,相对应的,就会产生比较难的感觉。确实不简单,至少我觉得它是整个框架最复杂最难的地方了。
对照着视频和文章大家可以多看几遍,不明白的地方写一些示例代码辅助调试,编写详细的注释。还是那句话,书读百遍,其义自现。
阅读的过程中,大家需要抓住编译器解析部分的本质:将类 HTML 字符串模版解析成 AST 对象。
所以这么多代码都在做一件事情,就是解析字符串模版,将整个模版用 AST 对象来表示和记录。所以,大家阅读的时候,可以将解析过程中生成的 AST 对象记录下来,帮助阅读和理解,这样在读完以后不至于那么迷茫,也有助于大家理解。
这是我在阅读的时候的一个简单记录:
总结
面试官 问:简单说一下 Vue 的编译器都做了什么?
答:
Vue 的编译器做了三件事情:
将组件的 html 模版解析成 AST 对象
优化,遍历 AST,为每个节点做静态标记,标记其是否为静态节点,然后进一步标记出静态根节点,这样在后续更新的过程中就可以跳过这些静态节点了;标记静态根用于生成渲染函数阶段,生成静态根节点的渲染函数
从 AST 生成运行时的渲染函数,即大家说的 render,其实还有一个,就是 staticRenderFns 数组,里面存放了所有的静态节点的渲染函数
面试官 问:详细说一说编译器的解析过程,它是怎么将 html 字符串模版变成 AST 对象的?
答:
遍历 HTML 模版字符串,通过正则表达式匹配 "<"
跳过某些不需要处理的标签,比如:注释标签、条件注释标签、Doctype。
解析开始标签
得到一个对象,包括 标签名(tagName)、所有的属性(attrs)、标签在 html 模版字符串中的索引位置
进一步处理上一步得到的 attrs 属性,将其变成 [{ name: attrName, value: attrVal, start: xx, end: xx }, ...] 的形式
通过标签名、属性对象和当前元素的父元素生成 AST 对象,其实就是一个 普通的 JS 对象,通过 key、value 的形式记录了该元素的一些信息
接下来进一步处理开始标签上的一些指令,比如 v-pre、v-for、v-if、v-once,并将处理结果放到 AST 对象上
处理结束将 ast 对象存放到 stack 数组
处理完成后会截断 html 字符串,将已经处理掉的字符串截掉
解析闭合标签
如果匹配到结束标签,就从 stack 数组中拿出最后一个元素,它和当前匹配到的结束标签是一对。
再次处理开始标签上的属性,这些属性和前面处理的不一样,比如:key、ref、scopedSlot、样式等,并将处理结果放到元素的 AST 对象上
然后将当前元素和父元素产生联系,给当前元素的 ast 对象设置 parent 属性,然后将自己放到父元素的 ast 对象的 children 数组中
最后遍历完整个 html 模版字符串以后,返回 ast 对象
链接
感谢各位的:关注、点赞、收藏和评论,我们下期见。
当学习成为了习惯,知识也就变成了常识。 感谢各位的 关注、 点赞、收藏和评论。
新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn
文章已收录到 github 仓库 liyongning/blog,欢迎 Watch 和 Star。
The text was updated successfully, but these errors were encountered: