公司网站要备案吗,北京网站推广技巧,机械加工网报价,十大财务软件排名文章目录 1. 前言2. 模板编译阶段分析2.1 两种$mount方法对比2.2 完整版的vm.$mount方法分析 3. 总结 1. 前言
前几篇文章中我们介绍了生命周期的初始化阶段#xff0c;我们知道#xff0c;在初始化阶段各项工作做完之后调用了vm.$mount方法#xff0c;该方法的调用标志着初… 文章目录 1. 前言2. 模板编译阶段分析2.1 两种$mount方法对比2.2 完整版的vm.$mount方法分析 3. 总结 1. 前言
前几篇文章中我们介绍了生命周期的初始化阶段我们知道在初始化阶段各项工作做完之后调用了vm.$mount方法该方法的调用标志着初始化阶段的结束和进入下一个阶段从官方文档给出的生命周期流程图中可以看到下一个阶段就进入了模板编译阶段该阶段所做的主要工作是获取到用户传入的模板内容并将其编译成渲染函数。 模板编译阶段并不是存在于Vue的所有构建版本中它只存在于完整版即vue.js中。在只包含运行时版本即vue.runtime.js中并不存在该阶段这是因为当使用vue-loader或vueify时*.vue文件内部的模板会在构建时预编译成渲染函数所以是不需要编译的从而不存在模板编译阶段由上一步的初始化阶段直接进入下一阶段的挂载阶段。
在这里我们有必要介绍一下什么是完整版和只包含运行时版。
vue基于源码构建的有两个版本一个是runtime only(一个只包含运行时的版本)另一个是runtime compiler(一个同时包含编译器和运行时的完整版本)。而两个版本的区别仅在于后者包含了一个编译器。 完整版本 一个完整的Vue版本是包含编译器的我们可以使用template选项进行模板编写。编译器会自动将template选项中的模板字符串编译成渲染函数的代码,源码中就是render函数。如果你需要在客户端编译模板 (比如传入一个字符串给 template 选项或挂载到一个元素上并以其 DOM 内部的 HTML 作为模板)就需要一个包含编译器的版本。 如下 // 需要编译器的版本
new Vue({template: div{{ hi }}/div
})只包含运行时版本 只包含运行时的版本拥有创建Vue实例、渲染并处理Virtual DOM等功能基本上就是除去编译器外的完整代码。该版本的适用场景有两种 1.我们在选项中通过手写render函数去定义渲染过程这个时候并不需要包含编译器的版本便可完整执行。 // 不需要编译器
new Vue({render (h) {return h(div, this.hi)}
})2.借助vue-loader这样的编译工具进行编译当我们利用webpack进行Vue的工程化开发时常常会利用vue-loader对*.vue文件进行编译尽管我们也是利用template模板标签去书写代码但是此时的Vue已经不需要利用编译器去负责模板的编译工作了这个过程交给了插件去实现。
很明显编译过程对性能会造成一定的损耗并且由于加入了编译的流程代码Vue代码的总体积也更加庞大(运行时版本相比完整版体积要小大约 30%)。因此在实际开发中我们需要借助像webpack的vue-loader这类工具进行编译将Vue对模板的编译阶段合并到webpack的构建流程中这样不仅减少了生产环境代码的体积也大大提高了运行时的性能一举两得。
为了完整的学习源码本篇文章将会分析完整版中的模板编译阶段到底做了些什么。
2. 模板编译阶段分析
上文中说了完整版和只包含运行时版之间的差异主要在于是否有模板编译阶段而是否有模板编译阶段主要表现在vm.$mount方法的实现上。此时你可能会有疑问照这么说$mount方法也有两个版本对的你可以这么理解但归根结底来说还是一种。我们分别来看一下。
2.1 两种$mount方法对比
只包含运行时版本的$mount代码如下
Vue.prototype.$mount function (el,hydrating) {el el inBrowser ? query(el) : undefined;return mountComponent(this, el, hydrating)
};在该版本中的$mount方法内部获取到el选项对应的DOM元素后直接调用mountComponent函数进行挂载操作关于该函数我们会在挂载阶段详细介绍。
而完整版本的$mount代码如下
var mount Vue.prototype.$mount;
Vue.prototype.$mount function (el,hydrating) {// 省略获取模板及编译代码return mount.call(this, el, hydrating)
}注意在完整版本的$mount定义之前先将Vue原型上的$mount方法先缓存起来记作变量mount。此时你可能会问了这$mount方法还没定义呢怎么先缓存起来了。
其实在源码中是先定义只包含运行时版本的$mount方法再定义完整版本的$mount方法所以此时缓存的mount变量就是只包含运行时版本的$mount方法。
为什么要这么做呢上文我们说了完整版本和只包含运行时版本之间的差异主要在于是否有模板编译阶段只包含运行时版本没有模板编译阶段初始化阶段完成后直接进入挂载阶段而完整版本是初始化阶段完成后进入模板编译阶段然后再进入挂载阶段。也就是说这两个版本最终都会进入挂载阶段。所以在完整版本的$mount方法中将模板编译完成后需要回头去调只包含运行时版本的$mount方法以进入挂载阶段。
这也就是在完整版本的$mount方法中先把只包含运行时版本的$mount方法缓存下来记作变量mount然后等模板编译完成再执行mount方法即只包含运行时版本的$mount方法。
所以分析模板编译阶段其实就是分析完整版的vm.$mount方法的实现。
2.2 完整版的vm.$mount方法分析
完整版的vm.$mount方法定义位于源码的src/platforms/web/entry-runtime-with-compiler.js中如下
var mount Vue.prototype.$mount;
Vue.prototype.$mount function (el,hydrating) {el el query(el);if (el document.body || el document.documentElement) {warn(Do not mount Vue to html or body - mount to normal elements instead.);return this}var options this.$options;// resolve template/el and convert to render functionif (!options.render) {var template options.template;if (template) {if (typeof template string) {if (template.charAt(0) #) {template idToTemplate(template);/* istanbul ignore if */if (!template) {warn((Template element not found or is empty: (options.template)),this);}}} else if (template.nodeType) {template template.innerHTML;} else {{warn(invalid template option: template, this);}return this}} else if (el) {template getOuterHTML(el);}if (template) {if (config.performance mark) {mark(compile);}var ref compileToFunctions(template, {outputSourceRange: development ! production,shouldDecodeNewlines: shouldDecodeNewlines,shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this);var render ref.render;var staticRenderFns ref.staticRenderFns;options.render render;options.staticRenderFns staticRenderFns;if (config.performance mark) {mark(compile end);measure((vue (this._name) compile), compile, compile end);}}}return mount.call(this, el, hydrating)
};从代码中可以看到该函数可大致分为三部分
根据传入的el参数获取DOM元素在用户没有手写render函数的情况下获取传入的模板template将获取到的template编译成render函数
接下来我们就逐一分析。
首先根据传入的el参数获取DOM元素。如下
el el query(el);function query (el) {if (typeof el string) {var selected document.querySelector(el);if (!selected) {warn(Cannot find element: el);return document.createElement(div)}return selected} else {return el}
}由于el参数可以是元素也可以是字符串类型的元素选择器所以调用query函数来获取到el对应的DOM元素。由于query函数比较简单就是根据传入的el参数是否为字符串从而以不同方式获取到对应的DOM元素这里就不逐行展开介绍了。
另外这里还多了一个判断就是判断获取到el对应的DOM元素如果是body或html元素时将会抛出警告。这是因为Vue会将模板中的内容替换el对应的DOM元素如果是body或html元素时替换之后将会破坏整个DOM文档所以不允许el是body或html。如下
if (el document.body || el document.documentElement) {warn(Do not mount Vue to html or body - mount to normal elements instead.);return this
}接着在用户没有手写render函数的情况下获取传入的模板template如下
if (!options.render) {var template options.template;if (template) {if (typeof template string) {if (template.charAt(0) #) {template idToTemplate(template);/* istanbul ignore if */if (!template) {warn((Template element not found or is empty: (options.template)),this);}}} else if (template.nodeType) {template template.innerHTML;} else {{warn(invalid template option: template, this);}return this}} else if (el) {template getOuterHTML(el);}
}首先获取用户传入的template选项赋给变量template如果变量template存在则接着判断如果template是字符串并且以##开头则认为template是id选择符则调用idToTemplate函数获取到选择符对应的DOM元素的innerHTML作为模板如下
if (template) {if (typeof template string) {if (template.charAt(0) #) {template idToTemplate(template);}}
}var idToTemplate cached(function (id) {var el query(id);return el el.innerHTML
});如果template不是字符串那就判断它是不是一个DOM元素如果是则使用该DOM元素的innerHTML作为模板如下
if (template.nodeType) {template template.innerHTML;
}如果既不是字符串也不是DOM元素此时会抛出警告提示用户template选项无效。如下
else {{warn(invalid template option: template, this);}return this
}如果变量template不存在表明用户没有传入template选项则根据传入的el参数调用getOuterHTML函数获取外部模板如下
if (el) {template getOuterHTML(el);
}function getOuterHTML (el) {if (el.outerHTML) {return el.outerHTML} else {var container document.createElement(div);container.appendChild(el.cloneNode(true));return container.innerHTML}
}不管是从内部的template选项中获取模板还是从外部获取模板总之就是要获取到用户传入的模板内容有了模板内容接下来才能将模板编译成渲染函数。
获取到模板之后接下来要做的事就是将其编译成渲染函数如下
if (template) {var ref compileToFunctions(template, {outputSourceRange: development ! production,shouldDecodeNewlines: shouldDecodeNewlines,shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this);var render ref.render;var staticRenderFns ref.staticRenderFns;options.render render;options.staticRenderFns staticRenderFns;
}关于将模板编译成渲染函数的具体步骤在前面文章模板编译篇中已经做了详细介绍在这里我们仅做简单回顾。
把模板编译成渲染函数是在compileToFunctions函数中进行的该函数接收待编译的模板字符串和编译选项作为参数返回一个对象对象里面的render属性即是编译好的渲染函数最后将渲染函数设置到$options上。
3. 总结
本篇文章介绍了生命周期中的第二个阶段——模板编译阶段。
首先介绍了Vue源码构建的两种版本完整版本和只包含运行时版本。并且我们知道了模板编译阶段只存在于完整版中在只包含运行时版本中不存在该阶段这是因为在只包含运行时版本中当使用vue-loader或vueify时*.vue文件内部的模板会在构建时预编译成渲染函数所以是不需要编译的从而不存在模板编译阶段。
然后对比了两种版本$mount方法的区别。它们的区别在于在$mount方法中是否进行了模板编译。在只包含运行时版本的$mount方法中获取到DOM元素后直接进入挂载阶段而在完整版本的$mount方法中是先将模板进行编译然后回过头调只包含运行时版本的$mount方法进入挂载阶段。
最后我们知道了分析模板编译阶段其实就是分析完整版的vm.$mount方法的实现我们将完整版的vm.$mount方法源码进行了逐行分析。知道了在该阶段中所做的工作就是从用户传入的el选项和template选项中获取到用户传入的内部或外部模板然后将获取到的模板编译成渲染函数。