.13-Vue源码之patch(3)(终于完事)
怎么感覺遙遙無期了呀~這個(gè)源碼,跑不完了。
這個(gè)系列寫的不好,僅作為一個(gè)記錄,善始善終,反正也沒人看,寫著玩吧!
?
接著上一節(jié)的cbs,這個(gè)對象在初始化應(yīng)該只會調(diào)用create模塊數(shù)組方法,簡單回顧一下到哪了。
// line-4944function invokeCreateHooks(vnode, insertedVnodeQueue) {// 遍歷調(diào)用數(shù)組方法// emptyNode => 空虛擬DOM// vnode => 當(dāng)前掛載的虛擬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);}}}后面的暫時(shí)不去看,依次執(zhí)行cbs.create中的方法:
一、updateAttrs
前面是對vnode的attrs進(jìn)行更新,__ob__屬性代表該對象被觀測,可能會變動,后面是對舊vnode屬性的移除。
// line-5456// 因?yàn)槭浅跏蓟?/span>// 此時(shí)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的屬性,調(diào)用setAttr進(jìn)行設(shè)置。
// 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};函數(shù)看似判斷很多很復(fù)雜,其實(shí)很簡單,只是根據(jù)屬性的類別做處理。本例中,直接會跳到最后的setAttribute,直接調(diào)用原生方法設(shè)置屬性,結(jié)果就是給div設(shè)置了id:app屬性。
因?yàn)閛ldVnode是空的,所以沒有屬性可以移除。
?
二、updateClass
這個(gè)從名字看就明白了,就是類名更換而已。
// 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;}}因?yàn)闆]有class,所有會直接返回了。
?
三、updateDOMListeners
這個(gè)是更新事件監(jiān)聽
// 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
這個(gè)是更新組件的props值
// line-6174function updateDOMProps(oldVnode, vnode) {if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {return}// code}因?yàn)榇a太長又不執(zhí)行,所以簡單跳過。
?
五、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
這個(gè)enter函數(shù)有點(diǎn)長,但是直接return,所以具體代碼就不貼出來了。
// line-6934function _enter(_, vnode) {// 第一參數(shù)沒有用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了}這個(gè)也return了。
?
八、updateDirectives
從名字來看是更新組件沒錯(cuò)了!
// line-5346function updateDirectives(oldVnode, vnode) {if (oldVnode.data.directives || vnode.data.directives) {_update(oldVnode, vnode);}}先判斷新舊節(jié)點(diǎn)是否有directives屬性,沒有直接跳過。
?
到這里,8個(gè)初始化方法全部調(diào)用完畢,函數(shù)返回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);}// 下一個(gè) 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);}}經(jīng)過createChildren后,vnode的elm屬性,也就是原生DOM會被添加各種屬性,然后進(jìn)入insert函數(shù)。
insert函數(shù)接受3個(gè)參數(shù),父節(jié)點(diǎn)、當(dāng)前節(jié)點(diǎn)、相鄰節(jié)點(diǎn),本例中對應(yīng)body、div、空白文本節(jié)點(diǎn)。
// 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);}}}這個(gè)函數(shù)前面也見過,簡單說一下,三個(gè)參數(shù),父、當(dāng)前、相鄰。
1、如果都存在,調(diào)用insertBefore將當(dāng)前插入相鄰前。
2、如果沒有相鄰節(jié)點(diǎn),直接調(diào)用appendChild把節(jié)點(diǎn)插入父。
這個(gè)調(diào)用完,頁面終于驚喜的出現(xiàn)了變化!
patch函數(shù)一階段完成,頁面已經(jīng)插入的對應(yīng)的DOM節(jié)點(diǎn)。
?
返回后,進(jìn)入第二階段,移除那個(gè){{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節(jié)點(diǎn)并插入頁面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}接下里會進(jìn)入removeVnodes函數(shù)對模板樣式進(jìn)行移除,至于另外分支是對舊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);}}}}函數(shù)很簡潔,一個(gè)分支處理DOM節(jié)點(diǎn),一個(gè)分支處理文本節(jié)點(diǎn)。本例中進(jìn)入第一個(gè)分支,將移除掛載的DOM節(jié)點(diǎn)。
有兩個(gè)方法處理移除DOM節(jié)點(diǎn):
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 {// 返回一個(gè)函數(shù)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();}}這個(gè)方法非常繞,處理的東西很多,然而本例只有一個(gè)DOM節(jié)點(diǎn)需要移除,所以跳過很多地方,直接執(zhí)行rm()函數(shù)。
即rm =>?removeNode() => parentNode.removeChild()。
最后成功移除原有的div。
?
但是沒完,移除DOM節(jié)點(diǎn)后,還需要處理vnode,于是進(jìn)行第二步invokeDestroyHook:
// line-4981function invokeDestroyHook(vnode) {var i, j;var data = vnode.data;if (isDef(data)) {// destroy鉤子函數(shù)?if (isDef(i = data.hook) && isDef(i = i.destroy)) {i(vnode);}// 主函數(shù)for (i = 0; i < cbs.destroy.length; ++i) {cbs.destroy[i](vnode);}}// 遞歸處理子節(jié)點(diǎn)if (isDef(i = vnode.children)) {for (j = 0; j < vnode.children.length; ++j) {invokeDestroyHook(vnode.children[j]);}}}這里與上面的cbs.create類似,也是遍歷調(diào)用對應(yīng)的數(shù)組方法,此處為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函數(shù)進(jìn)行最后一步。
// line-5341function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {// 插入移除節(jié)點(diǎn) invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);return vnode.elm}這里的invokeInsertHook處理鉤子函數(shù)的插入,由于不存在,所以也直接返回了。
接下來會一直返回到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;// 調(diào)用mounted鉤子函數(shù) if (vm.$vnode == null) {vm._isMounted = true;callHook(vm, 'mounted');}return vm}最后調(diào)用鉤子函數(shù)mounted,返回vue實(shí)例。
?
到此,流程全部跑完了。
啊~不容易。
轉(zhuǎn)載于:https://www.cnblogs.com/QH-Jimmy/p/7210363.html
總結(jié)
以上是生活随笔為你收集整理的.13-Vue源码之patch(3)(终于完事)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 贝叶斯公式的个人理解
- 下一篇: Vue源码后记-更多options参数(