本文vue 版本爲 2.5.17, 分析的是 Runtime + Compiler 構建出來的 Vue.js。 在 Vue.js 2.0 中,最終都是經過 render 函數渲染,若是含有 template 屬性,則須要將 template 編譯成 render 函數,那麼這個編譯過程會發⽣運⾏時,因此須要帶有Compiler編譯器的版本。本文爲vue源碼介紹系列的第一篇,主要概括整合vue實例化,將render函數轉爲vnode到生成掛載真實dom主要流程,具體細節請查看源碼。第二篇將介紹組件化過程。vue
具體調試能夠下載vue.js,而後具體作斷點debugger調試。node
<script src="./vue.js"></script> <div id="app"></div> <script> var vm = new Vue({ el: '#app', render(h) { return h( 'div', { attr: { class: 'classname' } }, [ 'first item', h('h2', { style: {color: 'orange' } }, 'second item') ] ) }, data: { message: 'Hello', } }) </script>
先上圖分析流程瀏覽器
function Vue (options) { if ("development" !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword'); } this._init(options); } initMixin(Vue); // 定義 _init stateMixin(Vue); // 定義 $set $get $delete $watch 等 eventsMixin(Vue); // 定義事件 $on $once $off $emit lifecycleMixin(Vue); // 定義 _update $forceUpdate $destroy renderMixin(Vue); // 定義 _render 返回虛擬dom
實例化Vue時,執行 _init, _init 定義在 initMixin 中app
Vue.prototype._init = function (options) { // 合併 options if (options && options._isComponent) { initInternalComponent(vm, options); // 組件合併 } else { // 非組件合併 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); } initLifecycle(vm); // 定義 vm.$parent vm.$root vm.$children vm.$refs 等 initEvents(vm); // 定義 vm._events vm._hasHookEvent 等 initRender(vm); // 定義 $createElement $c callHook(vm, 'beforeCreate'); // 掛載 beforeCreate 鉤子函數 initInjections(vm); // resolve injections before data/props initState(vm); // 初始化 props methods data computed watch 等方法 initProvide(vm); // resolve provide after data/props callHook(vm, 'created'); // 掛載 created 鉤子函數 if (vm.$options.el) { vm.$mount(vm.$options.el); // 實例掛載渲染dom } };
vue最終都是經過render函數將dom編譯爲虛擬domdom
// 構建render函數 if (!options.render) { // 若是沒有render屬性,那麼將template模版編譯轉爲render } // 最後調用 mount return mount.call(this, el, hydrating) // mount 調用 mountComponent return mountComponent(this, el, hydrating)
經過 new Watcher 調用執行 updateComponent, vm._render獲取虛擬dom, vm._update將虛擬dom轉爲真實的dom並掛載到頁面ide
// hydrating 表明服務端渲染 hydrating => false updateComponent = function () { vm._update(vm._render(), hydrating); // 關鍵點 };
_render執行render函數 返回vnoe函數
Vue.prototype._render = function () { // 此處的 vm._renderProxy 等價於 vm vnode = render.call(vm._renderProxy, vm.$createElement); }
$createElement 主要是參數重載,整合爲統一格式後調用 _createElement函數oop
function createElement ( context, tag, data, children, normalizationType, alwaysNormalize) { // 參數重載 if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children; children = data; data = undefined; } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE; } return _createElement(context, tag, data, children, normalizationType) }
_createElement 主要是根據 tag 標籤判斷是組件仍是普通node標籤,返回對應的vnode虛擬dom組件化
function _createElement ( context, tag, data, children, normalizationType) { if (typeof tag === 'string') { // platform built-in elements vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ); }else{ // direct component options / constructor vnode = createComponent(tag, data, context, children); } }
_update 主要實現 vnode 轉化爲實際的dom, 注入到頁面的同時並銷燬頁面模版。ui
定義 _update
// _update => __patch__ Vue.prototype._update = function (vnode, hydrating) { if (!prevVnode) { // 初始化時 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */); } else { // 更新時 vm.$el = vm.__patch__(prevVnode, vnode); } }
定義 __patch__
// __patch__ => patch Vue.prototype.__patch__ = inBrowser ? patch : noop;
定義 patch,
// 利用函數柯里化,將服務端和瀏覽器的差別集成到modules, nodeOps爲dom元素操做方法集合 var modules = platformModules.concat(baseModules); var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
定義 createPatchFunction
function createPatchFunction (backend) { return function patch (oldVnode, vnode, hydrating, removeOnly) { // 建立新節點 createElm( vnode, insertedVnodeQueue, oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ); // 銷燬節點 if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0); } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode); } } }
定義 createElm, 根據vnode建立真實dom
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) { // createChildren函數由子到父,深序遞歸調用createElm createChildren(vnode, children, insertedVnodeQueue); if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue); } // 子節點插入到父節點 insert(parentElm, vnode.elm, refElm); }
以上就是vue從實例化,到調用render函數生成vnode,vnode經過patch轉爲真實dom節點,並掛載到頁面的流程狀況。接下來將第二篇將接掃vue組件化和生命週期過程。