先給出vue簡單的使用demo,經過建立一個Vue的實例,<div id="app"></div>將被替換成template模版中的內容,a,b的值也會被換成data屬性的值vue
<div id="app"></div> var vm = new Vue({ el: '#app', template: `<div> <p>{{a}}</p> <p>{{b}}</p> <div :class="a">btn1</div> <div @click="plus()">btn2</div> <div> <div> <div>str1</div> <div>str2</div> </div> <div>str3</div> </div> </div>`, data(){ return { a: 1, b: 2 } } })
如下的分析代碼都通過做者簡化,只爲簡單清楚的解析vue的實現邏輯
首先根據參數template屬性生成render函數node
function Vue (options) { this._init(options); } Vue.prototype._init = function (options) { var vm = this; //參數el屬性存在,就調用mount方法;初始化時也能夠不傳el屬性,後續調用mount方法 if (vm.$options.el) { vm.$mount(vm.$options.el); } } Vue.prototype.$mount = function () { var ref = compileToFunctions(template, { shouldDecodeNewlines: shouldDecodeNewlines, delimiters: options.delimiters, comments: options.comments }, this); //由template參數獲得render方法 var render = ref.render; //由template參數獲得最大靜態渲染樹 var staticRenderFns = ref.staticRenderFns; };
下面看下compileToFunctions生成render方法的具體實現正則表達式
var ast = parse(template.trim(), options); optimize(ast, options); var code = generate(ast, options);
首先根據template字符串生成ast對象,parse函數主要是經過正則表達式將str轉換成
樹結構的對象,ast對象基本結構以下:瀏覽器
而後對ast對象進行優化,找出ast對象中全部的最大靜態子樹(能夠簡單理解爲不包含參數data屬性的dom節點,每次data數據改變致使頁面從新渲染的時候,最大靜態子樹不須要從新計算生成),基本實現邏輯以下:app
function optimize (root, options) { //將ast對象的全部節點標記爲是否靜態 markStatic(root); markStaticRoots(root, false); } function markStatic (node) { //當前節點是否靜態 node.static = isStatic(node); //遞歸標記node的子節點是否靜態 for (var i = 0, l = node.children.length; i < l; i++) { var child = node.children[i]; markStatic(child); //只要有一個子節點非靜態,父節點也非靜態 if (!child.static) { node.static = false; } } } function markStaticRoots (node, isInFor) { //將包含至少一個非文本子節點(node.type === 3表明文本節點)的節點標記爲最大靜態樹的根節點 if (node.static && node.children.length && !( node.children.length === 1 && node.children[0].type === 3 )) { node.staticRoot = true; return } else { node.staticRoot = false; } //當前node節點不是靜態根節點,遞歸判斷子節點 if (node.children) { for (var i = 0, l = node.children.length; i < l; i++) { markStaticRoots(node.children[i], isInFor || !!node.for); } } }
本例中的最大靜態樹:
dom
最終生成的渲染函數code以下
函數
其中render是整個模版的渲染函數,staticrenderfns是靜態樹的渲染函數,staticrenderfns中的函數只會初始化一次,後續不須要再計算
render函數中用到的一些方法以下oop
function installRenderHelpers (target) { target._o = markOnce; target._n = toNumber; //轉換爲string對象 target._s = toString; target._l = renderList; target._t = renderSlot; target._q = looseEqual; target._i = looseIndexOf; target._m = renderStatic; target._f = resolveFilter; target._k = checkKeyCodes; target._b = bindObjectProps; //生成虛擬文本節點 target._v = createTextVNode; target._e = createEmptyVNode; target._u = resolveScopedSlots; target._g = bindObjectListeners; } //生成虛擬dom節點 vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
獲得render函數後會繼續調用下面的方法優化
function mountComponent ( updateComponent = function () { vm._update(vm._render(), hydrating); }; //對vue實例新建一個Watcher監聽對象,每當vm.data數據有變化,Watcher監聽到後負責調用updateComponent進行dom更新 vm._watcher = new Watcher(vm, updateComponent, noop); )
render函數調用後(vm._render())會生成vnode對象,也就是你們熟知的虛擬dom樹,調用update方法就能根據vonde更新真實的瀏覽器dom。
接下來咱們分析下updatecomponents是如何調用的,這就涉及到了vue經典的watch機制(此處先簡單介紹,下一篇會有較詳細的分析)。this
//new Watcher時會先調用一次updateComponent,後續會監聽vm.data的變化 var Watcher = function Watcher (vm,expOrFn){ if (typeof expOrFn === 'function') { this.getter = expOrFn; } this.get(); } Watcher.prototype.get = function get () { value = this.getter.call(vm, vm); }
最後再講一下 vm._update方法的實現
Vue.prototype._update = function (vnode, hydrating) { var prevVnode = vm._vnode; vm._vnode = vnode; //判斷vnode是否初始化過 if (!prevVnode) { // initial render vm.$el = vm.__patch__( // vm.$el是vm對象掛載的節點,本例是<div id="app"></div> // vm.$options._parentElm是組件掛載的節點(父節點),後面介紹組件時分析 vm.$el, vnode, hydrating, false /* removeOnly */, vm.$options._parentElm, vm.$options._refElm ); } else { // updates vm.$el = vm.__patch__(prevVnode, vnode); } } //vm.__pathch__方法 function function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm){ //根據vnode生成element並插入parentElm createElm(vnode, insertedVnodeQueue, parentElm, refElm); } //下面主要介紹初始化dom的實現 function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) { //根據vnode節點生成瀏覽器Element對象 vnode.elm = nodeOps.createElement(tag, vnode); var children = vnode.children; //遞歸將vnode子節點生成Element對象 createChildren(vnode, children, insertedVnodeQueue); //將生成的vnode.elm插入到瀏覽器的父節點當中 insert(parentElm, vnode.elm, refElm); } function createChildren (vnode, children, insertedVnodeQueue) { if (Array.isArray(children)) { for (var i = 0; i < children.length; ++i) { createElm(children[i], insertedVnodeQueue, vnode.elm, null, true); } //當vnode是文本節點時中止遞歸 } else if (isPrimitive(vnode.text)) { nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text)); } }