用法css
Vue有三個屬性和模板有關,官網上是這樣解釋的:html
el ;提供一個在頁面上已存在的 DOM 元素做爲 Vue 實例的掛載目標vue
template ;一個字符串模板做爲 Vue 實例的標識使用。模板將會 替換 掛載的元素。掛載元素的內容都將被忽略,除非模板的內容有分發插槽。node
render ;字符串模板的代替方案,容許你發揮 JavaScript 最大的編程能力。該渲染函數接收一個 createElement
方法做爲第一個參數用來建立 VNode
。編程
簡單說一下,就是:數組
Vue內部會判斷若是沒有render屬性則把template屬性的值做爲模板,若是template不存在則把el對應的DOM節點的outerHTML屬性做爲模板,通過一系列正則解析和流程生成一個render函數,最後經過with(this){}來執行。瀏覽器
也就是說template的優先級大於el。app
render的參數是Vue內部的$createElement函數(位於4486行),它的可擴展性更強一些,在一些項目的需求中,能夠用很簡單的代碼獲得一個模板。例如Vue實戰9.3裏介紹的例子,有興趣能夠看看函數
render能夠帶3個參數,分別以下:源碼分析
tag ;元素的標籤名,也能夠是組件名
data ;該VNode的屬性,是個對象
children ;子節點,是個數組
其中參數2能夠省略的,在4335行作了修正,最後執行_createElement()函數,以下:
function createElement ( //第4335行 context, tag, data, children, normalizationType, alwaysNormalize ) { if (Array.isArray(data) || isPrimitive(data)) { //若是data是個數組或者是基本類型 normalizationType = children; children = data; //修正data爲children data = undefined; //修正data爲undefined } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE; } return _createElement(context, tag, data, children, normalizationType) //最後執行_createElement建立一個虛擬VNode }
例以下面三個Vue實例,分別用el、template和rentder指定模板,它們的輸出是同樣的
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <script src="https://cdn.bootcss.com/vue/2.5.16/vue.js"></script> <title>Document</title> </head> <body> <div id="app1">{{message}}</div> <div id="app2"></div> <div id="app3"></div> <script> var data={message:'you are so annoying'} new Vue({el:'#app1',data}) //用el作模板 new Vue({el:'#app2',data,template:"<div>{{message}}</div>"}) //用template作模板 new Vue({el:'#app3',data,render:function(h){return h('div',this.message)}}) //直接用render函數指定模板 </script> </body> </html>
、瀏覽器顯示結果:
能夠看到輸出是一摸同樣的
Vue實例後會先執行_init()進行初始化,快結束時會判斷是否有el屬性,若是存在則調用$mount進行掛載,$mount函數以下:
Vue.prototype.$mount = function ( //定義在10861行 el, hydrating ) { el = el && query(el); /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { "development" !== 'production' && 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 function if (!options.render) { //若是render屬性不存在 var template = options.template; //則嘗試獲取template屬性並將其編譯成render if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template); /* istanbul ignore if */ if ("development" !== 'production' && !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) { //若是templtate不存在可是el存在,則獲取調用getOuterHTML()函數獲取el的outerHTML屬性,getOuterHTML()定義在10933行,也就是末尾,用戶獲取DOM的outerHTML template = getOuterHTML(el); } if (template) { /* istanbul ignore if */ if ("development" !== 'production' && config.performance && mark) { mark('compile'); } var ref = compileToFunctions(template, { shouldDecodeNewlines: shouldDecodeNewlines, shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this); //這裏調用compileToFunctions()將template解析成一個render函數,並返回 var render = ref.render; var staticRenderFns = ref.staticRenderFns; options.render = render; options.staticRenderFns = staticRenderFns; /* istanbul ignore if */ if ("development" !== 'production' && config.performance && mark) { mark('compile end'); measure(("vue " + (this._name) + " compile"), 'compile', 'compile end'); } } } return mount.call(this, el, hydrating) };
compileToFunctions函數是由createCompiler()返回的(這裏有點繞,研究代碼的時候在裏面繞了好幾天),我把大體主體貼出來,以下:
var baseOptions ={} //編譯的配置項 第9802行 function createCompileToFunctionFn(compile){ var cache = Object.create(null); return return function compileToFunctions(template, options, vm) { //編譯時先執行這裏 /**/ compile(template,options) /**/ } } function createCompilerCreator(baseCompile){ return function(baseOptions){ function compile(template, options) {/**/} return { compile: compile, compileToFunctions: createCompileToFunctionFn(compile) //難點:匿名函數返回的值中又調用了createCompileToFunctionFn函數 } } } var createCompiler = createCompilerCreator(function(){ //傳入一個匿名函數 var ast = parse(template.trim(), options); //編譯時,第二步:再執行這裏 if (options.optimize !== false) { optimize(ast, options); } var code = generate(ast, options); return {ast: ast,render: code.render,staticRenderFns: code.staticRenderFns} //最後返回一個對象 }) var ref$1 = createCompiler(baseOptions); var compileToFunctions = ref$1.compileToFunctions; //編譯的入口文件
是否是有點暈呢,我舉一個例子就能看明白了,以下:
function show(show){ //shou函數也直接返回一個匿名函數,帶一個參數 return function(info){ show(info) //show經過做用域鏈就能夠訪問到參數的show函數了 } } var info=show(function(info){ console.log(info) }) //這裏執行show函數,傳入一個匿名函數 info({name:'gsz'}) //控制檯輸出:{name: "gsz"}
Vue內部看得晦澀是由於傳參的時候都註明了一個函數名,其實這個函數名是能夠忽略的,這樣看起來會更清晰一點 注:這樣設計是爲了跨平臺一些代碼的複用和存放吧,代碼結構在node下更好理解一點
compileToFunctions函數內部會調用parse()將模板通過一系列的正則解析,用一個AST對象保存,而後調用generate()作靜態節點標記,最後調用generate生成一個render函數
以上面的第一個Vue實例來講,parse()解析後的AST對象以下:
、再經過generate()後生成以下一個對象,其中render就是最終要執行的render函數了
compileToFunctions函數返回值是一個對象,以上面的第一個vue實例爲例,返回後的信息以下:{ render:"(function anonymous() {with(this){return _c('div',{attrs:{"id":"app1"}},[_v(_s(message))])}})", //最終渲染出來的render函數 staticRenderFns:Function[] //若是是靜態節點,則保存到這裏}之後分析到每一個API時這裏會單獨分析的最後在mountcomponent()函數內會以當前Vue實例爲上下文,執行該render函數(在2739行),此時就會完成渲染watch的收集,並生成虛擬VNode,最後調用_update()方法生成真實DOM節點。