Vue源码学习(十四):diff算法patch比对
好家伙,
本篇将会解释要以下效果的实现
1.目标
我们要实现以下元素替换的效果
gif:

以上例子的代码:
//创建vnode
let vm1 = new Vue({data:{name:'张三'}})
let render1 = compileToFunction(`<a>{{name}}</a>`)
let vnode1 = render1.call(vm1)
document.body.appendChild(createELm(vnode1)) //数据更新
let vm2 = new Vue({data:{name:'李四'}})
let render2 = compileToFunction(`<div>{{name}}</div>`)
let vnode2 = render2.call(vm2)
//属性添加
let vm3 = new Vue({data:{name:'李四'}})
let render3 = compileToFunction(`<div style="color:red">{{name}}</div>`)
let vnode3 = render3.call(vm3) //patch 比对
setTimeout(()=>{
patch(vnode1,vnode2)
},2000) setTimeout(()=>{
patch(vnode2,vnode3)
},3000)
以上例子中compileToFunction()方法的详细解释
Vue源码学习(四):<templete>渲染第三步,将ast语法树转换为渲染函数
一句话解释,这是一个将模板变为render函数的方法
开搞:
思路非常简单,依旧是对不同的情况分类处理
2.代码解释
patch.js
export function patch(oldVnode, Vnode) {
//原则 将虚拟节点转换成真实的节点
console.log(oldVnode, Vnode)
console.log(oldVnode.nodeType)
console.log(Vnode.nodeType)
//第一次渲染 oldVnode 是一个真实的DOM
//判断ldVnode.nodeType是否唯一,意思就是判断oldVnode是否为属性节点
if (oldVnode.nodeType === 1) {
console.log(oldVnode, Vnode) //注意oldVnode 需要在加载 mount 添加上去 vm.$el= el
let el = createELm(Vnode) // 产生一个新的DOM
let parentElm = oldVnode.parentNode //获取老元素(app) 父亲 ,body
// console.log(oldVnode)
// console.log(parentElm)
parentElm.insertBefore(el, oldVnode.nextSibling) //当前真实的元素插入到app 的后面
parentElm.removeChild(oldVnode) //删除老节点
//重新赋值
return el
}else{ // diff
console.log(oldVnode.nodeType)
console.log(oldVnode, Vnode)
//1 元素不是一样
if(oldVnode.tag!==Vnode.tag){
//旧的元素 直接替换为新的元素
return oldVnode.el.parentNode.replaceChild(createELm(Vnode),oldVnode.el)
}
//2 标签一样 text 属性 <div>1</div> <div>2</div> tag:undefined
if(!oldVnode.tag){
if(oldVnode.text !==Vnode.text){
return oldVnode.el.textContent = Vnode.text
}
}
//2.1属性 (标签一样) <div id='a'>1</div> <div style>2</div>
//在updataRpors方法中处理
//方法 1直接复制
let el = Vnode.el = oldVnode.el
updataRpors(Vnode,oldVnode.data)
//diff子元素 <div>1</div> <div></div>
let oldChildren = oldVnode.children || []
let newChildren = Vnode.children || []
if(oldChildren.length>0&&newChildren.length>0){ //老的有儿子 新有儿子
//创建方法
updataChild(oldChildren,newChildren,el)
}else if(oldChildren.length>0&&newChildren.length<=0){//老的元素 有儿子 新的没有儿子
el.innerHTML = ''
}else if(newChildren.length>0&&oldChildren.length<=0){//老没有儿子 新的有儿子
for(let i = 0;i<newChildren.length;i++){
let child = newChildren[i]
//添加到真实DOM
el.appendChild(createELm(child))
}
}
}
}
function updataChild (oldChildren,ewChildren,el){
}
//添加属性
function updataRpors(vnode,oldProps={}){ //第一次
let newProps = vnode.data ||{} //获取当前新节点 的属性
let el = vnode.el //获取当前真实节点 {}
//1老的有属性,新没有属性
for(let key in oldProps){
if(!newProps[key]){
//删除属性
el.removeAttribute[key] //
}
}
//2演示 老的 style={color:red} 新的 style="{background:red}"
let newStyle = newProps.style ||{} //获取新的样式
let oldStyle = oldProps.style ||{} //老的
for(let key in oldStyle){
if(!newStyle[key]){
el.style =''
}
}
//新的
for(let key in newProps){
if(key ==="style"){
for(let styleName in newProps.style){
el.style[styleName] = newProps.style[styleName]
}
}else if( key ==='class'){
el.className = newProps.class
}else{
el.setAttribute(key,newProps[key])
}
}
}
//vnode 变成真实的Dom
export function createELm(vnode) {
let { tag, children, key, data, text } = vnode
//注意
if (typeof tag === 'string') { //创建元素 放到 vnode.el上
vnode.el = document.createElement(tag) //创建元素
updataRpors(vnode)
//有儿子
children.forEach(child => {
// 递归 儿子 将儿子渲染后的结果放到 父亲中
vnode.el.appendChild(createELm(child))
})
} else { //文本
vnode.el = document.createTextNode(text)
}
return vnode.el //新的dom
}
三个方法,我们一个个看
2.1.patch()
export function patch(oldVnode, Vnode) {
//原则 将虚拟节点转换成真实的节点
console.log(oldVnode, Vnode)
console.log(oldVnode.nodeType)
console.log(Vnode.nodeType)
//第一次渲染 oldVnode 是一个真实的DOM
//判断ldVnode.nodeType是否唯一,意思就是判断oldVnode是否为属性节点
if (oldVnode.nodeType === 1) {
console.log(oldVnode, Vnode) //注意oldVnode 需要在加载 mount 添加上去 vm.$el= el
let el = createELm(Vnode) // 产生一个新的DOM
let parentElm = oldVnode.parentNode //获取老元素(app) 父亲 ,body
// console.log(oldVnode)
// console.log(parentElm)
parentElm.insertBefore(el, oldVnode.nextSibling) //当前真实的元素插入到app 的后面
parentElm.removeChild(oldVnode) //删除老节点
//重新赋值
return el
}else{ // diff
console.log(oldVnode.nodeType)
console.log(oldVnode, Vnode)
//1 元素不是一样
if(oldVnode.tag!==Vnode.tag){
//旧的元素 直接替换为新的元素
return oldVnode.el.parentNode.replaceChild(createELm(Vnode),oldVnode.el)
}
//2 标签一样 text 属性 <div>1</div> <div>2</div> tag:undefined
if(!oldVnode.tag){
if(oldVnode.text !==Vnode.text){
return oldVnode.el.textContent = Vnode.text
}
}
//2.1属性 (标签一样) <div id='a'>1</div> <div style>2</div>
//在updataRpors方法中处理
//方法 1直接复制
let el = Vnode.el = oldVnode.el
updataRpors(Vnode,oldVnode.data)
//diff子元素 <div>1</div> <div></div>
let oldChildren = oldVnode.children || []
let newChildren = Vnode.children || []
if(oldChildren.length>0&&newChildren.length>0){ //老的有儿子 新有儿子
//创建方法
updataChild(oldChildren,newChildren,el)
}else if(oldChildren.length>0&&newChildren.length<=0){//老的元素 有儿子 新的没有儿子
el.innerHTML = ''
}else if(newChildren.length>0&&oldChildren.length<=0){//老没有儿子 新的有儿子
for(let i = 0;i<newChildren.length;i++){
let child = newChildren[i]
//添加到真实DOM
el.appendChild(createELm(child))
}
}
}
}
patch()方法用于根据新的虚拟节点更新旧的虚拟节点以及对应的真实 DOM 元素。
首先判断旧的虚拟节点是否是一个真实 DOM 元素(即是否为属性节点),
--1--如果是,则表示这是第一次渲染,需要使用 createELm 函数创建新的 DOM 元素,并将其插入到旧的元素之前,最后再删除旧的元素,返回新创建的元素。
--2--如果不是第一次渲染,则进行 diff 操作,
--2.1--首先判断新老节点的标签是否相同,如果不同,则直接使用新的节点替换旧的节点。
--2.2--如果标签相同,则需要判断节点的文本内容和属性是否发生了变化,如果发生了变化,则通过 updataRpors 函数更新 DOM 元素属性或文本内容。
--2.3--最后,需要 diff 子元素。
--2.3.1--如果旧节点和新节点均有子元素,则需要将新旧子元素进行比较,通过 updataChild 函数更新旧节点的子元素与新节点的子元素。
--2.3.2--如果旧节点有子元素而新节点没有,则直接将旧节点的内容清空;
--2.3.3--如果新节点有子元素而旧节点没有,则直接将新节点的子元素添加到旧节点中。
2.2.updataRpors()
//添加属性
function updataRpors(vnode,oldProps={}){ //第一次
let newProps = vnode.data ||{} //获取当前新节点 的属性
let el = vnode.el //获取当前真实节点 {}
//1老的有属性,新没有属性
for(let key in oldProps){
if(!newProps[key]){
//删除属性
el.removeAttribute[key] //
}
}
//2演示 老的 style={color:red} 新的 style="{background:red}"
let newStyle = newProps.style ||{} //获取新的样式
let oldStyle = oldProps.style ||{} //老的
for(let key in oldStyle){
if(!newStyle[key]){
el.style =''
}
}
//新的
for(let key in newProps){
if(key ==="style"){
for(let styleName in newProps.style){
el.style[styleName] = newProps.style[styleName]
}
}else if( key ==='class'){
el.className = newProps.class
}else{
el.setAttribute(key,newProps[key])
}
}
}
updataRpors()是一个更新属性的方法,其主要功能是更新虚拟节点的属性,包括删除不再存在的属性、更新样式和类名等。
2.3.createELm()
//vnode 变成真实的Dom
export function createELm(vnode) {
let { tag, children, key, data, text } = vnode
//注意
if (typeof tag === 'string') { //创建元素 放到 vnode.el上
vnode.el = document.createElement(tag) //创建元素
updataRpors(vnode)
//有儿子
children.forEach(child => {
// 递归 儿子 将儿子渲染后的结果放到 父亲中
vnode.el.appendChild(createELm(child))
})
} else { //文本
vnode.el = document.createTextNode(text)
}
return vnode.el //新的dom
}
createELm()是一个用于创建和渲染虚拟DOM的函数.
函数名称为`createELm`,它接收一个参数`vnode`,这个参数是一个虚拟DOM节点对象。
这段代码的主要作用是根据传入的虚拟DOM节点数据结构(`vnode`)创建一个相应的实际DOM元素,并返回该元素。
如果虚拟DOM节点包含子节点,它会递归地为每个子节点创建相应的DOM元素并添加到父节点的DOM元素中。
Vue源码学习(十四):diff算法patch比对的更多相关文章
- Vue源码学习1——Vue构造函数
Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...
- Vue源码学习三 ———— Vue构造函数包装
Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...
- 最新 Vue 源码学习笔记
最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...
- Linux0.11源码学习(四)
Linux0.11源码学习(四) linux0.11源码学习笔记 参考资料: https://github.com/sunym1993/flash-linux0.11-talk https://git ...
- Vue源码学习二 ———— Vue原型对象包装
Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...
- 【Vue源码学习】依赖收集
前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...
- VUE 源码学习01 源码入口
VUE[version:2.4.1] Vue项目做了不少,最近在学习设计模式与Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后 ...
- Vue源码学习(一):调试环境搭建
最近开始学习Vue源码,第一步就是要把调试环境搭好,这个过程遇到小坑着实费了点功夫,在这里记下来 一.调试环境搭建过程 1.安装node.js,具体不展开 2.下载vue项目源码,git或svn等均可 ...
- Vue 源码学习(1)
概述 我在闲暇时间学习了一下 Vue 的源码,有一些心得,现在把它们分享给大家. 这个分享只是 Vue源码系列 的第一篇,主要讲述了如下内容: 寻找入口文件 在打包的过程中 Vue 发生了什么变化 在 ...
- 【Vue源码学习】响应式原理探秘
最近准备开启Vue的源码学习,并且每一个Vue的重要知识点都会记录下来.我们知道Vue的核心理念是数据驱动视图,所有操作都只需要在数据层做处理,不必关心视图层的操作.这里先来学习Vue的响应式原理,V ...
随机推荐
- Python数据分析易错知识点归纳(二):Numpy
二.numpy 不带括号的基本属性 arr.dtype arr.shape # 返回元组 arr.size arr.ndim # 维度 arr.reshape/arr.resize/np.resize ...
- 2023年郑州轻工业大学校赛邀请赛mjh
首先,很感谢老师的信任,选择我去参加此次比赛:其次,感谢wh,wyh两位队友的全程带飞.此次比赛是我第一次参加线下的比赛活动,内心难免激动与紧张. 比赛开始,队友直接找到了签到题进行解答,而我拿到了看 ...
- 2023-07-23:给你 n 个任务和 m 个工人 每个任务需要一定的力量值才能完成 需要的力量值保存在下标从 0 开始的整数数组 tasks 中 第 i 个任务需要 tasks[i] 的力量才能完
2023-07-23:给你 n 个任务和 m 个工人 每个任务需要一定的力量值才能完成 需要的力量值保存在下标从 0 开始的整数数组 tasks 中 第 i 个任务需要 tasks[i] 的力量才能完 ...
- 输入平方米的三种方式㎡ m2 m²
如何在Word中输入平方米字符? 第1种方法 Win10自带输入法,输入"平方米",默认第5个就出来了㎡,也可以直接复制使用. 这种方式最直接,字母m和右上角的2是1个字符,所以不 ...
- Linux 命令:lsof
参考文档:lsof命令详解 lsof,列出系统中所有打开的文件. 各列字段意义如下: COMMAND: 进程的名称 PID: 进程标识符 USER: 进程所有者 FD: 文件描述符,应用程序通过文件描 ...
- 重返照片的原始世界:我为.NET打造的RAW照片解析利器
重返照片的原始世界:我为.NET打造的RAW照片解析利器 如果你是我的老读者,你可能还记得,在2019年,我冒险进入了一片神秘的领域--用C#解析RAW格式的照片: 20191208 - 用.NET解 ...
- 青少年CTF-Web-帝国CMS1-3通关记录
0x01说明 本次进通过平台内题目进行,非真实环境. 帝国CMS01 首先下发题目链接 我们首先先找后台看看 后台地址为/e/admin/ 随后,经过dirsearch进行扫描,得到了一个www.zi ...
- Elasticsearch 保姆级入门篇
Elasticsearch 是一个分布式的.面向生产规模工作负载优化的搜索引擎. Kibana 可以将 Elasticsearch 中的数据转化为直观的图表.图形和仪表盘. 这篇文章,您将学习本地安装 ...
- 使用redis pipeline提升性能
前言 本篇来介绍一下redis pipeline,主要是由于最近一次在帮开发同学review代码的时候,发现对redis有个循环操作可以优化.场景大概是这样的,根据某个uid要从redis查询一批数据 ...
- 【路由器】OpenWrt 手动编译 ipk
目录 .ipk 文件 编译准备 编译 .ipk 文件 更新 feeds 配置平台 获取交叉编译链 添加需要编译的第三方软件包 参考资料 .ipk 文件 .ipk 文件是可以通过 OpenWrt 的包管 ...