🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
>[success] # 模板编译代码分析 ~~~ 1.其实整个$mount方法在vue有两个,一个是专门解析模板在'platforms\web\runtime-with-compiler.js' 另一是直接解析渲染函数的在'\platforms\web\runtime\index.js', 2.我们解读分析的代码版本是包含'platforms\web\runtime-with-compiler.js'里面的$mount ~~~ >[info] ## 分析源码中的代码 ~~~ 1.之前说过即使使用了模板的方式,最后也转成了渲染函数,vue 也给我们提供了可以帮忙转换模板 的vue版本,也提供了不帮忙转换需要写渲染函数的版本,无论是那个版本都是$mount 作为入口 站在我的角度来说,我可能可以做一个变量来判断当前用户使用的版本然后一顿if-else来做区分, vue在这里是怎么做的呢? 2.在带编译版本的'$mount'使用前vue保留了他直接解析'渲染函数的$mount',可以看一下,下面的代码片段 ~~~ * 代码片段 ~~~js /*把原本不带编译的$mount方法保存下来,在最后会调用。*/ const mount = Vue.prototype.$mount // 这个是\platforms\web\runtime\index.js 只解析渲染函数的$mount /*挂载组件,带模板编译*/ Vue.prototype.$mount = function (){....} // platforms\web\runtime-with-compiler.js 中的$mount ~~~ >[danger] ##### 开始分析源码 ~~~ 1.这里要说明一下,下面源码中中文解释部分我使用的是'https://github.com/answershuto/learnVue' 这个github项目中 answershuto 大佬加的中文注释 2.通过源码可以解答上个章节第一个问题,'Vue 不能挂载在 body、html 这样的根节点上' 3.我们现在分析的是模板版本的$mount,可以发现他会先判断你是否有'render'这参数也是直接影响到vue 是使用了'渲染函数'的方式,还是'模板方式' ~~~ * 源码 ~~~js /*把原本不带编译的$mount方法保存下来,在最后会调用。*/ const mount = Vue.prototype.$mount /*挂载组件,带模板编译*/ Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function /*处理模板templete,编译成render函数,render不存在的时候才会编译template,否则优先使用render*/ if (!options.render) { let template = options.template /*template存在的时候取template,不存在的时候取el的outerHTML*/ if (template) { /*当template是字符串的时候*/ if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { /*当template为DOM节点的时候*/ template = template.innerHTML } else { /*报错*/ if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { /*获取element的outerHTML*/ template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } /*将template编译成render函数,这里会有render以及staticRenderFns两个返回,这是vue的编译时优化,static静态不需要在VNode更新时进行patch,优化性能*/ const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, delimiters: options.delimiters }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`${this._name} compile`, 'compile', 'compile end') } } } /*调用const mount = Vue.prototype.$mount保存下来的不带编译的mount*/ return mount.call(this, el, hydrating) } ~~~ >[danger] ##### 模板的使用方式分析 ~~~ 1.通过源码可以发现'模板的使用方式'有两种一种是你直接绑定'el' 中形式,一种是你使用'template'属性的 对这几种使用方式来做个说明 ~~~ * 先看el 的方式 ~~~ 1.先分析一下el这种情况,就需要分析'query(el)'方法在源码'platforms\web\util\index.js', 有一个'返回一个元素的DOM实例对象'方法 /*返回一个元素的DOM实例对象*/ export function query (el: string | Element): Element { if (typeof el === 'string') { const selected = document.querySelector(el) if (!selected) { process.env.NODE_ENV !== 'production' && warn( 'Cannot find element: ' + el ) return document.createElement('div') } return selected } else { return el } } 2.也就是说你的el如果是字符串可以使用'querySelector() 方法返回文档中匹配指定 CSS 选择器的一个元素' 或者你可以传入一个'dom实例对象' 因此我们可以写法如下 var vm = new Vue({ el: "#app", // 也可以绑定css选择器 }) var vm = new Vue({ el: document.getElementById('app'), // 也可以绑定css选择器 }) ~~~ * 在看template方式 ~~~ 1.第一步依旧需要el,第二步声明'template',这里要注意你在使用template不仅是在vue的opition中声明'template' 也需要声明el 2.而且template声明的内容要用template 标签包裹 3.即使你定义了el 里面的内容也会被template给替换掉 <body> <div id="app"> <p>{{msg}}</p> </div> <template id="app7777"> <div>{{msg}}123</div> </template> <script> var vm = new Vue({ el: "#app", template: document.getElementById('app7777'), data: { msg: "第一个案例" } }) </script> </body> // 或者直接绑定字符串dom形式 var vm = new Vue({ el: "#app", template: '<div>2222{{msg}}</div>', data: { msg: "第一个案例" } }) // 字符串id形式 var vm = new Vue({ el: "#app", template: '#app7777', data: { msg: "第一个案例" } }) ~~~ >[danger] ##### 现在来对着生命周期图来看这块 ~~~ 1.当然你实在不想写el 也可以这么写 new Vue({ render: h => h(App) }).$mount("#app"); ~~~ ![](https://img.kancloud.cn/13/14/13141c8cea9139bb9416f6c853e48d56_757x428.png) >[danger] ##### 小的细节代码说明 ~~~ 1.其实发现一段小的代码'idToTemplate(template)',这段代码是下面的代码组成的,利用闭包做的缓存的代码 function cached(fn) { const cache = Object.create(null) return (function cachedFn(str) { const hit = cache[str] return hit || (cache[str] = fn(str)) }) } /*根据id获取templete,即获取id对应的DOM,然后访问其innerHTML*/ const idToTemplate = cached(id => { const el = query(id) return el && el.innerHTML }) ~~~