当前位置: 首页 > Web前端 > vue.js

Vue源码解读(九)——编译器优化

时间:2023-04-01 02:18:33 vue.js

当学习成为习惯,知识就成为常识。感谢您的关注、点赞、收藏和评论。有新视频和文章会第一时间发到微信公众号。欢迎关注:李永宁lyn的文章已收录到github仓库liyongning/blog。欢迎收看星空。前言上一篇文章Vue源码解读(八)——编译器浅析详细讲解了编译器的第一部分,如何将html模板字符串编译成AST。今天带来编译器的第二部分,优化AST,也就是通常所说的静态标记。目标深入理解编译器的静态标记过程源码解读入口/src/compiler/index.js/***在此之前所做的所有事情只有一个目的,就是构建平台特定的编译选项(options),如web平台**1.将html模板解析成ast*2.静态标记ast树*3.将ast生成渲染函数*并将静态渲染函数放在code.staticRenderFns数组*代码中。render是一个动态渲染函数*在以后渲染的时候,执行渲染函数得到vnode*/exportconstcreateCompiler=createCompilerCreator(functionbaseCompile(template:string,options:CompilerOptions):CompiledResult{//将模板解析成AST,并在每个节点的ast对象上设置该元素的值的所有信息,如标签信息、属性信息、槽信息、父节点、子节点等。//具体属性见start和end,处理开始和结束标签的两种方法constast=parse(template.trim(),options)//优化,遍历AST,为每个节点做静态标记//标记每个节点是否为静态节点,然后进一步标记静态根节点//以便在后续的更新过程中可以跳过这些静态节点//标记静态根,用于生成渲染函数阶段,生成一个静态根节点If(options.optimize!==false){optimize(ast,options)}//从AST生成渲染函数,生成代码如下,例如:code.render="_c('div',{attrs:{"id":"app"}},_l((arr),function(item){return_c('div',{key:item},[_v(_s(item))])}),0)"constcode=generate(ast,options)return{ast,render:code.render,staticRenderFns:code.staticRenderFns}})optimize/src/compiler/optimizer.js/***优化:*遍历AST,标记每个节点是静态节点还是动态节点,然后标记静态根节点*,这样在后续的更新过程中就不需要关注这些节点了*/exportfunctionoptimize(root:?ASTElement,options:CompilerOptions){if(!root)return/***options.staticKeys='staticClass,staticStyle'*isStaticKey=function(val){returnmap[val]}*/isStaticKey=genStaticKeysCached(options.staticKeys||'')//平台保留标签isPlatformReservedTag=options.isReservedTag||no//遍历所有节点,为每个节点设置静态属性,识别是否为静态节点markStatic(root)//进一步标记静态根,一个节点需要是静态根节点,如下条件://的节点本身是静态节点,有子节点,而子节点不只是文本节点,它被标记为静态根//静态根节点不能只有静态文本子节点,因为回报太低。一直更新就好markStaticRoots(root,false)}markStatic/src/compiler/optimizer.js/***在所有节点上设置static属性,识别是否是静态节点*注意:如果有子节点的话是动态节点,父节点也算动态节点*@param{*}node*@returns*/functionmarkStatic(node:ASTNode){//使用node.static来判断节点是否是静态的nodenode.static=isStatic(node)if(node.type===1){/***不要将组件的槽内容设置为静态节点,这样可以避免:*1.组件无法改变槽节点*2.静态插槽内容在热重载时失败*/if(!isPlatformReservedTag(node.tag)&&node.tag!=='slot'&&node.attrsMap['inline-template']==null){//递归终止条件,如果该节点既不是平台保留标签&&也不是slot标签&&也不是内联模板,则end直接return}//遍历子节点,递归调用markStatic标记静态属性这些子节点为(leti=0,l=node.children.length;我<我;i++){constchild=node.children[i]markStatic(child)//如果子节点是非静态节点,更新父节点为非静态节点if(!child.static){node.static=false}}//如果节点有v-if,v-else-if,v-else等指令,依次标记block中节点的staticif(node.ifConditions){for(leti=1,l=node.ifConditions.length;idynamic,3:text=>static*任意v-bind,v-if,v-for这样的指令都是动态节点*组件是动态节点*父节点是包含v-for指令的模板标签,那么就是动态节点*@param{*}节点*@returnsboolean*/functionisStatic(node:ASTNode):boolean{if(node.type===2){//表达式//例如:{{msg}}returnfalse}if(node.type===3){//文本返回true}return!!(node.pre||(!node.hasBindings&&//没有动态bindings!node.if&&!node.for&&//不是v-if或v-for或v-else!isBuiltInTag(node.tag)&&//不是内置的isPlatformReservedTag(node.tag)&&//不是acomponent!isDirectChildOfTemplateFor(node)&&Object.keys(node).every(isStaticKey)))}markStaticRoots/src/compiler/optimizer.js/***进一步标记静态根。一个节点要成为静态根节点,需要满足以下条件:*节点本身是静态节点,并且有子节点,并且子节点不只是文本节点,它被标记为静态根*Static根节点不能只有静态文本子节点,因为返回值太低,这种情况下就一直更新**@param{ASTElement}节点当前节点*@param{boolean}isInFor当前节点是否包裹在v-for指令所在节点*/functionmarkStaticRoots(node:ASTNode,isInFor:boolean){if(node.type===1){if(node.static||node.once){//The节点是静态的或者节点上有v-once指令,标记node.staticInFor=true或false节点。staticInFor=isInFor}if(node.static&&node.children.length&&!(node.children.length===1&&node.children[0].type===3)){//节点本身是静态的node,并且有子节点,并且子节点不只有一个文本节点,则标记为静态根=>node.staticRoot=true,否则为非静态根node.staticRoot=truereturn}else{node.staticRoot=false}//当当前节点不是a静态根节点,递归遍历其子节点,标记静态根if(node.children){for(leti=0,l=node.children.length;i