我学习此算法的目的是为了三点:面试,学习思路,学习敲代码的风格
我在此次学习过程中真的,感受到的是,这些源码其实并不难,我们只是不了解代码的作者想要干什么,所以大家不要抱着很难的态度来看这篇文章。
同时,这篇文章中也有很多我没注意到并且写的很不好的地方,大家多多包涵,能帮我改出错误的话我会非常开心。
我们一下在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函数
的作用吧。先说一些简单的作用,避免难理解
参数 | 作用 |
---|---|
第一个参数 | 声明标签名字 |
第二个参数 | 是一个对象,我们认为他就这一个props对象参数。props对象里面包括含的数据就是我们dom元素的属性 |
第三个参数 | 如果为字符串 则就是该dom元素的innerText, 如果为h函数产生的对象那么他就是该元素的子元素 |
看图:
我觉得没办法解释,还是直接看图来的快。
参数 | 作用 |
---|---|
oldVNode 或 element | 旧的虚拟dom对象 或者 dom元素 |
newVNode | 新的虚拟dom对象 |
这个patch就比较难了,真正diff的地方,但是我们先不用着急,我们这次先把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)
}
}
很简单。
还是先看流程图
因为这个文章是入门先讲解的暴力破解这一块,所以想知道精细比较的看我下一篇文章。
我们现在根据流程图来敲代码
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这个函数,这个函数还是比较难咱们重点讲解一下
参数 | 作用 |
---|---|
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
函数的代码书写顺序,非常明了。
那到这里暴力拆除就完成了。谢谢大家的观看