.13-Vue源码之patch(3)(终于完事)
怎么感覺遙遙無期了呀~這個源碼,跑不完了。
這個系列寫的不好,僅作為一個記錄,善始善終,反正也沒人看,寫著玩吧!
?
接著上一節的cbs,這個對象在初始化應該只會調用create模塊數組方法,簡單回顧一下到哪了。
// line-4944function invokeCreateHooks(vnode, insertedVnodeQueue) {// 遍歷調用數組方法// emptyNode => 空虛擬DOM// vnode => 當前掛載的虛擬DOMfor (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {cbs.create[i$1](emptyNode, vnode);}i = vnode.data.hook; // Reuse variableif (isDef(i)) {if (isDef(i.create)) {i.create(emptyNode, vnode);}if (isDef(i.insert)) {insertedVnodeQueue.push(vnode);}}}后面的暫時不去看,依次執行cbs.create中的方法:
一、updateAttrs
前面是對vnode的attrs進行更新,__ob__屬性代表該對象被觀測,可能會變動,后面是對舊vnode屬性的移除。
// line-5456// 因為是初始化// 此時oldVnode是空的vnodefunction updateAttrs(oldVnode, vnode) {// 老、新vnode都沒屬性就返回if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {return}var key, cur, old;var elm = vnode.elm;var oldAttrs = oldVnode.data.attrs || {};var attrs = vnode.data.attrs || {};// __ob__屬性代表可能變動if (isDef(attrs.__ob__)) {attrs = vnode.data.attrs = extend({}, attrs);}// attrs => {id:app}for (key in attrs) {cur = attrs[key];old = oldAttrs[key];// 更改屬性if (old !== cur) {setAttr(elm, key, cur);}}// IE9if (isIE9 && attrs.value !== oldAttrs.value) {setAttr(elm, 'value', attrs.value);}// 移除舊vnode的屬性for (key in oldAttrs) {if (isUndef(attrs[key])) {if (isXlink(key)) {elm.removeAttributeNS(xlinkNS, getXlinkProp(key));} else if (!isEnumeratedAttr(key)) {elm.removeAttribute(key);}}}}這里主要是最后遍歷新vnode的屬性,調用setAttr進行設置。
// line-5492// el => div// key => id // value => appfunction setAttr(el, key, value) {if (isBooleanAttr(key)) {// 處理無值屬性 如:disabled if (isFalsyAttrValue(value)) {el.removeAttribute(key);} else {el.setAttribute(key, key);}} else if (isEnumeratedAttr(key)) {el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true');} else if (isXlink(key)) {if (isFalsyAttrValue(value)) {el.removeAttributeNS(xlinkNS, getXlinkProp(key));} else {el.setAttributeNS(xlinkNS, key, value);}} else {if (isFalsyAttrValue(value)) {el.removeAttribute(key);} else {el.setAttribute(key, value);}}}var isBooleanAttr = makeMap('allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' +'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' +'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' +'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' +'required,reversed,scoped,seamless,selected,sortable,translate,' +'truespeed,typemustmatch,visible');var isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck');var isXlink = function(name) {// 以xlink:開頭的字符串return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink'};var isFalsyAttrValue = function(val) {return val == null || val === false};函數看似判斷很多很復雜,其實很簡單,只是根據屬性的類別做處理。本例中,直接會跳到最后的setAttribute,直接調用原生方法設置屬性,結果就是給div設置了id:app屬性。
因為oldVnode是空的,所以沒有屬性可以移除。
?
二、updateClass
這個從名字看就明白了,就是類名更換而已。
// line-5525function updateClass(oldVnode, vnode) {var el = vnode.elm;var data = vnode.data;var oldData = oldVnode.data;// 是否有定義class屬性if (isUndef(data.staticClass) &&isUndef(data.class) && (isUndef(oldData) || (isUndef(oldData.staticClass) &&isUndef(oldData.class)))) {return}var cls = genClassForVnode(vnode);// handle transition classesvar transitionClass = el._transitionClasses;if (isDef(transitionClass)) {cls = concat(cls, stringifyClass(transitionClass));}// set the classif (cls !== el._prevClass) {el.setAttribute('class', cls);el._prevClass = cls;}}因為沒有class,所有會直接返回了。
?
三、updateDOMListeners
這個是更新事件監聽
// line-6156function updateDOMListeners(oldVnode, vnode) {if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {return}var on = vnode.data.on || {};var oldOn = oldVnode.data.on || {};target$1 = vnode.elm;normalizeEvents(on);updateListeners(on, oldOn, add$1, remove$2, vnode.context);}很明顯,本例中也沒有事件,所以跳過
?
四、updateDOMProps
這個是更新組件的props值
// line-6174function updateDOMProps(oldVnode, vnode) {if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {return}// code}因為代碼太長又不執行,所以簡單跳過。
?
五、updateStyle
更新style屬性~
// line-6364function updateStyle(oldVnode, vnode) {var data = vnode.data;var oldData = oldVnode.data;if (isUndef(data.staticStyle) && isUndef(data.style) &&isUndef(oldData.staticStyle) && isUndef(oldData.style)) {return}// 跳過...}?
六、_enter
這個enter函數有點長,但是直接return,所以具體代碼就不貼出來了。
// line-6934function _enter(_, vnode) {// 第一參數沒有用if (vnode.data.show !== true) {enter(vnode);}}?
七、create
// line-4674create: function create(_, vnode) {registerRef(vnode);} // line-4688function registerRef(vnode, isRemoval) {var key = vnode.data.ref;if (!key) {return}// return了}這個也return了。
?
八、updateDirectives
從名字來看是更新組件沒錯了!
// line-5346function updateDirectives(oldVnode, vnode) {if (oldVnode.data.directives || vnode.data.directives) {_update(oldVnode, vnode);}}先判斷新舊節點是否有directives屬性,沒有直接跳過。
?
到這里,8個初始化方法全部調用完畢,函數返回createElm:
// line-4802function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested) {// 獲取屬性// ...if (isDef(tag)) {// warning... vnode.elm = vnode.ns ?nodeOps.createElementNS(vnode.ns, tag) :nodeOps.createElement(tag, vnode);setScope(vnode);/* istanbul ignore if */{// 從這里出來 createChildren(vnode, children, insertedVnodeQueue);if (isDef(data)) {invokeCreateHooks(vnode, insertedVnodeQueue);}// 下一個 insert(parentElm, vnode.elm, refElm);}if ("development" !== 'production' && data && data.pre) {inPre--;}} else if (isTrue(vnode.isComment)) {vnode.elm = nodeOps.createComment(vnode.text);insert(parentElm, vnode.elm, refElm);} else {vnode.elm = nodeOps.createTextNode(vnode.text);insert(parentElm, vnode.elm, refElm);}}經過createChildren后,vnode的elm屬性,也就是原生DOM會被添加各種屬性,然后進入insert函數。
insert函數接受3個參數,父節點、當前節點、相鄰節點,本例中對應body、div、空白文本節點。
// line-4915// parent => body// elm => vnode.elm => div// ref => #textfunction insert(parent, elm, ref) {if (isDef(parent)) {if (isDef(ref)) {if (ref.parentNode === parent) {nodeOps.insertBefore(parent, elm, ref);}} else {nodeOps.appendChild(parent, elm);}}}這個函數前面也見過,簡單說一下,三個參數,父、當前、相鄰。
1、如果都存在,調用insertBefore將當前插入相鄰前。
2、如果沒有相鄰節點,直接調用appendChild把節點插入父。
這個調用完,頁面終于驚喜的出現了變化!
patch函數一階段完成,頁面已經插入的對應的DOM節點。
?
返回后,進入第二階段,移除那個{{message}}:
// line-5250function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {// isUndef(vnode)...var isInitialPatch = false;var insertedVnodeQueue = [];if (isUndef(oldVnode)) {// ... } else {var isRealElement = isDef(oldVnode.nodeType);if (!isRealElement && sameVnode(oldVnode, vnode)) {// patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);} else {if (isRealElement) {// SSR// hydrating oldVnode = emptyNodeAt(oldVnode);}var oldElm = oldVnode.elm;var parentElm$1 = nodeOps.parentNode(oldElm);// createElm => 生成原生DOM節點并插入頁面if (isDef(vnode.parent)) {// ... }// go!if (isDef(parentElm$1)) {removeVnodes(parentElm$1, [oldVnode], 0, 0);} else if (isDef(oldVnode.tag)) {invokeDestroyHook(oldVnode);}}}invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);return vnode.elm}接下里會進入removeVnodes函數對模板樣式進行移除,至于另外分支是對舊vnode移除,不屬于頁面初始化階段。
// line-4995// parentElm => body// vnodes => 掛載虛擬DOM集合// startIdx => endIdx =>1function removeVnodes(parentElm, vnodes, startIdx, endIdx) {for (; startIdx <= endIdx; ++startIdx) {var ch = vnodes[startIdx];if (isDef(ch)) {if (isDef(ch.tag)) {removeAndInvokeRemoveHook(ch);invokeDestroyHook(ch);} else { // Text node removeNode(ch.elm);}}}}函數很簡潔,一個分支處理DOM節點,一個分支處理文本節點。本例中進入第一個分支,將移除掛載的DOM節點。
有兩個方法處理移除DOM節點:
1、removeAndInvokeRemoveHook
// line-5009function removeAndInvokeRemoveHook(vnode, rm) {if (isDef(rm) || isDef(vnode.data)) {var i;var listeners = cbs.remove.length + 1;if (isDef(rm)) {rm.listeners += listeners;} else {// 返回一個函數rm = createRmCb(vnode.elm, listeners);}// recursively invoke hooks on child component root nodeif (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {removeAndInvokeRemoveHook(i, rm);}// 會直接返回for (i = 0; i < cbs.remove.length; ++i) {cbs.remove[i](vnode, rm);}if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {i(vnode, rm);} else {// 最后跳這 rm();}} else {removeNode(vnode.elm);}}// line-4782function createRmCb(childElm, listeners) {// 這是rmfunction remove$$1() {if (--remove$$1.listeners === 0) {removeNode(childElm);}}remove$$1.listeners = listeners;return remove$$1}// line-6934remove: function remove$$1(vnode, rm) {/* istanbul ignore else */if (vnode.data.show !== true) {// 由于沒有data屬性 直接返回vm() leave(vnode, rm);} else {rm();}}這個方法非常繞,處理的東西很多,然而本例只有一個DOM節點需要移除,所以跳過很多地方,直接執行rm()函數。
即rm =>?removeNode() => parentNode.removeChild()。
最后成功移除原有的div。
?
但是沒完,移除DOM節點后,還需要處理vnode,于是進行第二步invokeDestroyHook:
// line-4981function invokeDestroyHook(vnode) {var i, j;var data = vnode.data;if (isDef(data)) {// destroy鉤子函數?if (isDef(i = data.hook) && isDef(i = i.destroy)) {i(vnode);}// 主函數for (i = 0; i < cbs.destroy.length; ++i) {cbs.destroy[i](vnode);}}// 遞歸處理子節點if (isDef(i = vnode.children)) {for (j = 0; j < vnode.children.length; ++j) {invokeDestroyHook(vnode.children[j]);}}}這里與上面的cbs.create類似,也是遍歷調用對應的數組方法,此處為destroy。
1、destroy
這里沒有ref屬性,直接返回了。
// line-4683destroy: function destroy(vnode) {registerRef(vnode, true);}function registerRef(vnode, isRemoval) {var key = vnode.data.ref;if (!key) {return}// more}2、unbindDirectives
直接返回了。
// line-5341destroy: function unbindDirectives(vnode) {updateDirectives(vnode, emptyNode);}function updateDirectives(oldVnode, vnode) {if (oldVnode.data.directives || vnode.data.directives) {_update(oldVnode, vnode);}}
后面也沒有什么,直接返回到patch函數進行最后一步。
// line-5341function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {// 插入移除節點 invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);return vnode.elm}這里的invokeInsertHook處理鉤子函數的插入,由于不存在,所以也直接返回了。
接下來會一直返回到mountComponent方法:
// line-2374function mountComponent(vm, el, hydrating) {vm.$el = el;// warning callHook(vm, 'beforeMount');var updateComponent;if ("development" !== 'production' && config.performance && mark) {// warning} else {updateComponent = function() {// 渲染DOM vm._update(vm._render(), hydrating);};}vm._watcher = new Watcher(vm, updateComponent, noop);hydrating = false;// 調用mounted鉤子函數 if (vm.$vnode == null) {vm._isMounted = true;callHook(vm, 'mounted');}return vm}最后調用鉤子函數mounted,返回vue實例。
?
到此,流程全部跑完了。
啊~不容易。
轉載于:https://www.cnblogs.com/QH-Jimmy/p/7210363.html
總結
以上是生活随笔為你收集整理的.13-Vue源码之patch(3)(终于完事)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 贝叶斯公式的个人理解
- 下一篇: ES6重点--笔记(转)