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
if(!inVPre){processPre(element)if(element.pre){inVPre=true}}if(platformIsPreTag(element.tag)){inPre=true}if(inVPre){processRawAttrs(element)}else{processFor(element)processIf(element)processOnce(element)processKey(element)// determine whether this is a plain element after// removing structural attributeselement.plain=!element.key&&!attrs.lengthprocessRef(element)processSlot(element)processComponent(element)for(leti=0;i<transforms.length;i++){transforms[i](element,options)}processAttrs(element)}
functionisStatic(node: ASTNode): boolean{if(node.type===2){// 说明是包含 {{ XXX }} 的文本,不属于静态returnfalse}if(node.type===3){// 纯文本,属于静态returntrue}return!!(node.pre||(!node.hasBindings&&// no dynamic bindings!node.if&&!node.for&&// not v-if or v-for or v-else!isBuiltInTag(node.tag)&&// not a built-inisPlatformReservedTag(node.tag)&&// not a component!isDirectChildOfTemplateFor(node)&&// 非在 v-for template 节点下的子节点Object.keys(node).every(isStaticKey)))}
For a node to qualify as a static root, it should have children that
are not just static text. Otherwise the cost of hoisting out will
outweigh the benefits and it's better off to just always render it fresh.
前言
在使用 vue 的时候,如果不用 .vue 格式来写,那么肯定用过
$mount
方法,包括我们创建一个 root 节点,传入了el
,vue也会帮我们调用$mount
方法。而$mount
方法是 vue 中最重要的一环之一,用于解析模板,生成 render function 。入口文件
$mount
方法的入口文件在src/entries/web-runtime-with-compiler
中,在 vue 2.x 中引入了预编译的概念。模板可以被预编译成 render function ,在页面中渲染的时候就不需要再去解析构建 AST 静态语法树了,对页面性能有一定的提升作用。因此入口文件分成了带 compiler 的,以及不带 compiler 的。在
web-runtime-with-compiler
中调用了一个compileToFunctions
的方法,而compileToFunctions
方法又调用了compile
方法以及makeFunction
方法。其中,所有的逻辑都在
compile
中,compile 后的对象里,就包含了 render function 字符串,然后再通过 makeFunction(就是 new Function()) 实例化一个 function 对象。进到compile 方法所在的文件可以看到就剪短的几行代码
parse方法
parse 方法在
src/compiler/parser/index.js
中,先看代码上面的正则,每个正则的用户都用注释写了一下然后看 parse 方法,可以看到,真正的解析方法是在里面的 parseHTML 中,调用 parseHTML 方法的时候,可以看到传入了多个参数,占主要逻辑的解析方法是其中的
start
,end
,chars
方法。其中 start 方法,主要是用来创建 type 为 1 的 tag 类 ASTElement。chars 方法是用来创建 type 为 2 或者 3 的文本类 ASTElement,end 方法是用来处理未闭合的标签。
先不用急着看这几个方法干了啥,先直接进入到 parseHTML 上看,也就是在
src/compiler/parser/html-parser.js
文件里。还是先看一下正则:
然后就是主要方法 parseHTML ,里面逻辑还是比较清晰。首先会使用
html.indexOf('<')
来获取需要处理的位置索引,如果位置为0,进入标签判断逻辑:会判断当前的<,是属于注释、还是IE的条件判断,还是Doctype,如果是这三者之一,基本上就是直接通过
advance
方法,更新剩余的html。接着判断 end tag,如果是 ,则在更新完 html 之后,调用
parseEndTag
方法,在这个方法里,就是遍历stack
列表,stack列表是用于存放此前创建的 的抽象,获取相匹配的,如果发现获取到的 不是在 stack 列表的最后一位,说明有未闭合的标签。直接调用options.end
方法,通知上一个文件里的移除未闭合的 ASTElement 。再进行 start tag 的判断,判断 start tag 的方法单独抽成了一个 parseStartTag 方法,而不是一个正则那么简单了,因为除了做标签判断之外,还要收集标签上的属性值。逻辑也比较简单:
先匹配 startTagOpen ,也就可以获取到 <xxx ...> 中的 <xxx 以及 xxx。
此时,如果 html 是
<div v-for="item in items">
,经过上面的处理,html就剩下v-for="item in items">
了。经过上面一些逻辑判断,就可以知道当前标签如果匹配上了,就再调用
handleStartTag
方法,对属性值做进一步处理,然后在判断当前标签需不需要闭合,如果是不需要闭合的标签就不需要塞入 stack ,如果需要闭合,就塞入 stack 。紧接着就调用了
options.start
方法,再回到上一个文件中,在 start 方法里,就是把 抽象成一个 ASTElement ,并且对其进行多层处理,逐个处理 v-for、v-if、等这些指令:指令的处理就不细说,就相当于打标签似的,比如当前 ASTElement 上有 v-once ,则给这个 ASTElement 加上一个
once=true
的节点,当然也不是都那么简单,会有一些特殊处理。可以说,每一个标签开合即<XXX>,都会被转成一个 type 为 1 的 ASTElement。处理完<XXX>,然后就通过下面这段逻辑,来获取上一个 <XXX> 和下一个 <XXX> 或者 </XXX> 等中间的文本内容。
直接举个例子:
比如要处理的 html 是
<div>123<123{{ item }}<span></span></div>
。然后再看 options.chars 里的逻辑。那里的逻辑会比较简单:
而里面调用的
parseText
,则是从文本中匹配出{{ XXX }}
, 并且转换成表达式的方法。逻辑也比较简单,通过正则匹配出{{ XXX }}
中的 XXX,再判断是否有 filter ,也就是是否为{{ XXX \| filterName }}
的格式,如果是,则转换成_f("filterName")(XXX)
的格式,如果不是,就直接是XXX
了,紧接着再包一层变成_f("filterName")(XXX)
或者_s(XXX)
。_s
是toString
方法,而_f
则是获取 filter 的方法。再举个解析的例子:
解析前
解析后
optimize 方法
经过
parse
方法的解析,此时获得是一个抽象出来的 AST 树对象。而 optimize 方法做的事情相对于 parse 来说就简单一些了,只是遍历 AST 树里的所有节点,然后标记其本身包括子树是否静态的,以便在每次界面重渲染的时候,可以不需要重新生成静态树的dom,而在部分数据发生改变引发的 patch 的时候,也可以完全跳过对静态树的检查。判断逻辑即:
简单概括,会被认为静态的节点为以下三种:
进行完
static
的标记,还会进行staticRoot
以及staticInFor
的标记。其中staticRoot
的标记表明该节点是个有子节点的静态节点。但是只会标记有一个文本节点以上的节点,因为按照源码里的注释说明,说如果只有一个文本子节点,那么对这个处理就有点得不偿失了。而
staticInFor
则是标记该节点及其子节点是否为在 v-for 下的静态节点。generate 方法
经过 optimize 处理后,就使用 generate 方法把 AST 转成 render function。看这个方法,需要配合
src/core/instance/render.js
一起看,才知道 render function 中的那些简写的方法是干嘛用的。可以看出,generate 方法是通过 genElement 方法生成 function string 然后使用 with 拼装后返回。其中的
_c
方法是vnode
中的createElement
方法,也就是会实例化一个VNode
对象,即虚拟dom。具体怎么生成就不细说,那一块是 vdom 里的,不在本文中细说。而在
genElement
方法中,也就是根据ASTElement的类型不同,组装出不同的 function string;从上往下,如果 root ASTElement 是一棵静态树,那么就会执行
genStatic
方法。代码中的
_m
方法,就是render.js
中的renderStatic
方法。其他还有
genFor
、genData
这些方法就不细说了,有兴趣的可以自行去看,这些方法均是使用src/core/instance/render.js
里的方法做包装,最后用with(this){ /** function string **/ }
包裹起来,那么里面引用的_m
方法之类的,就用的都是 vue instance 的方法了。EOF
The text was updated successfully, but these errors were encountered: