.13-Vue源码之patch(3)(终于完事)

vue

怎么感觉遥遥无期了呀~这个源码,跑不完了。

这个系列写的不好,仅作为一个记录,善始善终,反正也没人看,写着玩吧!

  接着上一节的cbs,这个对象在初始化应该只会调用create模块数组方法,简单回顾一下到哪了。

    // line-4944

function invokeCreateHooks(vnode, insertedVnodeQueue) {

// 遍历调用数组方法

// emptyNode => 空虚拟DOM

// vnode => 当前挂载的虚拟DOM

for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {

cbs.create[i$1](emptyNode, vnode);

}

i = vnode.data.hook; // Reuse variable

if (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是空的vnode

function 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);

}

}

// IE9

if (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 => app

function 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-5525

function 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 classes

var transitionClass = el._transitionClasses;

if (isDef(transitionClass)) {

cls = concat(cls, stringifyClass(transitionClass));

}

// set the class

if (cls !== el._prevClass) {

el.setAttribute('class', cls);

el._prevClass = cls;

}

}

  因为没有class,所有会直接返回了。

三、updateDOMListeners

  这个是更新事件监听

    // line-6156

function 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-6174

function updateDOMProps(oldVnode, vnode) {

if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {

return

}

// code

}

  因为代码太长又不执行,所以简单跳过。

五、updateStyle

  更新style属性~

    // line-6364

function 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-6934

function _enter(_, vnode) {

// 第一参数没有用

if (vnode.data.show !== true) {

enter(vnode);

}

}

七、create

    // line-4674

create: function create(_, vnode) {

registerRef(vnode);

}

    // line-4688

function registerRef(vnode, isRemoval) {

var key = vnode.data.ref;

if (!key) {

return

}

// return了

}

  这个也return了。

八、updateDirectives

  从名字来看是更新组件没错了!

    // line-5346

function updateDirectives(oldVnode, vnode) {

if (oldVnode.data.directives || vnode.data.directives) {

_update(oldVnode, vnode);

}

}

  先判断新旧节点是否有directives属性,没有直接跳过。

  到这里,8个初始化方法全部调用完毕,函数返回createElm:

    // line-4802

function 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 => #text

function 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-5250

function 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 =>1

function 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-5009

function 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 node

if (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-4782

function createRmCb(childElm, listeners) {

// 这是rm

function remove$$1() {

if (--remove$$1.listeners === 0) {

removeNode(childElm);

}

}

remove$$1.listeners = listeners;

return remove$$1

}

// line-6934

remove: 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-4981

function 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-4683

destroy: function destroy(vnode) {

registerRef(vnode, true);

}

function registerRef(vnode, isRemoval) {

var key = vnode.data.ref;

if (!key) {

return

}

// more

}

2、unbindDirectives

  直接返回了。

    // line-5341

destroy: function unbindDirectives(vnode) {

updateDirectives(vnode, emptyNode);

}

function updateDirectives(oldVnode, vnode) {

if (oldVnode.data.directives || vnode.data.directives) {

_update(oldVnode, vnode);

}

}

  后面也没有什么,直接返回到patch函数进行最后一步。

    // line-5341

function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {

// 插入移除节点

invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);

return vnode.elm

}

  这里的invokeInsertHook处理钩子函数的插入,由于不存在,所以也直接返回了。

  接下来会一直返回到mountComponent方法:

    // line-2374

function 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实例。

  到此,流程全部跑完了。

  啊~不容易。

以上是 .13-Vue源码之patch(3)(终于完事) 的全部内容, 来源链接: utcz.com/z/377977.html

回到顶部