快完事咯!  

  简单看了下patch函数,虽然不长,但是实际上很长很长,慢慢来吧,

  首先来个总览:

    // line-5250
// oldVnode => 原生DOM节点
// vnode => 虚拟DOM
// hydrating => undefined
// removeOnly => false
// 后面两个undefined
function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
// 判断是否存在虚拟DOM
if (isUndef(vnode)) {
if (isDef(oldVnode)) {
invokeDestroyHook(oldVnode);
}
return
} var isInitialPatch = false;
var insertedVnodeQueue = []; if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
// 空挂载或作为组件
isInitialPatch = true;
createElm(vnode, insertedVnodeQueue, parentElm, refElm);
} else {
var isRealElement = isDef(oldVnode.nodeType);
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
} else {
if (isRealElement) {
// 判断是否SSR
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR);
hydrating = true;
}
// hydrating这个到底是啥????
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true);
return oldVnode
} else {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
);
}
}
// 两个都不是 新建空虚拟DOM
oldVnode = emptyNodeAt(oldVnode);
}
// 此处oldElm => div | parentElm$1 => body
var oldElm = oldVnode.elm;
var parentElm$1 = nodeOps.parentNode(oldElm);
createElm(
vnode,
insertedVnodeQueue,
// BUG:当节点处于leaving transition状态中不插入
oldElm._leaveCb ? null : parentElm$1,
// 获取相邻节点
nodeOps.nextSibling(oldElm)
); if (isDef(vnode.parent)) {
// component root element replaced.
// update parent placeholder node element, recursively
var ancestor = vnode.parent;
while (ancestor) {
ancestor.elm = vnode.elm;
ancestor = ancestor.parent;
}
if (isPatchable(vnode)) {
for (var i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode.parent);
}
}
} if (isDef(parentElm$1)) {
removeVnodes(parentElm$1, [oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
}
} invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}

  函数总共有6个形参,其中有3个为undefined,内部总共主要是条件判断,按照顺序跑下来。

一、

  由于不是SSR,也没有hydrating这个参数,所以会直接创建一个空的虚拟DOM作为oldVnode,如下:

    // line-4778
function emptyNodeAt(elm) {
return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
}

  

二、

  接下来到了createElm方法,代码如下:

    // line-4802
// vnode => 之前的虚拟DOM
// insertedVnodeQueue => [] => 空数组
// parentElm => body => 父元素
// refElm => 空白文本 => 节点的相邻元素
// nested => undefined
function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested) {
vnode.isRootInsert = !nested; // for transition enter check
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
} var data = vnode.data;
var children = vnode.children;
var tag = vnode.tag;
if (isDef(tag)) {
// error检测
{
if (data && data.pre) {
inPre++;
}
// 这个错误经常见!
if (!inPre &&
!vnode.ns &&
!(config.ignoredElements.length && config.ignoredElements.indexOf(tag) > -1) &&
config.isUnknownElement(tag)
) {
warn(
'Unknown custom element: <' + tag + '> - did you ' +
'register the component correctly? For recursive components, ' +
'make sure to provide the "name" option.',
vnode.context
);
}
}
// ns???
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);
}
}

  函数进入就会调用createComponent进行判断,这个函数暂时看不太懂,一个分支都没有进,直接返回了undefined:

    // line-4855
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
// 获取属性
var i = vnode.data;
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */ , parentElm, refElm);
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
}

  下一步是跳到createElement函数,nodeOps中的方法都是自封装的DOM操作方法,超级简单。

    // line-4599
function createElement$1(tagName, vnode) {
var elm = document.createElement(tagName);
if (tagName !== 'select') {
return elm
}
// 对select做特殊处理
// false or null will remove the attribute but undefined will not
if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {
elm.setAttribute('multiple', 'multiple');
}
return elm
}

  可以看,这个方法也就是创建一个DOM节点并返回,对select会做特殊处理,于是vnode.elm也就是个普通的DOM节点。

  接下来是setScope函数(),该函数目的是给根节点添加一个特殊的ID,避免css样式影响其他模块。

  这个东西我就熟悉啦,触发形式是这样:

  渲染结果是这样:

  这样可以保证所有的样式都是独立的,就算名字一样,后面添加的随机字符串可以保证唯一。

    // line-4958
function setScope(vnode) {
var i;
var ancestor = vnode;
while (ancestor) {
if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
nodeOps.setAttribute(vnode.elm, i, '');
}
ancestor = ancestor.parent;
}
// for slot content they should also get the scopeId from the host instance.
if (isDef(i = activeInstance) &&
i !== vnode.context &&
isDef(i = i.$options._scopeId)) {
nodeOps.setAttribute(vnode.elm, i, '');
}
}

  函数判断虚拟DOM是否存在_scopeId,然后对每一个节点添加对应的Id,这里没有,所以就会跳过去。

  

  接下来调用createChildren方法,从名字也能看出来是对子节点的处理。实际上,也是对子节点或者纯文本进行递归处理:

    // line-4927
function createChildren(vnode, children, insertedVnodeQueue) {
// 存在复杂子节点
if (Array.isArray(children)) {
for (var i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true);
}
}
// 子节点为纯文本
else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text));
}
}

  第一条分支处理DOM子节点,直接递归。本例子节点只是一个纯文本:

  因此,重新跳入createElm方法后,会直接进入第三个分支,即:

    // line-4802
function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested) {
// 属性获取
// ...
if (isDef(tag)) {
// 处理节点类型
} else if (isTrue(vnode.isComment)) {
// 处理注释
} else {
vnode.elm = nodeOps.createTextNode(vnode.text);
insert(parentElm, vnode.elm, refElm);
}
}

  这个分支只有两条语句,第一条会创建一个文本节点给elm属性,第二条负责插入。

  第一个DOM操作只是简单调用document.createTextNode方法,直接看第二个函数吧:

    // line-4915
// parent => div => 父节点
// elm => #text => 本节点
// ref => null => 相邻节点
function insert(parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (ref.parentNode === parent) {
//
nodeOps.insertBefore(parent, elm, ref);
}
}
// parent.appendChild(elm)
else {
nodeOps.appendChild(parent, elm);
}
}
}

  第二个也是DOM操作!好吧,比较简单,看看就懂了。

  

  子节点处理完后,接下来处理本节点的属性,相关函数为invokeCreateHooks:

    // line-4944
// vnode => 虚拟DOM
// insertedVnodeQueue => []
function invokeCreateHooks(vnode, insertedVnodeQueue) {
// cbs为工具对象
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对象,在外部函数运行最开始有进行初始化,如下:

    // line-4762
function createPatchFunction(backend) {
var i, j;
var cbs = {};
// line-6968
// var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
var modules = backend.modules;
var nodeOps = backend.nodeOps; for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = [];
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
cbs[hooks[i]].push(modules[j][hooks[i]]);
}
}
} // tools fn // return fn
}

  这个对象来源于多个对象的拼接,包含大量虚拟DOM的操作方法,内容如图:

  从名字可见,该对象整合了创建、摧毁、移除、更新等功能库,此处仅用create来创建,内部函数如图:

  invokeCreateHooks函数一开始遍会遍历此数组,依次传参执行,参数为空vnode与当前的vnode。

  这部分比较长,下次再讲。

  

.12-Vue源码之patch(2)的更多相关文章

  1. Vue 源码解读(12)—— patch

    前言 前面我们说到,当组件更新时,实例化渲染 watcher 时传递的 updateComponent 方法会被执行: const updateComponent = () => { // 执行 ...

  2. vue源码实现的整体流程解析

    一.前言 最近一直在使用vue做项目,闲暇之余查阅了一些关于vue实现原理的资料,一方面对所了解到的知识做个总结,另外一方面希望能对看到此文章的同学有所帮助.本文如有不足之处,还请过往的大佬批评指正. ...

  3. 【VUE】Vue 源码解析

    Vue 源码解析 Vue 的工作机制 在 new vue() 之后,Vue 会调用进行初始化,会初始化生命周期.事件.props.methods.data.computed和watch等.其中最重要的 ...

  4. Vue 源码解读(1)—— 前言

    当学习成为了习惯,知识也就变成了常识. 感谢各位的 点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog ...

  5. Vue源码后记-其余内置指令(3)

    其实吧,写这些后记我才真正了解到vue源码的精髓,之前的跑源码跟闹着玩一样. go! 之前将AST转换成了render函数,跳出来后,由于仍是字符串,所以调用了makeFunction将其转换成了真正 ...

  6. 大白话Vue源码系列(05):运行时鸟瞰图

    阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...

  7. vue源码入口文件分析

    开发vue项目有段时间了, 之前用angularjs 后来用 reactjs 但是那时候一直没有时间把自己看源码的思考记录下来,现在我不想再浪费这 来之不易的思考, 我要坚持!! 看源码我个人感觉非常 ...

  8. 入口文件开始,分析Vue源码实现

    Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...

  9. 入口开始,解读Vue源码(一)-- 造物创世

    Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...

  10. 【一套代码小程序&Native&Web阶段总结篇】可以这样阅读Vue源码

    前言 前面我们对微信小程序进行了研究:[微信小程序项目实践总结]30分钟从陌生到熟悉 在实际代码过程中我们发现,我们可能又要做H5站又要做小程序同时还要做个APP,这里会造成很大的资源浪费,如果设定一 ...

随机推荐

  1. python之time模块

    from time import * ''' class struct_time(tuple): pass ''' tuple1 = (, , , , , , , , ) s = struct_tim ...

  2. 这家IT教育公司太拼了:毕业生找不到工作就全额退学费!

    乐橙谷为了让更多的学生有工作,有高薪工作,已经决定尝试一种更刺激的游戏规则:完成课时的学员如果毕业找不到工作,公司将全额退还学费.这家公司秉承着自己的使命:以尊重的文化,敬畏的心态去努力帮助每个学生实 ...

  3. java 基础语法 2

    一.语句

  4. [AHOI2001]质数和分解

    [AHOI2001]质数和分解 题目描述 任何大于 1 的自然数 n 都可以写成若干个大于等于 2 且小于等于 n 的质数之和表达式(包括只有一个数构成的和表达式的情况),并且可能有不止一种质数和的形 ...

  5. Java的类加载器

    一.类加载器的概念 类加载器(class loader)用来加载 Java 类到 Java 虚拟机中.一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 ...

  6. WinForm事件中的Object sender和EventArgs e参数

    Windows程序有一个事件机制.用于处理用户事件. 在WinForm中我们经常需要给控件添加事件.例如给一个Button按钮添加一个Click点击事件.给TextBox文本框添加一个KeyPress ...

  7. node.js上除了Express还有哪些好用的web开发框架

    老司机都有体会, 开发本身没有多难, 最纠结其实是最初的技术和框架选型, 本没有绝对的好坏之分, 可一旦选择了不适合于自己业务场景的框架, 将来木已成舟后开发和维护成本都很高, 等发现不合适的时候更换 ...

  8. C# 7 局部函数剖析

    局部函数是C# 7中的一个新功能,允许在一个函数中定义另一个函数. 何时使用局部函数? 局部函数的主要功能与匿名方法非常相似:在某些情况下,创建一个命名函数在读者的认知负担方面代价太大.有时,函数本身 ...

  9. JDownload: 一款可以从网络上下载文件的小程序第四篇(整体架构描述)

    一 前言 时间过得真快,距离本系列博客第一篇的发布已经过去9个月了,本文是该系列的第四篇博客,将对JDownload做一个整体的描述与介绍.恩,先让笔者把记忆拉回到2017年年初,那会笔者在看Unix ...

  10. NOIP初赛 之 逻辑运算

    NOIP初赛 之 逻辑运算 逻辑运算先掌握各种运算,注意运算符的级别比较,做题是要细心.在NOIP中一般一题,分值为1.5分. 概念介绍: 非:not  ¬      与:and ∧      或:o ...