好家伙,

本节来解决我们上一章留下来的问题,

新旧节点同时有儿子的情况本章继续解决

1.要做什么?

本章将解决,

1.在相同tag下子元素的替换问题

2.使用双指针进行元素替换,

实现效果如下:

 

    let vm1 = new Vue({data:{name:'张三'}})
let render1 = compileToFunction(`<ul>
<li style="background:yellow" key="c">我是黄色</li>
</ul>`)
let vnode1 = render1.call(vm1)
document.body.appendChild(createELm(vnode1)) //数据更新
let vm2 = new Vue({data:{name:'李四'}})
let render2 = compileToFunction(`<ul>
<li style="background:blue" key="c">我是蓝色</li>
</ul>`)
let vnode2 = render2.call(vm2) //patch 比对
setTimeout(()=>{
patch(vnode1,vnode2)
},2000)

2.思路

let vm1 = new Vue({
data: {
name: '张三'
}
})
let render1 = compileToFunction(`<ul>
<li style="background:red" key="a">a</li>
<li style="background:pink" key="b">b</li>
<li style="background:blue" key="c">c</li>
</ul>`)
let vnode1 = render1.call(vm1)
document.body.appendChild(createELm(vnode1)) //数据更新
let vm2 = new Vue({
data: {
name: '李四'
}
})
let render2 = compileToFunction(`<ul>
<li style="background:red" key="a">a</li>
<li style="background:pink" key="b">b</li>
<li style="background:blue" key="c">c</li>
<li style="background:yellow" key="d">d</li>
</ul>`)
let vnode2 = render2.call(vm2) setTimeout(() => {
patch(vnode1, vnode2)
}, 2000)

我们用这个例子来举例

1.正序(从头开始)

 

 

 

找到不同(原先没有的)的项,再将它添加上去

大概的思路就是如此.

但同时,根据不同的情况

我们还有多种比对方法

2.2.逆序

 

2.3.交叉对比(从头)

 

2.4.交叉对比(从尾)

 

3.代码实现

3.1.双指针

//双指针 遍历
let oldStartIndex = 0 //老的开头索引
let oldStartVnode = oldChildren[oldStartIndex];
let oldEndIndex = oldChildren.length - 1
let oldEndVnode = oldChildren[oldEndIndex] let newStartIndex = 0 //新的开头索引
let newStartVnode = newChildren[newStartIndex];
let newEndIndex = newChildren.length - 1
let newEndVnode = newChildren[newEndIndex]

双指针的写法非常粗暴,但是好用

3.2.循环

(照着上面的图看)

while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
//比对子元素
console.log(666)
if (isSomeVnode(oldStartVnode, newStartVnode)) {
//递归
debugger;
//1 从头部开始
patch(oldStartVnode, newStartVnode);
//移动指针
oldStartVnode = oldChildren[++oldStartIndex];
newStartVnode = oldChildren[++newStartIndex];
}//2 从尾部开始
else if(isSomeVnode(oldEndVnode, newEndVnode)){
//
patch(oldEndVnode, newEndVnode);
oldEndVnode = oldChildren[--oldEndIndex]
newEndVnode = newChildren[--newEndIndex]
}//3 交叉比对 从头
else if(isSomeVnode(oldStartVnode,newEndVnode)){
patch(oldStartVnode, newEndVnode);
oldStartVnode =oldChildren[++oldStartIndex]
newEndVnode = newChildren[--newEndIndex];
}//4 交叉比对 从尾
else if(isSomeVnode(oldEndVnode,newStartVnode)){
patch(oldEndVnode, newStartVnode);
oldEndVnode =oldChildren[--oldStartIndex]
newStartVnode = newChildren[++newStartIndex];
}
}

3.3.isSomeVnode()

isSomeVnode()方法用于判断两个节点是否相同

function isSomeVnode(oldContext, newContext) {
// return true
return (oldContext.tag == newContext.tag) && (oldContext.key === newContext.key);
}

3.4.添加多余的子儿子

//判断完毕,添加多余的子儿子  例子:旧的a b c  新的 a b c d  将d添加到parent
if (newStartIndex <= newEndIndex) {
for (let i = newStartIndex; i <= newEndIndex; i++) { parent.appendChild(createELm(newChildren[i]))
}
}

搞定

4.patch.js完整代码

如下:

export function patch(oldVnode, Vnode) {
debugger; //原则 将虚拟节点转换成真实的节点
// 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, newChildren, parent) {
//diff算法 做了很多优化 例子<div>11</div> 更新为 <div>22</div>
//dom中操作元素 常用的 思想 尾部添加 头部添加 倒叙和正序的方式 //双指针 遍历
let oldStartIndex = 0 //老的开头索引
let oldStartVnode = oldChildren[oldStartIndex];
let oldEndIndex = oldChildren.length - 1
let oldEndVnode = oldChildren[oldEndIndex] let newStartIndex = 0 //新的开头索引
let newStartVnode = newChildren[newStartIndex];
let newEndIndex = newChildren.length - 1
let newEndVnode = newChildren[newEndIndex]
console.log(oldEndIndex,newEndIndex)
console.log(oldEndVnode,newEndVnode) while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
//比对子元素
console.log(666)
if (isSomeVnode(oldStartVnode, newStartVnode)) {
//递归
debugger;
//1 从头部开始
patch(oldStartVnode, newStartVnode);
//移动指针
oldStartVnode = oldChildren[++oldStartIndex];
newStartVnode = oldChildren[++newStartIndex];
}//2 从尾部开始
else if(isSomeVnode(oldEndVnode, newEndVnode)){
//
patch(oldEndVnode, newEndVnode);
oldEndVnode = oldChildren[--oldEndIndex]
newEndVnode = newChildren[--newEndIndex]
}//3 交叉比对 从头
else if(isSomeVnode(oldStartVnode,newEndVnode)){
patch(oldStartVnode, newEndVnode);
oldStartVnode =oldChildren[++oldStartIndex]
newEndVnode = newChildren[--newEndIndex];
}//4 交叉比对 从尾
else if(isSomeVnode(oldEndVnode,newStartVnode)){
patch(oldEndVnode, newStartVnode);
oldEndVnode =oldChildren[--oldStartIndex]
newStartVnode = newChildren[++newStartIndex];
}
}
//判断完毕,添加多余的子儿子 a b c 新的 a b c d
console.log(newEndIndex)
if (newStartIndex <= newEndIndex) {
for (let i = newStartIndex; i <= newEndIndex; i++) { parent.appendChild(createELm(newChildren[i]))
}
}
}
function isSomeVnode(oldContext, newContext) {
// return true
return (oldContext.tag == newContext.tag) && (oldContext.key === newContext.key);
} //添加属性
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
}

Vue源码学习(十五):diff算法(二)交叉比对(双指针)的更多相关文章

  1. Vue源码学习1——Vue构造函数

    Vue源码学习1--Vue构造函数 这是我第一次正式阅读大型框架源码,刚开始的时候完全不知道该如何入手.Vue源码clone下来之后这么多文件夹,Vue的这么多方法和概念都在哪,完全没有头绪.现在也只 ...

  2. C# DateTime的11种构造函数 [Abp 源码分析]十五、自动审计记录 .Net 登陆的时候添加验证码 使用Topshelf开发Windows服务、记录日志 日常杂记——C#验证码 c#_生成图片式验证码 C# 利用SharpZipLib生成压缩包 Sql2012如何将远程服务器数据库及表、表结构、表数据导入本地数据库

    C# DateTime的11种构造函数   别的也不多说没直接贴代码 using System; using System.Collections.Generic; using System.Glob ...

  3. Vue源码学习二 ———— Vue原型对象包装

    Vue原型对象的包装 在Vue官网直接通过 script 标签导入的 Vue包是 umd模块的形式.在使用前都通过 new Vue({}).记录一下 Vue构造函数的包装. 在 src/core/in ...

  4. 最新 Vue 源码学习笔记

    最新 Vue 源码学习笔记 v2.x.x & v3.x.x 框架架构 核心算法 设计模式 编码风格 项目结构 为什么出现 解决了什么问题 有哪些应用场景 v2.x.x & v3.x.x ...

  5. Vue源码学习三 ———— Vue构造函数包装

    Vue源码学习二 是对Vue的原型对象的包装,最后从Vue的出生文件导出了 Vue这个构造函数 来到 src/core/index.js 代码是: import Vue from './instanc ...

  6. 【Vue源码学习】依赖收集

    前面我们学习了vue的响应式原理,我们知道了vue2底层是通过Object.defineProperty来实现数据响应式的,但是单有这个还不够,我们在data中定义的数据可能没有用于模版渲染,修改这些 ...

  7. VUE 源码学习01 源码入口

    VUE[version:2.4.1] Vue项目做了不少,最近在学习设计模式与Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后 ...

  8. Vue源码学习(一):调试环境搭建

    最近开始学习Vue源码,第一步就是要把调试环境搭好,这个过程遇到小坑着实费了点功夫,在这里记下来 一.调试环境搭建过程 1.安装node.js,具体不展开 2.下载vue项目源码,git或svn等均可 ...

  9. Vue 源码学习(1)

    概述 我在闲暇时间学习了一下 Vue 的源码,有一些心得,现在把它们分享给大家. 这个分享只是 Vue源码系列 的第一篇,主要讲述了如下内容: 寻找入口文件 在打包的过程中 Vue 发生了什么变化 在 ...

  10. 【Vue源码学习】响应式原理探秘

    最近准备开启Vue的源码学习,并且每一个Vue的重要知识点都会记录下来.我们知道Vue的核心理念是数据驱动视图,所有操作都只需要在数据层做处理,不必关心视图层的操作.这里先来学习Vue的响应式原理,V ...

随机推荐

  1. OO第二次大作业

    前言 前言的前言 第二篇blog跟上一篇只隔了将近一个月,但是感觉心境上好像发生了很多的变化,认识到了自己存在的很多不足(可能是菜单折磨的),感觉对很多东西都一知半解,希望在写完这篇总结性blog之后 ...

  2. 银河麒麟等 Linux系统 安装 .net 5,net 6及更高版本的方法

    最近项目上用到 银河麒麟的操作系统,需要搭建 .net 跨平台方案.一开始使用各种命令都安装不上,很多提示命令找不到,或者下载包时候网络无法下载. 网上教程很多,但没有一个是成功的,多数使用 apt ...

  3. 基于C#的无边框窗体阴影绘制方案 - 开源研究系列文章

    今天介绍无边框窗体阴影绘制的内容. 上次有介绍使用双窗体的方法来显示阴影,这次介绍使用API函数来进行绘制.这里使用的是Windows API函数,操作系统的窗体也是用的这个来进行的绘制. 1. 项目 ...

  4. [语音识别] 基于Python构建简易的音频录制与语音识别应用

    语音识别技术的快速发展为实现更多智能化应用提供了无限可能.本文旨在介绍一个基于Python实现的简易音频录制与语音识别应用.文章简要介绍相关技术的应用,重点放在音频录制方面,而语音识别则关注于调用相关 ...

  5. 如何基于 Kubernetes 实现优质开发者平台体验?

    内部开发者平台(或 IDP)是使开发团队能够更快.更轻松.更一致地交付应用程序的基础设施.Kubernetes 本身是一个功能强大的平台,但它引入了太多复杂性和功能,因此不能简单地将其作为 IDP 交 ...

  6. 《SQL与数据库基础》19. 日志

    目录 日志 错误日志 二进制日志 日志格式 日志查看 日志删除 查询日志 慢查询日志 本文以 MySQL 为例 日志 错误日志 错误日志是 MySQL 中最重要的日志之一,它记录了当 mysql 启动 ...

  7. API数据接口的应用步骤

    API(Application Programming Interface)是现代软件应用程序开发中的一项重要技术,它允许不同的应用程序之间进行通信和数据交换.API接口通过提供统一的访问点,使得应用 ...

  8. 【项目源码】JSP超市积分管理系统源码下载

    会员积分管理系统的目标是为中小规模的会员积分管理系统的目标是为中小规模的超市提供会员信息管理.会员积分管理以及积分兑换管理的具体解决方案. 网站和积分管理系统采用B/S结构,使用J2EE的技术开发完成 ...

  9. 文心一言 VS 讯飞星火 VS chatgpt (91)-- 算法导论8.3 4题

    四.用go语言,说明如何在 O(n) 时间内,对 0 到 $n^3-1$ 区间内的n个整数进行排序. 文心一言: 在 O(n) 时间内对 0 到 n^3-1 区间内的 n 个整数进行排序,可以使用基数 ...

  10. HTML网页/KRPano项目一键打包EXE工具(HTML网页打包成单个windows可执行文件exe)

    HTML一键打包EXE工具使用说明 工具简介 HTML一键打包EXE工具(HTML封装EXE,桌件)能把任意HTML项目(网址)一键打包为单个EXE文件,可以脱离浏览器和服务器,直接双击即可运行.支持 ...