.13-Vue源码之patch(3)(终于完事)
怎么感觉遥遥无期了呀~这个源码,跑不完了。
这个系列写的不好,仅作为一个记录,善始善终,反正也没人看,写着玩吧!
接着上一节的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)(终于完事)的更多相关文章
- Vue 源码解读(12)—— patch
前言 前面我们说到,当组件更新时,实例化渲染 watcher 时传递的 updateComponent 方法会被执行: const updateComponent = () => { // 执行 ...
- vue源码入口文件分析
开发vue项目有段时间了, 之前用angularjs 后来用 reactjs 但是那时候一直没有时间把自己看源码的思考记录下来,现在我不想再浪费这 来之不易的思考, 我要坚持!! 看源码我个人感觉非常 ...
- 【一套代码小程序&Native&Web阶段总结篇】可以这样阅读Vue源码
前言 前面我们对微信小程序进行了研究:[微信小程序项目实践总结]30分钟从陌生到熟悉 在实际代码过程中我们发现,我们可能又要做H5站又要做小程序同时还要做个APP,这里会造成很大的资源浪费,如果设定一 ...
- Vue源码解析(一):入口文件
在学习Vue源码之前,首先要做的一件事情,就是去GitHub上将Vue源码clone下来,目前我这里分析的Vue版本是V2.5.21,下面开始分析: 一.源码的目录结构: Vue的源码都在src目录下 ...
- Vue源码学习1——Vue构造函数
Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...
- 【Vuejs】350- 学习 Vue 源码的必要知识储备
前言 我最近在写 Vue 进阶的内容.在这个过程中,有些人问我看 Vue 源码需要有哪些准备吗?所以也就有了这篇计划之外的文章. 当你想学习 Vue 源码的时候,需要有扎实的 JavaScript 基 ...
- vue源码实现的整体流程解析
一.前言 最近一直在使用vue做项目,闲暇之余查阅了一些关于vue实现原理的资料,一方面对所了解到的知识做个总结,另外一方面希望能对看到此文章的同学有所帮助.本文如有不足之处,还请过往的大佬批评指正. ...
- Vue源码探究-虚拟DOM的渲染
Vue源码探究-虚拟DOM的渲染 在虚拟节点的实现一篇中,除了知道了 VNode 类的实现之外,还简要地整理了一下DOM渲染的路径.在这一篇中,主要来分析一下两条路径的具体实现代码. 按照创建 Vue ...
- vue源码的入口(四)
我们之前提到过 Vue.js 构建过程,在 web 应用下,我们来分析 Runtime + Compiler 构建出来的 Vue.js,它的入口是 src/platforms/web/entry-ru ...
- vue2源码分析:patch函数
目录 1.patch函数的脉络 2.类vnode的设计 3.createPatch函数中的辅助函数和patch函数 4.源码运行展示(DEMO) 一.patch函数的脉络 首先梳理一下patch函数的 ...
随机推荐
- linux(5)--补充(管道| / 重定向> / xargs)/find 与xargs结合使用/vi,grep,sed,awk(支持正则表达式的工具程序)
本节中正则表达式的工具程序 grep,sed和awk是重点,也是难点!!! 先补充一下一. 管道| / 重定向> / xargs 如:1. 管道和重定向的区别:具体可以见 http://www. ...
- mybatis-generato的功能扩展
项目代码地址:https://github.com/whaiming/java-generator 我在原有的基础上扩展了和修改了一些功能: 1.增加获取sqlServer数据库字段注释功能 2.Ma ...
- Tensorflow之卷积神经网络(CNN)
前馈神经网络的弊端 前一篇文章介绍过MNIST,是采用的前馈神经网络的结构,这种结构有一个很大的弊端,就是提供的样本必须面面俱到,否则就容易出现预测失败.如下图: 同样是在一个图片中找圆形,如果左边为 ...
- pycharm 2017新建文件添加编码方式等
file->setting->Editor->File and Code Templates->Python Script 添加 #!/usr/bin/python3# -*- ...
- Python迭代器,生成器--精华中的精华
1. 迭代器 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退.另外,迭代器的一大 ...
- Bmob云IM实现头像更换并存入Bmob云数据库中(1.拍照替换,2.相册选择)
看图效果如下: 1.个人资料界面 2.点击头像弹出对话框 3.点击拍照 4.切割图片,选择合适的部分 5.点击保存,头像替换完毕,下面看从相册中选择图片. 6.点击相册 7.任选一张图片 8.切割图片 ...
- angular学习-01,使用第三方库(jquery...)
开发环境(window) 1.安装node https://nodejs.org/en/ 2.安装angular-cli npm install -g @angular/cli 3.使用ng new ...
- bzoj2118(加法原理)(墨墨的等式)
题目大意:给定n个物品,每个物品有一个非负价值,问[L,R]区间内有多少价值可以被凑出来. 题意网上一大片,具体求解过程是利用了加法原理,将各个模数拥有的个数之和相加. 就是说随机取一个数a[k],那 ...
- Detect Capital
Given a word, you need to judge whether the usage of capitals in it is right or not. We define the u ...
- margin:0px auto和text-align:center区别
(1)margin:0px auto :作用于块级元素,对块级元素进行居中 (2)text-align:center:作用于内联元素,必须放在要居中的内联元素所在的块级元素. 例: (1) <d ...