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
exportfunctioncompile(el,options,partial){// link function for the node itself.varnodeLinkFn=partial||!options._asComponent
? compileNode(el,options)
: null// link function for the childNodes// 如果nodeLinkFn.terminal为true,说明nodeLinkFn接管了整个元素和其子元素的编译过程,那也就不用编译el.childNodesvarchildLinkFn=!(nodeLinkFn&&nodeLinkFn.terminal)&&!isScript(el)&&el.hasChildNodes()
? compileNodeList(el.childNodes,options)
: nullreturnfunctioncompositeLinkFn(vm,el,host,scope,frag){// cache childNodes before linking parent, fix #657varchildNodes=toArray(el.childNodes)// link// 任何link都是包裹在linkAndCapture中执行的,详见linkAndCapture函数vardirs=linkAndCapture(functioncompositeLinkCapturer(){if(nodeLinkFn)nodeLinkFn(vm,el,host,scope,frag)if(childLinkFn)childLinkFn(vm,childNodes,host,scope,frag)},vm)returnmakeUnlinkFn(vm,dirs)}}
functioncompileTextNode(node,options){// skip marked text nodesif(node._skip){returnremoveText}vartokens=parseText(node.wholeText)// 没有token就意味着没有插值,// 没有插值那么内容不需要任何更改,也不会是响应式的数据if(!tokens){returnnull}// mark adjacent text nodes as skipped,// because we are using node.wholeText to compile// all adjacent text nodes together. This fixes// issues in IE where sometimes it splits up a single// text node into multiple ones.varnext=node.nextSiblingwhile(next&&next.nodeType===3){next._skip=truenext=next.nextSibling}varfrag=document.createDocumentFragment()varel,tokenfor(vari=0,l=tokens.length;i<l;i++){token=tokens[i]// '{{a}} vue {{b}}'这样一段插值得到的token中// token[1]就是' vue ',tag为false,// 直接用' vue ' createTextNode即可el=token.tag
? processTextToken(token,options)
: document.createTextNode(token.value)frag.appendChild(el)}returnmakeTextNodeLinkFn(tokens,frag,options)}/*** Process a single text token.** @param {Object} token* @param {Object} options* @return {Node}*/functionprocessTextToken(token,options){varelif(token.oneTime){el=document.createTextNode(token.value)}else{if(token.html){// 这个comment元素形成一个锚点的作用,告诉vue哪个地方应该插入v-html生成的内容el=document.createComment('v-html')setTokenType('html')}else{// IE will clean up empty textNodes during// frag.cloneNode(true), so we have to give it// something here...el=document.createTextNode(' ')setTokenType('text')}}functionsetTokenType(type){if(token.descriptor)return// parseDirective其实是解析出filters,// 比如 'msg | uppercase' // 就会生成{expression:'msg',filters:[过滤器名称和参数]}varparsed=parseDirective(token.value)token.descriptor={name: type,def: publicDirectives[type],expression: parsed.expression,filters: parsed.filters}}returnel}
compile
compile阶段执行的compileRoot函数就是编译我们在transclude阶段说过的,我们分别提取到了el顶级元素的属性和模板的顶级元素的属性,如果是component,那就需要把两者分开编译生成两个link。主要就是对属性编译,后续内容会细说属性编译,所以在此处不细说了,注释版源码在此。后面的resolveSlots出于篇幅考虑,也不再介绍,如有需求,请查看注释版源码。
我们来说说compile函数,他对元素执行compileNode,对其childNodes执行compileNodeList:
上面的代码中,我们看到了一个terminal属性,详见官网说明,其实就是终端指令这么个东东,比如v-if 因为元素是否存在和是否需要编译得视v-if的值而定(这个元素最终都不存在那就肯定不用浪费时间去编译他...- -),所以这个terminal指令接管了他和他的子元素的编译过程,由他来控制何时进行自己和后代的编译和link。
compile函数就是执行了compileNode和compileNodeList两个编译操作,他们分别编译了元素本身和元素的childNodes,然后将返回的两个link放在一个“组合link”函数里返回出去,link函数的内容我下节再说。
我们回头看看compileNode具体是怎么做的。至于compileNodeList其实是对应于多个元素情况下,对每个元素执行compileNode、对其childNodes递归执行compileNodeList,本质上就是遍历元素递归对每个元素执行compileNode。
可以看到很简单,compileNode就是判断了下node是元素节点还是文本节点,那我们分别看一下元素和文本节点是怎么编译的。
compileElement
代码过程中检测该元素是否有Terminal指令、是否是元素指令和component,这些情况下他们会接管元素及后代元素的编译过程。而一般情况下会执行compileDirectives,也就是编译元素上的属性。
我先说一下哪些属性需要处理的:
{{a}}
这样的形式比如id="item-{{ id }}"
,另外vue还支持html插值:{{{a}}}
和单次插值{{* a}}
。在属性里的插值,比如test="{{a}}"
其实等价于v-bind:test="a"
。v-model="a"
这样的vue指令,其不需要在value里写插值。compileDirectives代码较长,不便贴出。代码主要是首先对属性的value执行
parseText
,检测value中是否有插值的情况,若有则返回插值的处理结果:token数组。如果没返回token,那么在检测属性的name是否是Vue的提供的指令比如v-if
、transition
或者@xxxx
、:xxxx
之类。总之上述两种情况不管是那种出现了,就会对属性做进一步处理,比如拿属性的name执行parseModifiers,提取出属性中可能存在的修饰符,诸如此类,这些过程主要是使用正则表达式进行所需值的提取。
最终会生成这么一个指令描述对象,以
v-bind:href.literal="mylink"
为例:这就是指令描述对象,他包含了指令构造过程和执行过程的所有信息。对象中的
def
属性存放了指令定义对象。因为vue提供了大量的指令,并且也允许自定义指令,写过自定义指令的同学肯定清楚要定义的指令bind、updaate等方法。指令大运行过程都是一致的,不同就在于这些bind、update、优先级等细节,因此如果为这二三十个指令实现一个单独的类并根据指令描述对象手动调用对应的构造函数是不可取的。Vue是定义了一个统一的指令类Directive,在创建时Directive实例时,会把上述def
属性存放的具体指令的定义对象拷贝到this上,从而完成具体的指令的创建过程。回过头来说一说解析插值的parseText的具体执行过程,其核心过程就是这么几句代码(为方便理解,改了一下原版的),代码的注释已经解释清楚代码执行过程。
代码的执行结果就是把插值字符串转换成了一个token数组,每个token其实就是一个简单对象,里面的四个属性记录了对应的插值信息。这些token最终会存放在前述指令描述对象的interp字段里(interp为Interpolation简写)。
compileTextNode
说完了怎么处理element,那就看看另一种情况:textNode。
对于文本节点,我们只需要处理他的wholeText里面出现插值的情况,所以需要parseText解析他的value,如果没有插值,那就原样保持不动。接着新建一个fragment,最后对生成的tokens进行处理,处理过程遇到tag为false的就说明不是插值是纯字符串,那就直接
document.createTextNode(token.value)
(这种情况不会生成指令描述符,使得产生指令描述符并生成指令的情况只有纯插值的情况)。遇到插值token则创建对应元素,并在token的descriptor属性存放对应的指令描述符。这个指令描述符相比之前的指令描述符简单了很多,那是因为textNode只会对应v-bind、v-text和v-html三种指令,他们基本只需要expression即可。最终处理token过程中生成的元素都会添加到fragment里。这个fragment在link阶段link完毕后会替换掉模板dom里的对应节点,完成界面更新。The text was updated successfully, but these errors were encountered: