好家伙,

本篇将会解释要以下效果的实现

1.目标

我们要实现以下元素替换的效果

gif:

 

以上例子的代码:

  1. //创建vnode
  2. let vm1 = new Vue({data:{name:'张三'}})
  3. let render1 = compileToFunction(`<a>{{name}}</a>`)
  4. let vnode1 = render1.call(vm1)
  5. document.body.appendChild(createELm(vnode1))
  6. //数据更新
  7. let vm2 = new Vue({data:{name:'李四'}})
  8. let render2 = compileToFunction(`<div>{{name}}</div>`)
  9. let vnode2 = render2.call(vm2)
  10. //属性添加
  11. let vm3 = new Vue({data:{name:'李四'}})
  12. let render3 = compileToFunction(`<div style="color:red">{{name}}</div>`)
  13. let vnode3 = render3.call(vm3)
  14. //patch 比对
  15. setTimeout(()=>{
  16. patch(vnode1,vnode2)
  17. },2000)
  18. setTimeout(()=>{
  19. patch(vnode2,vnode3)
  20. },3000)

 

以上例子中compileToFunction()方法的详细解释

Vue源码学习(四):<templete>渲染第三步,将ast语法树转换为渲染函数

一句话解释,这是一个将模板变为render函数的方法

 

开搞:

思路非常简单,依旧是对不同的情况分类处理

 

2.代码解释

patch.js

  1. export function patch(oldVnode, Vnode) {
  2. //原则 将虚拟节点转换成真实的节点
  3. console.log(oldVnode, Vnode)
  4. console.log(oldVnode.nodeType)
  5. console.log(Vnode.nodeType)
  6. //第一次渲染 oldVnode 是一个真实的DOM
  7. //判断ldVnode.nodeType是否唯一,意思就是判断oldVnode是否为属性节点
  8. if (oldVnode.nodeType === 1) {
  9. console.log(oldVnode, Vnode) //注意oldVnode 需要在加载 mount 添加上去 vm.$el= el
  10. let el = createELm(Vnode) // 产生一个新的DOM
  11. let parentElm = oldVnode.parentNode //获取老元素(app) 父亲 ,body
  12. // console.log(oldVnode)
  13. // console.log(parentElm)
  14. parentElm.insertBefore(el, oldVnode.nextSibling) //当前真实的元素插入到app 的后面
  15. parentElm.removeChild(oldVnode) //删除老节点
  16. //重新赋值
  17. return el
  18. }else{ // diff
  19. console.log(oldVnode.nodeType)
  20. console.log(oldVnode, Vnode)
  21. //1 元素不是一样
  22. if(oldVnode.tag!==Vnode.tag){
  23. //旧的元素 直接替换为新的元素
  24. return oldVnode.el.parentNode.replaceChild(createELm(Vnode),oldVnode.el)
  25. }
  26. //2 标签一样 text 属性 <div>1</div> <div>2</div> tag:undefined
  27. if(!oldVnode.tag){
  28. if(oldVnode.text !==Vnode.text){
  29. return oldVnode.el.textContent = Vnode.text
  30. }
  31. }
  32. //2.1属性 (标签一样) <div id='a'>1</div> <div style>2</div>
  33. //在updataRpors方法中处理
  34. //方法 1直接复制
  35. let el = Vnode.el = oldVnode.el
  36. updataRpors(Vnode,oldVnode.data)
  37. //diff子元素 <div>1</div> <div></div>
  38. let oldChildren = oldVnode.children || []
  39. let newChildren = Vnode.children || []
  40. if(oldChildren.length>0&&newChildren.length>0){ //老的有儿子 新有儿子
  41. //创建方法
  42. updataChild(oldChildren,newChildren,el)
  43. }else if(oldChildren.length>0&&newChildren.length<=0){//老的元素 有儿子 新的没有儿子
  44. el.innerHTML = ''
  45. }else if(newChildren.length>0&&oldChildren.length<=0){//老没有儿子 新的有儿子
  46. for(let i = 0;i<newChildren.length;i++){
  47. let child = newChildren[i]
  48. //添加到真实DOM
  49. el.appendChild(createELm(child))
  50. }
  51. }
  52. }
  53. }
  54. function updataChild (oldChildren,ewChildren,el){
  55. }
  56. //添加属性
  57. function updataRpors(vnode,oldProps={}){ //第一次
  58. let newProps = vnode.data ||{} //获取当前新节点 的属性
  59. let el = vnode.el //获取当前真实节点 {}
  60. //1老的有属性,新没有属性
  61. for(let key in oldProps){
  62. if(!newProps[key]){
  63. //删除属性
  64. el.removeAttribute[key] //
  65. }
  66. }
  67. //2演示 老的 style={color:red} 新的 style="{background:red}"
  68. let newStyle = newProps.style ||{} //获取新的样式
  69. let oldStyle = oldProps.style ||{} //老的
  70. for(let key in oldStyle){
  71. if(!newStyle[key]){
  72. el.style =''
  73. }
  74. }
  75. //新的
  76. for(let key in newProps){
  77. if(key ==="style"){
  78. for(let styleName in newProps.style){
  79. el.style[styleName] = newProps.style[styleName]
  80. }
  81. }else if( key ==='class'){
  82. el.className = newProps.class
  83. }else{
  84. el.setAttribute(key,newProps[key])
  85. }
  86. }
  87. }
  88. //vnode 变成真实的Dom
  89. export function createELm(vnode) {
  90. let { tag, children, key, data, text } = vnode
  91. //注意
  92. if (typeof tag === 'string') { //创建元素 放到 vnode.el上
  93. vnode.el = document.createElement(tag) //创建元素
  94. updataRpors(vnode)
  95. //有儿子
  96. children.forEach(child => {
  97. // 递归 儿子 将儿子渲染后的结果放到 父亲中
  98. vnode.el.appendChild(createELm(child))
  99. })
  100. } else { //文本
  101. vnode.el = document.createTextNode(text)
  102. }
  103. return vnode.el //新的dom
  104. }

 

三个方法,我们一个个看

2.1.patch()

  1. export function patch(oldVnode, Vnode) {
  2. //原则 将虚拟节点转换成真实的节点
  3. console.log(oldVnode, Vnode)
  4. console.log(oldVnode.nodeType)
  5. console.log(Vnode.nodeType)
  6. //第一次渲染 oldVnode 是一个真实的DOM
  7. //判断ldVnode.nodeType是否唯一,意思就是判断oldVnode是否为属性节点
  8. if (oldVnode.nodeType === 1) {
  9. console.log(oldVnode, Vnode) //注意oldVnode 需要在加载 mount 添加上去 vm.$el= el
  10. let el = createELm(Vnode) // 产生一个新的DOM
  11. let parentElm = oldVnode.parentNode //获取老元素(app) 父亲 ,body
  12. // console.log(oldVnode)
  13. // console.log(parentElm)
  14. parentElm.insertBefore(el, oldVnode.nextSibling) //当前真实的元素插入到app 的后面
  15. parentElm.removeChild(oldVnode) //删除老节点
  16. //重新赋值
  17. return el
  18. }else{ // diff
  19. console.log(oldVnode.nodeType)
  20. console.log(oldVnode, Vnode)
  21. //1 元素不是一样
  22. if(oldVnode.tag!==Vnode.tag){
  23. //旧的元素 直接替换为新的元素
  24. return oldVnode.el.parentNode.replaceChild(createELm(Vnode),oldVnode.el)
  25. }
  26. //2 标签一样 text 属性 <div>1</div> <div>2</div> tag:undefined
  27. if(!oldVnode.tag){
  28. if(oldVnode.text !==Vnode.text){
  29. return oldVnode.el.textContent = Vnode.text
  30. }
  31. }
  32. //2.1属性 (标签一样) <div id='a'>1</div> <div style>2</div>
  33. //在updataRpors方法中处理
  34. //方法 1直接复制
  35. let el = Vnode.el = oldVnode.el
  36. updataRpors(Vnode,oldVnode.data)
  37. //diff子元素 <div>1</div> <div></div>
  38. let oldChildren = oldVnode.children || []
  39. let newChildren = Vnode.children || []
  40. if(oldChildren.length>0&&newChildren.length>0){ //老的有儿子 新有儿子
  41. //创建方法
  42. updataChild(oldChildren,newChildren,el)
  43. }else if(oldChildren.length>0&&newChildren.length<=0){//老的元素 有儿子 新的没有儿子
  44. el.innerHTML = ''
  45. }else if(newChildren.length>0&&oldChildren.length<=0){//老没有儿子 新的有儿子
  46. for(let i = 0;i<newChildren.length;i++){
  47. let child = newChildren[i]
  48. //添加到真实DOM
  49. el.appendChild(createELm(child))
  50. }
  51. }
  52. }
  53. }

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()

  1. //添加属性
  2. function updataRpors(vnode,oldProps={}){ //第一次
  3. let newProps = vnode.data ||{} //获取当前新节点 的属性
  4. let el = vnode.el //获取当前真实节点 {}
  5. //1老的有属性,新没有属性
  6. for(let key in oldProps){
  7. if(!newProps[key]){
  8. //删除属性
  9. el.removeAttribute[key] //
  10. }
  11. }
  12. //2演示 老的 style={color:red} 新的 style="{background:red}"
  13. let newStyle = newProps.style ||{} //获取新的样式
  14. let oldStyle = oldProps.style ||{} //老的
  15. for(let key in oldStyle){
  16. if(!newStyle[key]){
  17. el.style =''
  18. }
  19. }
  20. //新的
  21. for(let key in newProps){
  22. if(key ==="style"){
  23. for(let styleName in newProps.style){
  24. el.style[styleName] = newProps.style[styleName]
  25. }
  26. }else if( key ==='class'){
  27. el.className = newProps.class
  28. }else{
  29. el.setAttribute(key,newProps[key])
  30. }
  31. }
  32. }
  1. updataRpors()是一个更新属性的方法,其主要功能是更新虚拟节点的属性,包括删除不再存在的属性、更新样式和类名等。

 

2.3.createELm()

  1. //vnode 变成真实的Dom
  2. export function createELm(vnode) {
  3. let { tag, children, key, data, text } = vnode
  4. //注意
  5. if (typeof tag === 'string') { //创建元素 放到 vnode.el上
  6. vnode.el = document.createElement(tag) //创建元素
  7. updataRpors(vnode)
  8. //有儿子
  9. children.forEach(child => {
  10. // 递归 儿子 将儿子渲染后的结果放到 父亲中
  11. vnode.el.appendChild(createELm(child))
  12. })
  13. } else { //文本
  14. vnode.el = document.createTextNode(text)
  15. }
  16. return vnode.el //新的dom
  17. }
  1.  

 createELm()是一个用于创建和渲染虚拟DOM的函数.

函数名称为`createELm`,它接收一个参数`vnode`,这个参数是一个虚拟DOM节点对象。

这段代码的主要作用是根据传入的虚拟DOM节点数据结构(`vnode`)创建一个相应的实际DOM元素,并返回该元素。

如果虚拟DOM节点包含子节点,它会递归地为每个子节点创建相应的DOM元素并添加到父节点的DOM元素中。

 

 

Vue源码学习(十四):diff算法patch比对的更多相关文章

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

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

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

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

  3. 最新 Vue 源码学习笔记

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

  4. Linux0.11源码学习(四)

    Linux0.11源码学习(四) linux0.11源码学习笔记 参考资料: https://github.com/sunym1993/flash-linux0.11-talk https://git ...

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

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

  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. flex布局入门

    一.简介 Flexible 单词意思是灵活的意思,flex布局又称为弹性布局或弹性盒子布局 Flex布局(Flexible Box Layout)是CSS3引入的一种布局模型,它旨在提供一种灵活且高效 ...

  2. 黑马2023最新版Java学习路线和资料地址

    地址:https://pan.baidu.com/s/1LxIxcHDO7SYB96SE-GZfuQ 提取码:dor4

  3. python将两个列表组合成元组

    point_x = [A_x, B_x, C_x, D_x] point_y = [A_y, B_y, C_y, D_y] points_tulpe = list(zip(point_x, point ...

  4. Python3入门基础教程

    引:此文是自己学习python过程中的笔记和总结,适合有语言基础的人快速了解python3和没基础的作为学习的大纲,了解学习的方向.知识点:笔记是从多本书和视频上学习后的整合版. (一)初识pytho ...

  5. 26种source-map看花了眼?别急,理解这几个全弄懂

    上一篇 webpack处理模块化源码 的文章中提到了 "source map",这一篇来详细说说. 有什么作用 source map 用于映射编译后的代码与源码,这样如果编译后的代 ...

  6. msfvenom参数简介

    -p, –payload < payload> 指定需要使用的payload(攻击荷载).也可以使用自定义payload,几乎是支持全平台的 -l, –list [module_type] ...

  7. jmeter:内存溢出解决办法

    使用jmeter执行性能测试,报错:java.lang.OutOfMemoryError: Java heap space 需要对jmeter的jvm进行调优.记录如下: 1. 问题记录及分析: 使用 ...

  8. docker service 与 docker stack

    转载请注明出处: 1. Docker Service Docker Service(服务)是用于定义和管理单个容器服务的概念.它是在Docker Swarm集群中运行的容器实例,可以使用docker ...

  9. vite 找不到依赖模块:[plugin:vite:dep-pre-bundle]

    问题描述: 运行项目时,出现[plugin:vite:dep-pre-bundle] 错误.这种问题一般为依赖的包未正常配置相关字段,导致vite无法找到包的入口. 遇到这种模块内.找不到引用模块的, ...

  10. Docker Dockerfile指令大全

    FROM-指定基础镜像 指定基础镜像,并且Dockerfile中第一条指令必须是FROM指令,且在同一个Dockerfile中创建多个镜像时,可以使用多个FROM指令. # 语法格式 FROM < ...