}
Watcher.prototype.get = function () {
this.getter()
}
watcher 会保存更新回调,并且在新建 watcher 的时候就会立刻调用一遍更新回调
现在我们继续看 更新回调的内容
vm._update(vm._render());
如果你看到之前的文章应该知道这两个函数的作用
现在就来简单说一下
vm._render
生成页面模板对应的 Vnode 树,比如
生成的 Vnode 树是( 其中num的值是111 )
{
tag: “div”,
children:[{
tag: “span”
},{
tag: undefined,
text: “111”
}]
}
这一步是通过 compile 生成的,具体的话可以简单看下 学习vue源码(6)熟悉模板编译原理
vm._update
比较 旧Vnode 树 和 vm._render 生成的新 Vnode 树 进行比较
比较完后,更新页面的DOM,从而完成更新
ok,我们看下源码
Vue.prototype._update = function(vnode) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
vm._vnode = vnode;
// 不存在旧节点
if (!prevVnode) {
vm.$el = vm.patch(
vm.$el, vnode,
vm.$options._parentElm,
vm.$options._refElm
);
}
else {
vm.$el = vm.patch(
prevVnode, vnode
);
}
};
解释其中几个点
1 vm._vnode
这个属性保存的就是当前 Vnode 树
当页面开始更新,而生成了新的 Vnode 树之后
这个属性则会替换成新的Vnode
所以保存在这里,是为了方便拿到 旧 Vnode 树
2 vm.patch
是的,没有错,你在两处地方看到这个东西
这个东西就是 Diff 的主要内容,内有乾坤,内容很多,不会在这里说,毕竟今天只探索流程
但是要看看这个东西怎么来的
var patch = createPatchFunction();
Vue.prototype.patch = patch ;
嗯,是经过一个 createPatchFunciton 生成的
然后赋值到 Vue 的原型上,所以可以 vm.patch 调用喽
我们再来说说 _update 函数中出现的那两处 patch
(1) 不存在旧节点
不需要进行比较,直接全部创建
vm.$el
保存的是 DOM 节点,如果不存在旧节点,那么 vm.$el
此时也是不存在的
而传入 vm.$el
为空的时候,patch 拿到这个值判断为空的时候,就直接创建DOM,不会去做其他操作了
(2)存在旧节点
需要把旧节点和新节点比较,尽量找到最小差异部分,然后进行更新,这部分内容就是 Diff 的重点了,需要花费不少精力的。
好了,这一节就结束了。
在开始我们的 Diff 主要内容之前,我们先来了解下其中会用的的一些辅助函数
本来可以放到 Diff 那里写,但是全部合起来内容又太多
而且这些函数比较具有公用性,就是抽出来用
所以打算独立一节,先预热一下,内容也不多,也挺简单,光看下也会对我们的思维有所帮助
1.节点操作函数
下面是 Diff 在比较节点时,更新DOM 会使用到的一些函数
本来会有更多,为了看得方便,我把一些合并了
下面会介绍三个函数
insert,createElm,createChildren
简单介绍
insert 作用是把节点插入到某个位置
createElm 作用是创建DOM 节点
createChildren 作用也是创建DOM 节点,但是处理的是一个数组,并且会创建 DOM 节点 和 文本节点
下面就来仔细说说这三个方法
insert
这个函数的作用就是 插入节点
但是插入也会分两种情况
1、没有参考兄弟节点,直接插入父节点的子节点末尾
2、有参考兄弟节点,则插在兄弟节点前面
可以说这个函数是 Diff 的基石哈哈
function insert(parent, elm, ref) {
if (parent) {
if (ref) {
if (ref.parentNode === parent) {
parent.insertBefore(elm, ref);
}
} else {
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
其中会包含一些项目的解决思路
大家务必先记住一下这几个函数,在下节内容的源码中会频繁出现
到时不会仔细介绍
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 结构,所以 新旧两个头一样的话,这里就算结束了
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
更多面试题
**《350页前端校招面试题精编解析大全》**内容大纲主要包括 HTML,CSS,前端基础,前端核心,前端进阶,移动端开发,计算机基础,算法与数据结构,项目,职业发展等等
dx].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 结构,所以 新旧两个头一样的话,这里就算结束了
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-kbfwsM1x-1713777960840)]
[外链图片转存中…(img-Gc4zAKHo-1713777960840)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-RGkeYN2Q-1713777960840)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
[外链图片转存中…(img-hpNdMFas-1713777960841)]
更多面试题
**《350页前端校招面试题精编解析大全》**内容大纲主要包括 HTML,CSS,前端基础,前端核心,前端进阶,移动端开发,计算机基础,算法与数据结构,项目,职业发展等等
[外链图片转存中…(img-NTiTv4Zr-1713777960841)]