從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這個函式的引數吧,沒關系我把資料結構給你畫出來

清晰了嗎? 現在開始說說這h函式 patch函式的作用吧,先說一些簡單的作用,避免難理解
h函式
| 引數 | 作用 |
|---|---|
| 第一個引數 | 宣告標簽名字 |
| 第二個引數 | 是一個物件,我們認為他就這一個props物件引數,props物件里面包括含的資料就是我們dom元素的屬性 |
| 第三個引數 | 如果為字串則就是該dom元素的innerText, 如果為h函式產生的物件那么他就是該元素的子元素 |
- h函式的作用:將我們傳入的引數轉變成虛擬的dom物件(VNode),什么是dom物件?
看圖:

我覺得沒辦法解釋,還是直接看圖來的快,
patch函式
| 引數 | 作用 |
|---|---|
| oldVNode 或 element | 舊的虛擬dom物件 或者 dom元素 |
| newVNode | 新的虛擬dom物件 |
- patch函式的作用:將新的虛擬dom轉化為真正的dom替換舊的虛擬dom
這個patch就比較難了,真正diff的地方,但是我們先不用著急,我們這次先把h函式寫出來,感受一下寫原始碼的樂趣,并且慢慢認識一下這些代碼的目的
第二階段:手寫h函式
我們用一個流程圖來解釋h函式做了什么

現在請你根據這張圖回傳本文章的第一階段,看我寫的那個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'
}
好了現在根據流程圖來寫

import VNode from './vnode'
import { ifString, ifNumber, ifArrary, ifObject } from '../tool/tool'
function H (sel, data, c) {
}

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)) {
}
}

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)) {
}
}

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


很簡單,
手寫patch函式
還是先看流程圖

因為這個文章是入門先講解的暴力破解這一塊,所以想知道精細比較的看我下一篇文章,
我們現在根據流程圖來敲代碼

import VNode from './vnode'
function patch (oldVNode, newVNode) {
}

import VNode from './vnode'
function patch (oldVNode, newVNode) {
// 判斷isElement
if(oldVNode.sel === '' || oldVNode.sel === undefined) {
}
}

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

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的作用

略過

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
這里面用到了遞回,比較難懂對吧,沒關系還是看流程圖,

這個流程圖就是上邊createElement函式的代碼書寫順序,非常明了,
那到這里暴力拆除就完成了,謝謝大家的觀看
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/283064.html
標籤:其他
