学习vue源码(14)深入学习diff

parent.appendChild(elm);

}

}

}

createElm

这个函数的作用跟它的名字一样,就是创建节点的意思

创建完节点之后,会调用 insert 去插入节点

你可以看一下,不难

function createElm(vnode, parentElm, refElm) {

var children = vnode.children;

var tag = vnode.tag;

if (tag) {

vnode.elm = document.createElement(tag)

// 先把 子节点插入 vnode.elm,然后再把 vnode.elm 插入parent

createChildren(vnode, children);

// 插入DOM 节点

insert(parentElm, vnode.elm, refElm);

}

else {

vnode.elm = document.createTextNode(vnode.text);

insert(parentElm, vnode.elm, refElm);

}

}

createElm 需要根据 Vnode 来判断需要创建什么节点

1、文本节点

当 vnode.tag 为 undefined 的时候,创建文本节点,看下 真实文本vnode

并且,文本节点是没有子节点的

2、普通节点

vnode.tag 有值,那就创建相应的 DOM

但是 该 DOM 可能存在子节点,所以子节点甚至子孙节点,也都是要创建的

所以会调用一个 createChildren 去完成所有子孙节点的创建

createChildren

这个方法处理子节点,必然是用遍历递归的方法逐个处理的

1 如果子节点是数组,则遍历执行 createElm 逐个处理

2 如果子节点的 text 属性有数据,则表示这个 vnode 是个文本节点,直接创建文本节点,然后插入到父节点中

function createChildren(vnode, children) {

if (Array.isArray(children)) {

for (var i = 0; i < children.length; ++i) {

createElm(children[i], vnode.elm, null);

}

}

else if (

typeof vnode.text=== ‘string’ ||

typeof vnode.text=== ‘number’ ||

typeof vnode.text=== ‘boolean’

) {

vnode.elm.appendChild(

document.createTextNode(vnode.text)

)

}

}

服务Diff工具函数

下面的函数是 Vue 专门用来服务 Diff 的,介绍两个

createKeyToOldIdx,sameVnode

createKeyToOldIdx

接收一个 children 数组,生成 key 与 index 索引对应的一个 map 表

function createKeyToOldIdx(

children, beginIdx, endIdx

) {

var i, key;

var map = {};

for (i = beginIdx; i <= endIdx; ++i) {

key = children[i].key;

if (key) {

map[key] = i;

}

}

return map

}

比如你的旧子节点数组是

[{

tag:“div”, key: “key_1”

},{

tag:“strong”, key:“key_2”

},{

tag:“span”, key:“key_4”

}]

经过 createKeyToOldIdx 生成一个 map 表 oldKeyToIdx,是下面这样

{

“key_1”:0,

“key_2”:1,

“key_4”:2

}

把 vnode 的 key 作为属性名,而该 vnode 在children 的位置 作为 属性值

这个函数在 Diff 中的作用是

判断某个新 vnode 是否在 这个旧的 Vnode 数组中,并且拿到它的位置。就是拿到 新 Vnode 的 key,然后去这个 map 表中去匹配,是否有相应的节点,有的话,就返回这个节点的位置

比如

现在我有一个 新子节点数组,一个 旧子节点数组

我拿到 新子节点数组 中的某一个 newVnode,我要判断他是否 和 旧子节点数组 中某个vnode相同

要怎么判断???难道是双重遍历数组,逐个判断 newVnode.key==vnode.key ??

Vue 用了更聪明的办法,使用 旧 Vnode 数组生成一个 map 对象 obj

当 obj[ newVnode.key ] 存在的时候,说明 新旧子节点数组都存在这个节点

并且我能拿到该节点在 旧子节点数组 中的位置(属性值)

反之,则不存在

这个方法也给我们提供了在项目中相似场景的一个解决思路,以对象索引查找替代数组遍历

希望大家记住哦

sameVnode

这个函数在 Diff 中也起到非常大的作用,大家务必要记住啊

它的作用是判断两个节点是否相同

这里说的相同,并不是完全一毛一样,而是关键属性一样,可以先看下源码

function sameVnode(a, b) {

return (

a.key === b.key &&

a.tag === b.tag &&

!!a.data === !!b.data &&

sameInputType(a, b)

)

}

function sameInputType(a, b) {

if (a.tag !== ‘input’) return true

var i;

var types = [

‘text’,‘number’,‘password’,

‘search’,‘email’,‘tel’,‘url’

]

var typeA = (i = a.data) && (i = i.attrs) && i.type;

var typeB = (i = b.data) && (i = i.attrs) && i.type;

// input 的类型一样,或者都属于基本input类型

return (

typeA === typeB ||

types.indexOf(typeA)>-1 &&

types.indexOf(typeB)>-1

)

}

判断的依据主要是 三点,key,tag,是否存在 data

这里判断的节点是只是相对于 节点本身,并不包括 children 在内

也就是说,就算data不一样,children 不一样,两个节点还是可能一样

比如下面这两个

有一种特殊情况,就是 input 节点

input 需要额外判断, 两个节点的 type 是否相同

或者

两个节点的类型可以不同,但是必须属于那些 input 类型

sameVnode 的内容就到这里了,但是我不禁又开始思考一个问题

为什么 sameVnode 会这么判断??

下面纯属个人意淫想法,仅供参考

sameVnode 应用在 Diff ,作用是为了判断节点是否需要新建

当两个 新旧vnode 进行 sameVnode 得到 false 的时候,说明两个vnode 不一样,会新建DOM插入

也就是两个节点从根本上不一样时才会创建

其中会比较 唯一标识符 key 和 标签名 tag,从而得到 vnode 是否一样 ,这些是毫无疑问的了

但是这里不需要判断 data 是否一样,我开始不太明白

后面想到 data 是包含有一些 dom 上的属性的,所以 data 不一样没有关系

因为就算不一样,他们还是基于同一个 DOM

因为DOM属性的值是可能是动态绑定动态更新变化的,所以变化前后的 两个 vnode,相应的 data 肯定不一样,但是其实他们是同一个 Vnode,所以 data 不在判断范畴

但是 data 在新旧节点中,必须都定义,或者都不定义

不存在 一个定义,而一个没定义, 但是会相同的 Vnode

比如,下面这个就会存在data

这个就不会存在data

他们在模板中,肯定是不属于同一个节点

总结

涉及的函数主要分为两类

一类是专门负责操作 DOM 的,insert,createElm,createChildren

这类函数比较通用,就算在我们自己的项目中也可以用得上

一类是专门特殊服务 Diff 的,createKeyToOldIdx,sameVnode

其中会包含一些项目的解决思路

大家务必先记住一下这几个函数,在下节内容的源码中会频繁出现

到时不会仔细介绍

4. Diff 流程


Diff 的内容不算多,但是如果要讲得很详细的话,就要说很多了,而且要配很多图

这是 Diff 的最后一节,最重要也是最详细的一节了

所以本节内容很多,先提个内容概览

1、分析 Diff 源码比较步骤

2、个人思考为什么如此比较

3、写个例子,一步步走个Diff 流程

文章很长,也非常详细,如果你对这内容有兴趣的话,也推荐边阅读源码边看

下面开始我们的正文

在之前,我们已经探索了 Vue 是如何从新建实例到开始diff 的

你应该还有印象,其中Diff涉及的一个重要函数就是 createPatchFunciton

var patch = createPatchFunction();

Vue.prototype.patch = patch

那么我们就来看下这个函数

createPatchFunction

function createPatchFunction() {

return function patch(

oldVnode, vnode, parentElm, refElm

) {

// 没有旧节点,直接生成新节点

if (!oldVnode) {

createElm(vnode, parentElm, refElm);

}

else {

// 且是一样 Vnode

if (sameVnode(oldVnode, vnode)) {

// 比较存在的根节点

patchVnode(oldVnode, vnode);

}

else {

// 替换存在的元素

var oldElm = oldVnode.elm;

var _parentElm = oldElm.parentNode

// 创建新节点

createElm(vnode, _parentElm, oldElm.nextSibling);

// 销毁旧节点

if (_parentElm) {

removeVnodes([oldVnode], 0, 0);

}

}

}

return vnode.elm

}

}

这个函数的作用就是

比较 新节点 和 旧节点 有什么不同,然后完成更新

所以你看到接收一个 oldVnode 和 vnode

处理的流程分为

1、没有旧节点

2、旧节点 和 新节点 自身一样(不包括其子节点)

3、旧节点 和 新节点自身不一样

速度来看下这三个流程了

1 .没有旧节点

没有旧节点,说明是页面刚开始初始化的时候,此时,根本不需要比较了

直接全部都是新建,所以只调用 createElm

2 .旧节点 和 新节点 自身一样

通过 sameVnode 判断节点是否一样,这个函数在上节中说过了

旧节点 和 新节点自身一样时,直接调用 patchVnode 去处理这两个节点

patchVnode 下面会讲到这个函数

在讲 patchVnode 之前,我们先思考这个函数的作用是什么?

当两个Vnode自身一样的时候,我们需要做什么?

首先,自身一样,我们可以先简单理解,是 Vnode 的两个属性 tag 和 key 一样

那么,我们是不知道其子节点是否一样的,所以肯定需要比较子节点

所以,patchVnode 其中的一个作用,就是比较子节点

3 .旧节点 和 新节点 自身不一样

当两个节点不一样的时候,不难理解,直接创建新节点,删除旧节点

patchVnode

在上一个函数 createPatchFunction 中,有出现一个函数 patchVnode

我们思考了这个函数的其中的一个作用是 比较两个Vnode 的子节点

是不是我们想的呢,可以先来过一下源码

function patchVnode(oldVnode, vnode) {

if (oldVnode === vnode) return

var elm = vnode.elm = oldVnode.elm;

var oldCh = oldVnode.children;

var ch = vnode.children;

// 更新children

if (!vnode.text) {

// 存在 oldCh 和 ch 时

if (oldCh && ch) {

if (oldCh !== ch)

updateChildren(elm, oldCh, ch);

}

// 存在 newCh 时,oldCh 只能是不存在,如果存在,就跳到上面的条件了

else if (ch) {

if (oldVnode.text) elm.textContent = ‘’;

for (var i = 0; i <= ch.length - 1; ++i) {

createElm(

ch[i],elm, null

);

}

}

else if (oldCh) {

for (var i = 0; i<= oldCh.length - 1; ++i) {

oldCh[i].parentNode.removeChild(el);

}

}

else if (oldVnode.text) {

elm.textContent = ‘’;

}

}

else if (oldVnode.text !== vnode.text) {

elm.textContent = vnode.text;

}

}

我们现在就来分析这个函数

没错,正如我们所想,这个函数的确会去比较处理子节点

总的来说,这个函数的作用是

1、Vnode 是文本节点,则更新文本(文本节点不存在子节点)

2、Vnode 有子节点,则处理比较更新子节点

更进一步的总结就是,这个函数主要做了两种判断的处理

1、Vnode 是否是文本节点

2、Vnode 是否有子节点

下面我们来看看这些步骤的详细分析

1 Vnode是文本节点

当 VNode 存在 text 这个属性的时候,就证明了 Vnode 是文本节点

我们可以先来看看 文本类型的 Vnode 是什么样子

所以当 Vnode 是文本节点的时候,需要做的就是,更新文本

同样有两种处理

1、当 新Vnode.text 存在,而且和 旧 VNode.text 不一样时

直接更新这个 DOM 的 文本内容

elm.textContent = vnode.text;

注:textContent 是 真实DOM 的一个属性, 保存的是 dom 的文本,所以直接更新这个属性

2、新Vnode 的 text 为空,直接把 文本DOM 赋值给空

elm.textContent = ‘’;

2 Vnode存在子节点

当 Vnode 存在子节点的时候,因为不知道 新旧节点的子节点是否一样,所以需要比较,才能完成更新

这里有三种处理

1、新旧节点 都有子节点,而且不一样

2、只有新节点

3、只有旧节点

后面两个节点,相信大家都能想通,但是我们还是说一下

1 只有新节点

只有新节点,不存在旧节点,那么没得比较了,所有节点都是全新的

所以直接全部新建就好了,新建是指创建出所有新DOM,并且添加进父节点的

2 只有旧节点

只有旧节点而没有新节点,说明更新后的页面,旧节点全部都不见了

那么要做的,就是把所有的旧节点删除

也就是直接把DOM 删除

3 新旧节点 都有子节点,而且不一样

咦惹,又出现了一个新函数,那就是 updateChildren

预告一下,这个函数非常的重要,是 Diff 的核心模块,蕴含着 Diff 的思想

可能会有点绕,但是不用怕,相信在我的探索之下,可以稍微明白些

同样的,我们先来思考下 updateChildren 的作用

记得条件,当新节点 和 旧节点 都存在,要怎么去比较才能知道有什么不一样呢?

哦没错,使用遍历,新子节点和旧子节点一个个比较

如果一样,就不更新,如果不一样,就更新

下面就来验证下我们的想法,来探索一下 updateChildren 的源码

updateChildren

这个函数非常的长,但是其实不难,就是分了几种处理流程而已,但是一开始看可能有点懵

或者可以先跳过源码,看下分析,或者便看分析边看源码

function updateChildren(parentElm, oldCh, newCh) {

var oldStartIdx = 0;

var oldEndIdx = oldCh.length - 1;

var oldStartVnode = oldCh[0];

var oldEndVnode = oldCh[oldEndIdx];

var newStartIdx = 0;

var newEndIdx = newCh.length - 1;

var newStartVnode = newCh[0];

var newEndVnode = newCh[newEndIdx];

var oldKeyToIdx, idxInOld, vnodeToMove, refElm;

// 不断地更新 OldIndex 和 OldVnode ,newIndex 和 newVnode

while (

oldStartIdx <= oldEndIdx &&

newStartIdx <= newEndIdx

) {

if (!oldStartVnode) {

oldStartVnode = oldCh[++oldStartIdx];

}

else if (!oldEndVnode) {

oldEndVnode = oldCh[–oldEndIdx];

}

// 旧头 和新头 比较

else if (sameVnode(oldStartVnode, newStartVnode)) {

patchVnode(oldStartVnode, newStartVnode);

oldStartVnode = oldCh[++oldStartIdx];

newStartVnode = newCh[++newStartIdx];

}

// 旧尾 和新尾 比较

else if (sameVnode(oldEndVnode, newEndVnode)) {

patchVnode(oldEndVnode, newEndVnode);

oldEndVnode = oldCh[–oldEndIdx];

newEndVnode = newCh[–newEndIdx];

}

// 旧头 和 新尾 比较

else if (sameVnode(oldStartVnode, newEndVnode)) {

patchVnode(oldStartVnode, newEndVnode);

// oldStartVnode 放到 oldEndVnode 后面,还要找到 oldEndValue 后面的节点

parentElm.insertBefore(

oldStartVnode.elm,

oldEndVnode.elm.nextSibling

);

oldStartVnode = oldCh[++oldStartIdx];

newEndVnode = newCh[–newEndIdx];

}

// 旧尾 和新头 比较

else if (sameVnode(oldEndVnode, newStartVnode)) {

patchVnode(oldEndVnode, newStartVnode);

// oldEndVnode 放到 oldStartVnode 前面

parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);

oldEndVnode = oldCh[–oldEndIdx];

newStartVnode = newCh[++newStartIdx];

}

// 单个新子节点 在 旧子节点数组中 查找位置

else {

// oldKeyToIdx 是一个 把 Vnode 的 key 和 index 转换的 map

if (!oldKeyToIdx) {

oldKeyToIdx = createKeyToOldIdx(

oldCh, oldStartIdx, oldEndIdx

);

}

// 使用 newStartVnode 去 OldMap 中寻找 相同节点,默认key存在

idxInOld = oldKeyToIdx[newStartVnode.key]

// 新孩子中,存在一个新节点,老节点中没有,需要新建

if (!idxInOld) {

// 把 newStartVnode 插入 oldStartVnode 的前面

createElm(

newStartVnode,

parentElm,

oldStartVnode.elm

);

}

else {

// 找到 oldCh 中 和 newStartVnode 一样的节点

vnodeToMove = oldCh[idxInOld];

if (sameVnode(vnodeToMove, newStartVnode)) {

patchVnode(vnodeToMove, newStartVnode);

// 删除这个 index

oldCh[idxInOld] = undefined;

// 把 vnodeToMove 移动到 oldStartVnode 前面

parentElm.insertBefore(

vnodeToMove.elm,

oldStartVnode.elm

);

}

// 只能创建一个新节点插入到 parentElm 的子节点中

else {

// same key but different element. treat as new element

createElm(

newStartVnode,

parentElm,

oldStartVnode.elm

);

}

}

// 这个新子节点更新完毕,更新 newStartIdx,开始比较下一个

newStartVnode = newCh[++newStartIdx];

}

}

// 处理剩下的节点

if (oldStartIdx > oldEndIdx) {

var newEnd = newCh[newEndIdx + 1]

refElm = newEnd ? newEnd.elm :null;

for (; newStartIdx <= newEndIdx; ++newStartIdx) {

createElm(

newCh[newStartIdx], parentElm, refElm

);

}

}

// 说明新节点比对完了,老节点可能还有,需要删除剩余的老节点

else if (newStartIdx > newEndIdx) {

for (; oldStartIdx<=oldEndIdx; ++oldStartIdx) {

oldCh[oldStartIdx].parentNode.removeChild(el);

}

}

}

首先要明确这个函数处理的是什么

处理的是 新子节点 和 旧子节点,循环遍历逐个比较

如何 循环遍历?

1、使用 while

2、新旧节点数组都配置首尾两个索引

新节点的两个索引:newStartIdx , newEndIdx

旧节点的两个索引:oldStartIdx,oldEndIdx

以两边向中间包围的形式 来进行遍历

头部的子节点比较完毕,startIdx 就加1

尾部的子节点比较完毕,endIdex 就减1

只要其中一个数组遍历完(startIdx<endIdx),则结束遍历

源码处理的流程分为两个

1、比较新旧子节点

2、比较完毕,处理剩下的节点

我们来逐个说明这两个流程

1 比较新旧子节点

注:这里有两个数组,一个是 新子Vnode数组,一个旧子Vnode数组

在比较过程中,不会对两个数组进行改变(比如不会插入,不会删除其子项)

而所有比较过程中都是直接 插入删除 真实页面DOM

我们明确一点,比较的目的是什么?

找到 新旧子节点中 的 相同的子节点,尽量以 移动 替代 新建 去更新DOM

只有在实在不同的情况下,才会新建

比较更新计划步骤:

首先考虑,不移动DOM

其次考虑,移动DOM

最后考虑,新建 / 删除 DOM

能不移动,尽量不移动。不行就移动,实在不行就新建

下面开始说源码中的比较逻辑

五种比较逻辑如下

1、旧头 == 新头

2、旧尾 == 新尾

3、旧头 == 新尾

4、旧尾 == 新头

5、单个查找

来分析下这五种比较逻辑

1 旧头 == 新头

sameVnode(oldStartVnode, newStartVnode)

当两个新旧的两个头一样的时候,并不用做什么处理

符合我们的步骤第一条,不移动DOM完成更新

但是看到一句,patchVnode

就是为了继续处理这两个相同节点的子节点,或者更新文本

因为我们不考虑多层DOM 结构,所以 新旧两个头一样的话,这里就算结束了

可以直接进行下一轮循环

newStartIdx ++ , oldStartIdx ++

2 旧尾 == 新尾

sameVnode(oldEndVnode, newEndVnode)

和 头头 相同的处理是一样的

尾尾相同,直接跳入下个循环

newEndIdx ++ , oldEndIdx ++

3 旧头 == 新尾

sameVnode(oldStartVnode, newEndVnode)

这步不符合 不移动DOM,所以只能 移动DOM 了

怎么移动?

源码是这样的

parentElm.insertBefore(

oldStartVnode.elm,

oldEndVnode.elm.nextSibling

);

以 新子节点的位置 来移动的,旧头 在新子节点的 末尾

所以把 oldStartVnode 的 dom 放到 oldEndVnode 的后面

但是因为没有把dom 放到谁后面的方法,所以只能使用 insertBefore

即放在 oldEndVnode 后一个节点的前面

图示是这样的

然后更新两个索引

oldStartIdx++,newEndIdx–

4 旧尾 == 新头

sameVnode(oldEndVnode, newStartVnode)

同样不符合 不移动DOM,也只能 移动DOM 了

怎么移动?

parentElm.insertBefore(

oldEndVnode.elm,

oldStartVnode.elm

);

把 oldEndVnode DOM 直接放到 当前 oldStartVnode.elm 的前面

图示是这样的

然后更新两个索引

oldEndIdx–,newStartIdx++

5 单个遍历查找

当前面四种比较逻辑都不行的时候,这是最后一种处理方法

拿 新子节点的子项,直接去 旧子节点数组中遍历,找一样的节点出来

流程大概是

1、生成旧子节点数组以 vnode.key 为key 的 map 表

2、拿到新子节点数组中 一个子项,判断它的key是否在上面的map 中

3、不存在,则新建DOM

4、存在,继续判断是否 sameVnode

下面就详细说一下

1 生成map 表

这个map 表的作用,就主要是判断存在什么旧子节点

比如你的旧子节点数组是

[{

tag:“div”, key:1

},{

tag:“strong”, key:2

},{

tag:“span”, key:4

}]

经过 createKeyToOldIdx 生成一个 map 表 oldKeyToIdx

{ vnodeKey: 数组Index }

属性名是 vnode.key,属性值是 该 vnode 在children 的位置

是这样(具体源码看上篇文章 Diff - 源码版 之 相关辅助函数)

oldKeyToIdx = {

1:0,

2:1,

4:2

}

2 判断 新子节点是否存在旧子节点数组中

拿到新子节点中的 子项Vnode,然后拿到它的 key

去匹配map 表,判断是否有相同节点

oldKeyToIdx[newStartVnode.key]

3 不存在旧子节点数组中

直接创建DOM,并插入oldStartVnode 前面

createElm(newStartVnode, parentElm, oldStartVnode.elm);

4 存在旧子节点数组中

找到这个旧子节点,然后判断和新子节点是否 sameVnode

如果相同,直接移动到 oldStartVnode 前面

如果不同,直接创建插入 oldStartVnode 前面

我们上面说了比较子节点的处理的流程分为两个

1、比较新旧子节点

2、比较完毕,处理剩下的节点

比较新旧子节点上面已经说完了,下面就到了另一个流程,比较剩余的节点,详情看下面

处理可能剩下的节点

在updateChildren 中,比较完新旧两个数组之后,可能某个数组会剩下部分节点没有被处理过,所以这里需要统一处理

1 新子节点遍历完了

newStartIdx > newEndIdx

新子节点遍历完毕,旧子节点可能还有剩

所以我们要对可能剩下的旧节点进行 批量删除!

就是遍历剩下的节点,逐个删除DOM

for (; oldStartIdx <= oldEndIdx; ++oldStartIdx) {

oldCh[oldStartIdx]

.parentNode

.removeChild(el);

}

2.旧子节点遍历完了

oldStartIdx > oldEndIdx

旧子节点遍历完毕,新子节点可能有剩

所以要对剩余的新子节点处理

很明显,剩余的新子节点不存在 旧子节点中,所以全部新建

for (; newStartIdx <= newEndIdx; ++newStartIdx) {

createElm(

newCh[newStartIdx],

parentElm,

refElm

);

}

但是新建有一个问题,就是插在哪里?

所以其中的 refElm 就成了疑点,看下源码

var newEnd = newCh[newEndIdx + 1]

refElm = newEnd ? newEnd.elm :null;

refElm 获取的是 newEndIdx 后一位的节点

当前没有处理的节点是 newEndIdx

也就是说 newEndIdx+1 的节点如果存在的话,肯定被处理过了

如果 newEndIdx 没有移动过,一直是最后一位,那么就不存在 newCh[newEndIdx + 1]

那么 refElm 就是空,那么剩余的新节点 就全部添加进 父节点孩子的末尾,相当于

for (; newStartIdx <= newEndIdx; ++newStartIdx) {

parentElm.appendChild(

newCh[newStartIdx]

);

}

框架相关

原生JS虽能实现绝大部分功能,但要么就是过于繁琐,要么就是存在缺陷,故绝大多数开发者都会首选框架开发方案。现阶段较热门是React、Vue两大框架,两者工作原理上存在共通点,也存在一些不同点,对于校招来说,不需要两个框架都学得特别熟,一般面试官会针对你简历中写的框架进行提问。

在框架方面,生命周期、钩子函数、虚拟DOM这些基本知识是必须要掌握的,在学习的过程可以结合框架的官方文档

Vue框架

知识要点:
1. vue-cli工程
2. vue核心知识点
3. vue-router
4. vuex
5. http请求
6. UI样式
7. 常用功能
8. MVVM设计模式

React框架

知识要点:
1. 基本知识
2. React 组件
3. React Redux
4. React 路由

4 存在旧子节点数组中

找到这个旧子节点,然后判断和新子节点是否 sameVnode

如果相同,直接移动到 oldStartVnode 前面

如果不同,直接创建插入 oldStartVnode 前面

我们上面说了比较子节点的处理的流程分为两个

1、比较新旧子节点

2、比较完毕,处理剩下的节点

比较新旧子节点上面已经说完了,下面就到了另一个流程,比较剩余的节点,详情看下面

处理可能剩下的节点

在updateChildren 中,比较完新旧两个数组之后,可能某个数组会剩下部分节点没有被处理过,所以这里需要统一处理

1 新子节点遍历完了

newStartIdx > newEndIdx

新子节点遍历完毕,旧子节点可能还有剩

所以我们要对可能剩下的旧节点进行 批量删除!

就是遍历剩下的节点,逐个删除DOM

for (; oldStartIdx <= oldEndIdx; ++oldStartIdx) {

oldCh[oldStartIdx]

.parentNode

.removeChild(el);

}

2.旧子节点遍历完了

oldStartIdx > oldEndIdx

旧子节点遍历完毕,新子节点可能有剩

所以要对剩余的新子节点处理

很明显,剩余的新子节点不存在 旧子节点中,所以全部新建

for (; newStartIdx <= newEndIdx; ++newStartIdx) {

createElm(

newCh[newStartIdx],

parentElm,

refElm

);

}

但是新建有一个问题,就是插在哪里?

所以其中的 refElm 就成了疑点,看下源码

var newEnd = newCh[newEndIdx + 1]

refElm = newEnd ? newEnd.elm :null;

refElm 获取的是 newEndIdx 后一位的节点

当前没有处理的节点是 newEndIdx

也就是说 newEndIdx+1 的节点如果存在的话,肯定被处理过了

如果 newEndIdx 没有移动过,一直是最后一位,那么就不存在 newCh[newEndIdx + 1]

那么 refElm 就是空,那么剩余的新节点 就全部添加进 父节点孩子的末尾,相当于

for (; newStartIdx <= newEndIdx; ++newStartIdx) {

parentElm.appendChild(

newCh[newStartIdx]

);

}

框架相关

原生JS虽能实现绝大部分功能,但要么就是过于繁琐,要么就是存在缺陷,故绝大多数开发者都会首选框架开发方案。现阶段较热门是React、Vue两大框架,两者工作原理上存在共通点,也存在一些不同点,对于校招来说,不需要两个框架都学得特别熟,一般面试官会针对你简历中写的框架进行提问。

在框架方面,生命周期、钩子函数、虚拟DOM这些基本知识是必须要掌握的,在学习的过程可以结合框架的官方文档

Vue框架

知识要点:
1. vue-cli工程
2. vue核心知识点
3. vue-router
4. vuex
5. http请求
6. UI样式
7. 常用功能
8. MVVM设计模式

[外链图片转存中…(img-qKot49KY-1718862464461)]

React框架

知识要点:
1. 基本知识
2. React 组件
3. React Redux
4. React 路由

[外链图片转存中…(img-Y3IPydg7-1718862464462)]

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值