从snabbdom开始学习vue diff算法(暴力拆除篇)

从snabbdom开始学习vue diff算法(暴力拆除篇)

前言

我学习此算法的目的是为了三点:面试,学习思路,学习敲代码的风格

我在此次学习过程中真的,感受到的是,这些源码其实并不难,我们只是不了解代码的作者想要干什么,所以大家不要抱着很难的态度来看这篇文章。

同时,这篇文章中也有很多我没注意到并且写的很不好的地方,大家多多包涵,能帮我改出错误的话我会非常开心。

第一阶段:介绍snabbdom几个主要的函数是什么作用

我们一下在snabbdom github中出示的示例代码

import {
     
  init,
  classModule,
  propsModule,
  styleModule,
  eventListenersModule,
  h,
} from "snabbdom";

const patch = init([classModule,propsModule,styleModule,eventListenersModule])

let v1 = h('a', {
     
  props: {
     
    href: 'http://www.baidu.com',
    style: 'color:red;'
  }
}, "asdas")

let v2 = h('div', {
     props: {
     class: 'asd'}}, [h('div', {
     props: {
     class: 'asd'}}, '这是子节点1')h('div', {
     props: {
     class: 'asd'}}, '这是子节点2')])


const App = document.querySelector('#app')
patch(App, v1)

看起来也简单也不简单,首先我们排除跟我们想要学的简单diff不相关的代码:classModule, propsModule, styleModule, eventListenersModule

还有这个init函数,他只是创建了patch函数我们也把他省略

我说排除的意思是现在根本不用看这些代码干了什么,就当他没有出现过,你也不必深究他们干了什么。

那简化后的代码就变成了这样

import {
     
  h,
} from "snabbdom";


let v1 = h('a', {
     
  props: {
     
    href: 'http://www.baidu.com'
  }
}, "这是一个a标签")

let v2 = h('div', {
     props: {
     class: 'asd'}}, [h('div', {
     props: {
     class: 'asd'}}, '这是子节点1')h('div', {
     props: {
     class: 'asd'}}, '这是子节点2')])


const App = document.querySelector('#app')
patch(App, v1)

现在如果还有看不懂的就是v2这个函数的参数吧,没关系我把数据结构给你画出来

从snabbdom开始学习vue diff算法(暴力拆除篇)_第1张图片

清晰了吗? 现在开始说说这h函数 patch函数的作用吧。先说一些简单的作用,避免难理解

h函数

参数 作用
第一个参数 声明标签名字
第二个参数 是一个对象,我们认为他就这一个props对象参数。props对象里面包括含的数据就是我们dom元素的属性
第三个参数 如果为字符串则就是该dom元素的innerText, 如果为h函数产生的对象那么他就是该元素的子元素
  • h函数的作用:将我们传入的参数转变成虚拟的dom对象(VNode),什么是dom对象?

看图:

从snabbdom开始学习vue diff算法(暴力拆除篇)_第2张图片

我觉得没办法解释,还是直接看图来的快。

patch函数

参数 作用
oldVNode 或 element 旧的虚拟dom对象 或者 dom元素
newVNode 新的虚拟dom对象
  • patch函数的作用:将新的虚拟dom转化为真正的dom替换旧的虚拟dom

这个patch就比较难了,真正diff的地方,但是我们先不用着急,我们这次先把h函数写出来,感受一下写源码的乐趣,并且慢慢认识一下这些代码的目的

第二阶段:手写h函数

我们用一个流程图来解释h函数做了什么

从snabbdom开始学习vue diff算法(暴力拆除篇)_第3张图片

现在请你根据这张图返回本文章的第一阶段,看我写的那个h函数的介绍,有无茅塞顿开的感觉,或者更了解了h函数的作用。

我们写代码的时候尽量保持到一个函数他就一个作用。

所以我们按照这个思想来看的话h函数他就是一个判断的作用对不对,我们完全可以吧生成VNode的代码单提出来生成一个新的函数就叫 vnode

export default function (
  sel,
  data,
  children,
  text,
  elm
) {
     
  return {
     
    sel,
    data,
    children,
    text,
    elm
  }
}

他就只是将他的参数变成了对象。不要多想。现在就开始用代码来写h函数了

import VNode from './vnode'
import {
      ifString, ifNumber, ifArrary, ifObject } from '../tool/tool'

function H () {
     
  
  
}

大家看到了我创建的tool这个js文件了吧,他真的很简单,但是这么写真的很有逼格,现在来看一下tool.js文件

export function ifString (c) {
     
  return typeof c === 'string'
}

export function ifNumber (c) {
     
  return typeof c === 'number'
}

export function ifArrary (c) {
     
  return Array.isArray(c)
}

export function ifObject (c) {
     
  return typeof c === 'object'
}

好了现在根据流程图来写

从snabbdom开始学习vue diff算法(暴力拆除篇)_第4张图片

import VNode from './vnode'
import {
      ifString, ifNumber, ifArrary, ifObject } from '../tool/tool'

function H (sel, data, c) {
     
    
}

从snabbdom开始学习vue diff算法(暴力拆除篇)_第5张图片

import VNode from './vnode'
import {
      ifString, ifNumber, ifArrary, ifObject } from '../tool/tool'

function H (sel, data, c) {
     
    if (ifString(c) || ifNumber(c)) {
     
        
     } else if(ifArrary(c)) {
     
         
     }
}

从snabbdom开始学习vue diff算法(暴力拆除篇)_第6张图片

import VNode from './vnode'
import {
      ifString, ifNumber, ifArrary, ifObject } from '../tool/tool'

function H (sel, data, c) {
     
    if (ifString(c) || ifNumber(c)) {
     
        return VNode(sel, data, undefined, c, undefined)
     } else if(ifArrary(c)) {
     
         
     }
}

从snabbdom开始学习vue diff算法(暴力拆除篇)_第7张图片

import VNode from './vnode'
import {
      ifString, ifNumber, ifArrary, ifObject } from '../tool/tool'

function H (sel, data, c) {
     
    if (ifString(c) || ifNumber(c)) {
     
        return VNode(sel, data, undefined, c, undefined)
     } else if(ifArrary(c)) {
     
         let ch = []
         let tx = ''
         // 判断非空
         if(c.length === 0)
             throw new Error("数组不要为空")

         // 判断数组内元素if对象
         for (let i = 0; i < c.length; i++) {
     
             if(!(ifObject(c[i]) || c[i].sel || ifString(c[i]))) {
     
                 throw new Error("数组内请传入对象")
             }
             if (ifObject(c[i])) {
     
                 ch.push(c[i])
             }
             ifString(c[i])
                 ?
                 tx = c[i]
                 : 
                 tx = ''
         }
         return VNode(sel, data, ch, tx, undefined)
     }
}

从snabbdom开始学习vue diff算法(暴力拆除篇)_第8张图片
从snabbdom开始学习vue diff算法(暴力拆除篇)_第9张图片

很简单。

手写patch函数

还是先看流程图

从snabbdom开始学习vue diff算法(暴力拆除篇)_第10张图片

因为这个文章是入门先讲解的暴力破解这一块,所以想知道精细比较的看我下一篇文章。

我们现在根据流程图来敲代码

从snabbdom开始学习vue diff算法(暴力拆除篇)_第11张图片

import VNode from './vnode'

function patch (oldVNode, newVNode) {
     
  
}

从snabbdom开始学习vue diff算法(暴力拆除篇)_第12张图片

import VNode from './vnode'

function patch (oldVNode, newVNode) {
     
  // 判断isElement
  if(oldVNode.sel === '' || oldVNode.sel === undefined) {
     
    
  }
}

从snabbdom开始学习vue diff算法(暴力拆除篇)_第13张图片

import VNode from './vnode'

export function packagingElement (element) {
     
  let el = element.nodeName.toLocaleLowerCase()
  let props = {
     }
  let text = element.innerHTML
  let tags = element.attributes
  
  for (let i = 0; i < tags.length; i++) {
     
    props[tags[i].nodeName] = tags[i].nodeValue
  }

  return VNode(el, {
     props}, [], text, element)
}


function patch (oldVNode, newVNode) {
     
  // 判断isElement
  if(oldVNode.sel === '' || oldVNode.sel === undefined) {
     
    oldVNode =  packagingElement(oldVNode)
  }
}

从snabbdom开始学习vue diff算法(暴力拆除篇)_第14张图片

import VNode from './vnode'

export function packagingElement (element) {
     
  let el = element.nodeName.toLocaleLowerCase()
  let props = {
     }
  let text = element.innerHTML
  let tags = element.attributes
  
  for (let i = 0; i < tags.length; i++) {
     
    props[tags[i].nodeName] = tags[i].nodeValue
  }

  return VNode(el, {
     props}, [], text, element)
}

export function ifEqVNode (VNode1, VNode2) {
     
  return VNode1.key === VNode2.key &&
         VNode2.sel === VNode1.sel
}


function patch (oldVNode, newVNode) {
     
  // 判断isElement
  if(oldVNode.sel === '' || oldVNode.sel === undefined) {
     
    oldVNode =  packagingElement(oldVNode)
  }
    
  if(ifEqVNode(oldVNode, newVNode)) {
     
    console.log("是同一个节点");
  } else {
     
    console.log("不是同一个节点");
    
  }
}

写到这里一道面试题就出来了 vue中key的作用

在这里插入图片描述

略过

从snabbdom开始学习vue diff算法(暴力拆除篇)_第15张图片

import VNode from './vnode'
import createElement from './createElement'


// 封装element oldVNode
export function packagingElement (element) {
     
  let el = element.nodeName.toLocaleLowerCase()
  let props = {
     }
  let text = element.innerHTML
  let tags = element.attributes
  
  for (let i = 0; i < tags.length; i++) {
     
    props[tags[i].nodeName] = tags[i].nodeValue
  }

  return VNode(el, {
     props}, [], text, element)
}

export function ifEqVNode (VNode1, VNode2) {
     
  return VNode1.key === VNode2.key &&
         VNode2.sel === VNode1.sel
}


function patch (oldVNode, newVNode) {
     
  // 判断isElement
  if(oldVNode.sel === '' || oldVNode.sel === undefined) {
     
    oldVNode =  packagingElement(oldVNode)
  }

  if(ifEqVNode(oldVNode, newVNode)) {
     
    console.log("是同一个节点");
  } else {
     
    console.log("不是同一个节点");
    let newVNodeElm = createElement(newVNode)
    // 判断存在
    if(oldVNode.elm.parentNode && newVNodeElm) {
     
      oldVNode.elm.parentNode.insertBefore(newVNodeElm, oldVNode.elm)
    }
  }
}


export default patch

为什么我不讲代码那些方法的作用呢? 因为我觉得代码的作用你都不知道,你就不应该看这篇文章应该先去补补课。

上边用了createElement这个函数,这个函数还是比较难咱们重点讲解一下

createElement函数

参数 作用
vnode 根据此虚拟dom生成真正的dom
import {
      ifArrary, ifObject } from "../tool/tool"

function createElement (vnode) {
     
  let dom = document.createElement(vnode.sel)
  let ar =vnode.data.props ?  Object.keys(vnode.data.props) : []
  ar.forEach(item => {
     
    dom.setAttribute(item, vnode.data.props[item])
  })
  if(
    vnode.text !== '' &&
    (vnode.children === undefined || vnode.children.length === 0)
  ){
     
    dom.innerText = vnode.text
  } else if(ifArrary(vnode.children) && vnode.children.length > 0) {
     
    // 遍历子元素
    for (let i = 0; i < vnode.children.length; i++) {
     
      let ch = vnode.children[i]
      if (ch.text !== '') {
     
        dom.innerText = vnode.text
      }
      let newdom = createElement(ch)
      dom.appendChild(newdom)
    }
  }
  vnode.elm = dom
  return vnode.elm
}

export default createElement

这里面用到了递归,比较难懂对吧,没关系还是看流程图。

从snabbdom开始学习vue diff算法(暴力拆除篇)_第16张图片

这个流程图就是上边createElement函数的代码书写顺序,非常明了。

那到这里暴力拆除就完成了。谢谢大家的观看

你可能感兴趣的:(vue原理,vue,js,dom,算法)