上节跑完了超长的parse函数:
// Line-9261 function baseCompile(template, options) { // Done! var ast = parse(template.trim(), options); // go! optimize(ast, options); var code = generate(ast, options); return { ast: ast, render: code.render, staticRenderFns: code.staticRenderFns } }
返回一个ast对象,包括attrs、attrsList、attrsMap、children、plain、tag、type等属性,如图:。
包含了DOM字符串中节点类型、属性、文本内容,接下来进入优化函数:optimize。
// Line-8648 function optimize(root, options) { if (!root) { return } // 缓存静态标签 isStaticKey = genStaticKeysCached(options.staticKeys || ''); isPlatformReservedTag = options.isReservedTag || no; // 标记非静态节点 markStatic$1(root); // 标记静态节点 markStaticRoots(root, false); }
首先函数会对静态属性进行缓存:
// Line-8558 function genStaticKeys$1(keys) { return makeMap( 'type,tag,attrsList,attrsMap,plain,parent,children,attrs' + (keys ? ',' + keys : '') ) }
可以看到,上面的好像全是静态的属性。
接下来调用markStatic$1标记非静态节点:
// Line-8648 function markStatic$1(node) { // 判断是否是静态节点 node.static = isStatic(node); if (node.type === 1) { // 静态节点不包括slot和template标签 if (!isPlatformReservedTag(node.tag) && node.tag !== 'slot' && node.attrsMap['inline-template'] == null ) { return } for (var i = 0, l = node.children.length; i < l; i++) { var child = node.children[i]; // 递归处理子节点 markStatic$1(child); // 子节点为false 父节点也是false if (!child.static) { node.static = false; } } } }
函数对节点做了三重判断,首先用isStatic方法判断类型。
// Line-8621 function isStatic(node) { if (node.type === 2) { // expression return false } if (node.type === 3) { // text return true } 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-in isPlatformReservedTag(node.tag) && // not a component !isDirectChildOfTemplateFor(node) && // 判断是否v-for的子节点 Object.keys(node).every(isStaticKey) // 遍历判断属性是否静态 )) }
排除type为2的表达式和3的文本,然后对属性进行遍历,若存在v-的动态属性,则会出现对应的属性,注释已经写出来了,这里就不做说明了。
由于本案例只有一个动态文本,所以这里返回的是true。
接着判断标签是否是slot或者template,此类动态标签不属性静态节点。
最后对子节点,即children属性中的内容进行递归处理。
函数完成后,ast对象会多一个属性,即static:false。
剩下一个是对静态节点进行标记:
// Line-8587 function markStaticRoots(node, isInFor) { if (node.type === 1) { if (node.static || node.once) { node.staticInFor = isInFor; } // 作为静态节点 必须保证有子节点并且不为纯文本 否则更新消耗较大 if (node.static && node.children.length && !( node.children.length === 1 && node.children[0].type === 3 )) { node.staticRoot = true; return } else { node.staticRoot = false; } // 递归处理子节点 if (node.children) { for (var i = 0, l = node.children.length; i < l; i++) { markStaticRoots(node.children[i], isInFor || !!node.for); } } // 无此属性 if (node.ifConditions) { walkThroughConditionsBlocks(node.ifConditions, isInFor); } } }
由于本例子节点是纯文本,所以staticRoot属性被标记为false。
经过optimize函数,ast对象被添加了两个静态属性:
最后是generate函数:
// Line-8799 function generate(ast, options) { // 保存上一个属性值 var prevStaticRenderFns = staticRenderFns; var currentStaticRenderFns = staticRenderFns = []; var prevOnceCount = onceCount; onceCount = 0; currentOptions = options; warn$3 = options.warn || baseWarn; transforms$1 = pluckModuleFunction(options.modules, 'transformCode'); dataGenFns = pluckModuleFunction(options.modules, 'genData'); platformDirectives$1 = options.directives || {}; isPlatformReservedTag$1 = options.isReservedTag || no; // 将ast对象转换为字符串 var code = ast ? genElement(ast) : '_c("div")'; staticRenderFns = prevStaticRenderFns; onceCount = prevOnceCount; return { render: ("with(this){return " + code + "}"), staticRenderFns: currentStaticRenderFns } }
这个函数主要部分是code那一块,将ast对象转换成自定义的字符串形式,但是由于本例只有很简单的属性和文本,所以摘取分支看一下。
// Line-8823 function genElement(el) { if ( /* staticRoot,once,for,if,template,slot */ ) { /* code */ } else { // component or element var code; if (el.component) { code = genComponent(el.component, el); } else { // 1 var data = el.plain ? undefined : genData(el); // 2 var children = el.inlineTemplate ? null : genChildren(el, true); code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")"; } // module transforms for (var i = 0; i < transforms$1.length; i++) { code = transforms$1[i](el, code); } return code } }
跳过所有属性的判断,直接进入最后的分支,在这里对节点与子节点分别做了处理。
首先看genData函数。
// Line-8937 function genData(el) { var data = '{'; if ( /* directives,key,ref,refInFor,pre,component */ ) { /* code */ } // style,class for (var i = 0; i < dataGenFns.length; i++) { data += dataGenFns[i](el); } // 进入这个分支 if (el.attrs) { data += "attrs:{" + (genProps(el.attrs)) + "},"; } if ( /* props,events,nativeEvents,slotTarget,scopedSlots,model,inline-template */ ) { /* code */ } data = data.replace(/,$/, '') + '}'; // v-bind data wrap if (el.wrapData) { data = el.wrapData(data); } return data }
可以看到跳过了大多数的判断,直接进入attrs,调用genProps函数并将之前{name:id,value:app}的对象传了进去。
// Line-9146 function genProps(props) { var res = ''; // 将name,value拼接成 "name":"value", 形式的字符串 for (var i = 0; i < props.length; i++) { var prop = props[i]; res += "\"" + (prop.name) + "\":" + (transformSpecialNewlines(prop.value)) + ","; } // 去掉最后的逗号 return res.slice(0, -1) }
遍历attrs数组,将键值拼接成对应的字符串,本例只有一个id属性,拼接后返回。
处理完后调用正则将最后的逗号去掉并加上对应的大括号,最后的wrapData属性也没有,所以直接返回data,最终结果如图所示:
第一步完事后,进行子节点处理:
// Line-8823 function genElement(el) { if ( /* code... */ ) { /* code... */ } else { var code; /* code... */ // 返回节点信息 var data = el.plain ? undefined : genData(el); // 子节点 var children = el.inlineTemplate ? null : genChildren(el, true); code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")"; // module transforms for (var i = 0; i < transforms$1.length; i++) { code = transforms$1[i](el, code); } return code } }
// Line-8823 function genChildren(el, checkSkip) { var children = el.children; if (children.length) { var el$1 = children[0]; // 对简单的v-for做优化 if (children.length === 1 && el$1.for && el$1.tag !== 'template' && el$1.tag !== 'slot') { return genElement(el$1) } // 对存在子DOM节点的对象做处理 不存在返回0 var normalizationType = checkSkip ? getNormalizationType(children) : 0; return ("[" + (children.map(genNode).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : '')) } } // Line-9107 function genNode(node) { if (node.type === 1) { return genElement(node) } else { return genText(node) } } function genText(text) { // 进行包装 return ("_v(" + (text.type === 2 ? text.expression // no need for () because already wrapped in _s() : transformSpecialNewlines(JSON.stringify(text.text))) + ")") }
调用genChildren后同样返回一个包装后的字符串:。
最后,将节点与内容结合,生成一个总的字符串,如图所示:
返回到generate函数:
// Line-8799 function generate(ast, options) { /* code */ var code = ast ? genElement(ast) : '_c("div")'; staticRenderFns = prevStaticRenderFns; onceCount = prevOnceCount; return { render: ("with(this){return " + code + "}"), staticRenderFns: currentStaticRenderFns } }
输出一个对象,返回到最初的baseCompile函数,除了ast,多出来的对象内容如图:
这个对象返回到compileToFunctions函数,目前进度是这样的:
// Line-9326 function compileToFunctions(template, options, vm) { options = options || {}; /* new Function检测 */ /* 缓存查询 */ // compile var compiled = compile(template, options); /* 输出返回的error和tips */ // 将字符串代码转化为函数 var res = {}; var fnGenErrors = []; res.render = makeFunction(compiled.render, fnGenErrors); var l = compiled.staticRenderFns.length; res.staticRenderFns = new Array(l); for (var i = 0; i < l; i++) { res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors); } /* checkError */ return (functionCompileCache[key] = res) }
返回的compiled如图所示:
接下来将render字符串重新转换为函数,makeFunction方法很简单,就是使用new Function生成一个函数:
// Line-9275 function makeFunction(code, errors) { try { return new Function(code) } catch (err) { errors.push({ err: err, code: code }); return noop } }
结果如图:
由于res.staticRenderFns是空,所以最后直接把该res缓存进functionCompileCache然后返回。
这个函数完事后,返回到了$mount方法中,很久之前的一个函数,内容如下:
// Line-9553 Vue$3.prototype.$mount = function( el, hydrating ) { el = el && query(el); /* warning */ var options = this.$options; // resolve template/el and convert to render function if (!options.render) { var template = options.template; if (template) { /* 获取template */ } else if (el) { template = getOuterHTML(el); } if (template) { /* compile start */ if ("development" !== 'production' && config.performance && mark) { mark('compile'); } var ref = compileToFunctions(template, { shouldDecodeNewlines: shouldDecodeNewlines, delimiters: options.delimiters }, this); var render = ref.render; var staticRenderFns = ref.staticRenderFns; options.render = render; options.staticRenderFns = staticRenderFns; /* compile end */ } } return mount.call(this, el, hydrating) };
调用完compileToFunctions后,返回的对象包含一个render函数,一个staticRenderFns属性,分别挂载到options参数上,然后再次调用mount方法。
结束~